/* -----------------------------------------------------------------------------
*
*  npvideo.c
*
*  ANTz - realtime 3D data visualization tools for the real-world, based on NPE.
*
*  ANTz is hosted at http://openantz.com and NPE at http://neuralphysics.org
*
*  Written in 2010-2017 by Shane Saxon - saxon@openantz.com
*
*  Please see main.c for a complete list of additional code contributors.
*
*  To the extent possible under law, the author(s) have dedicated all copyright 
*  and related and neighboring rights to this software to the public domain
*  worldwide. This software is distributed without any warranty.
*
*  Released under the CC0 license, which is GPL compatible.
*
*  You should have received a copy of the CC0 Public Domain Dedication along
*  with this software (license file named LICENSE.txt). If not, see
*  http://creativecommons.org/publicdomain/zero/1.0/
*
* --------------------------------------------------------------------------- */

//#include "npbmd.h"

using namespace std;

/* SDL_DeckLink - OpenGL DeckLink A/V Playback for GNU/Linux
* Written by Ethan "flibitijibibo" Lee
* http://www.flibitijibibo.com/
*
* Build Instructions:
* g++ -O2 -o SDL_DeckLink SDL_DeckLink.cpp -lSDL2 -lGL -ldl -pthread
*
* Released under public domain.
* No warranty implied; use at your own risk.
*
* The fragment shader located at `shader_fragment` comes from an example found
* in the DeckLink SDK. This example is released under the BSD license.
* See the Blackmagic DeckLink SDK for details.
*/


#include <gl/glew.h>

#include <stdio.h>
//#include <tchar.h>
//#include <conio.h>
//#include <objbase.h>		// Necessary for COM
//#include <comutil.h>
#include "DeckLinkAPI_h.h"
#include "DeckLinkAPI_i.c"

#define GL_GLEXT_PROTOTYPES
//#include "SDL.h"
//#include "SDL_opengl.h"


#define WINDOW_WIDTH 1280
#define WINDOW_HEIGHT 720
#define VIDEO_WIDTH 1280
#define VIDEO_HEIGHT 720
#define VIDEO_MODE bmdModeHD720p5994
/* #define DEBUG_GL_MODE */

static const char *shader_vertex =
"#version 130\n"
"attribute vec2 pos;\n"
"attribute vec2 tex;\n"
"void main(void)\n"
"{\n"
"	gl_Position = vec4(pos.xy, 0.0, 1.0);\n"
"	gl_TexCoord[0].xy = tex;\n"
"}\n";
static const char *shader_fragment =
"#version 130\n"
"uniform sampler2D UYVYtex;\n"
"void main(void)\n"
"{\n"
"	float tx, ty, Y, Cb, Cr, r, g, b;\n"
"	tx = gl_TexCoord[0].x;\n"
"	ty = gl_TexCoord[0].y;\n"
"	int true_width = textureSize(UYVYtex, 0).x * 2;\n"
"	if (fract(floor(tx * true_width + 0.5) / 2.0) > 0.0)\n"
"		Y = texture2D(UYVYtex, vec2(tx,ty)).a;\n"
"	else\n"
"		Y = texture2D(UYVYtex, vec2(tx,ty)).g;\n"
"	Cb = texture2D(UYVYtex, vec2(tx,ty)).b;\n"
"	Cr = texture2D(UYVYtex, vec2(tx,ty)).r;\n"
"	Y = (Y * 256.0 - 16.0) / 219.0;\n"
"	Cb = (Cb * 256.0 - 16.0) / 224.0 - 0.5;\n"
"	Cr = (Cr * 256.0 - 16.0) / 224.0 - 0.5;\n"
"	r = Y + 1.5748 * Cr;\n"
"	g = Y - 0.1873 * Cb - 0.4681 * Cr;\n"
"	b = Y + 1.8556 * Cb;\n"
"	gl_FragColor = vec4(r, g, b, 0.7);\n"
"}\n";
static const float vert_pos[8] =
{
	-1.0f, 1.0f,
	1.0f, 1.0f,
	-1.0f, -1.0f,
	1.0f, -1.0f
};
static const float vert_tex[8] =
{
	0.0f, 0.0f,
	1.0f, 0.0f,
	0.0f, 1.0f,
	1.0f, 1.0f
};


//class CaptureDelegate : public IDeckLinkInputCallback
class DeckLinkInput : public IDeckLinkInputCallback
{
public:
	// OpenGLComposite* pOwner);
	UINT16 *YUVData = NULL;// [VIDEO_WIDTH * VIDEO_HEIGHT];

	//	SDL_AudioDeviceID audio;

	DeckLinkInput()
	{
		//		audio = 0;
	}

	// IUnknown needs only a dummy implementation
	virtual HRESULT	STDMETHODCALLTYPE	QueryInterface(REFIID iid, LPVOID *ppv)	{ return E_NOINTERFACE; }
	virtual ULONG	STDMETHODCALLTYPE	AddRef()	{ return 1; }
	virtual ULONG	STDMETHODCALLTYPE	Release()	{ return 1; }

	virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(
		IDeckLinkVideoInputFrame *videoFrame,
		IDeckLinkAudioInputPacket *audioPacket
		) {
		void *frameData;
		if (!YUVData)
			YUVData = (UINT16*)malloc(VIDEO_WIDTH * VIDEO_HEIGHT * sizeof(UINT16));

		if (videoFrame != NULL)
		{
			videoFrame->GetBytes(&frameData);
		//	SDL_memcpy()
			memcpy(YUVData, frameData, sizeof(UINT16)* VIDEO_WIDTH * VIDEO_HEIGHT);
//			printf("raw frame: %.40s\n", YUVData);
		}
		/*		if (audioPacket != NULL && audio != 0)
		{
		audioPacket->GetBytes(&frameData);
		SDL_QueueAudio(
		audio,
		frameData,
		audioPacket->GetSampleFrameCount() * 4
		);
		}
		*/
		//		free(YUVData);
		return S_OK;
	}

	virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged(
		BMDVideoInputFormatChangedEvents notificationEvents,
		IDeckLinkDisplayMode *newDisplayMode,
		BMDDetectedVideoInputFormatFlags detectedSignalFlags
		) {
		return S_OK;
	}

};

#ifdef DEBUG_GL_MODE
void GLDebugCallback(
	GLenum source,
	GLenum type,
	GLuint id,
	GLenum severity,
	GLint length,
	const GLchar *message,
	const GLvoid *userParam
	) {
	printf("%s\n", message);
}
#endif


/* DeckLink-specific variables */
IDeckLinkIterator *deckLinkIterator;
IDeckLink *deckLinkInstance;
IDeckLinkInput *deckLinkInput;
DeckLinkInput deckLinkInputCallback;


GLuint shaderProgram;

extern "C"
int npInitBMD( void* dataRef)
{
	/* SDL2-specific variables */
//	SDL_Window *window;
//	SDL_GLContext context;
	//	SDL_AudioSpec spec;
//	SDL_Event evt;
	GLubyte run = 1;

	/* OpenGL-specific variables */
//	GLuint texture;
	GLuint vertShader;
	GLuint fragShader;
	GLint shaderLen;
	GLint compile_ok;


	/* Initialize DeckLink stream */
	//zz Linux / OSX method	deckLinkIterator = CreateDeckLinkIteratorInstance();
	CoInitializeEx(NULL, COINIT_MULTITHREADED);
	CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**)&deckLinkIterator);

	if (deckLinkIterator == NULL)
	{
		printf("BMD drivers not installed\n");
		return 0;
	}
	if (deckLinkIterator->Next(&deckLinkInstance) != S_OK)
	{
		printf("BMD video device not found\n");
		return 0;
	}
/*	if (deckLinkIterator->Next(&deckLinkInstance) != S_OK)
	{
		printf("BMD video device 2 not found\n");
		return 0;
	}
*/
	if (deckLinkInstance->QueryInterface(
		IID_IDeckLinkInput,
		(void**)&deckLinkInput
		) != S_OK) {
		printf("BMD video input failed to open\n");
		return 0;
	}
	if (deckLinkInput->EnableVideoInput(
		VIDEO_MODE,
		bmdFormat8BitYUV,
		bmdVideoInputFlagDefault
		) != S_OK) {
		printf("BMD failed to enable video input\n");
		return 0;
	}
	/*	if (deckLinkInput->EnableAudioInput(
	bmdAudioSampleRate48kHz,
	bmdAudioSampleType16bitInteger,
	2
	) != S_OK) {
	printf("DeckLink input failed to enable audio input!\n");
	return 0;
	}
*/
	if (deckLinkInput->SetCallback(&deckLinkInputCallback) != S_OK)
	{
		printf("DeckLink input failed to set input callback!\n");
		return 0;
	}
	if (deckLinkInput->StartStreams() != S_OK)
	{
		printf("DeckLink input failed to start stream!\n");
		return 0;
	}

	printf("DeckLink Video ENABLED!\n");

/*
	// Initialize SDL subsystems 
	SDL_Init(SDL_INIT_VIDEO);// | SDL_INIT_AUDIO);

	// Create window and OpenGL context
	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
#ifdef DEBUG_GL_MODE
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
#endif
	window = SDL_CreateWindow(
		"SDL_DeckLink",
		SDL_WINDOWPOS_CENTERED,
		SDL_WINDOWPOS_CENTERED,
		WINDOW_WIDTH,
		WINDOW_HEIGHT,
		SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN
		);
	context = SDL_GL_CreateContext(window);
	SDL_GL_SetSwapInterval(-1);
*/
	// Create audio output device
	/*	SDL_memset(&spec, '\0', sizeof(spec));
	spec.freq = 48000;
	spec.format = AUDIO_S16;
	spec.channels = 2;
	spec.samples = 4096;
	deckLinkInputCallback.audio = SDL_OpenAudioDevice(
	NULL,
	0,
	&spec,
	NULL,
	0
	);
	SDL_PauseAudioDevice(deckLinkInputCallback.audio, 0);
	*/
	printf("GL ver: %s\n", (char*)glGetString(GL_VERSION));
	/*
	// Extension wrangler initialising
	GLenum glew_status = glewInit();
	if (glew_status != GLEW_OK) {
		printf("Error: glewInit: %s\n", glewGetErrorString(glew_status));
		return EXIT_FAILURE;
	}
	*/
#ifdef DEBUG_GL_MODE
	// Add the ARB_debug_output callback
	glDebugMessageCallbackARB((GLDEBUGPROCARB)GLDebugCallback, NULL);
	glDebugMessageControlARB(
		GL_DONT_CARE,
		GL_DONT_CARE,
		GL_DONT_CARE,
		0,
		NULL,
		GL_TRUE
		);
#endif
	/*
	// Prepare YUV texture
	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);
	glTexParameteri(
		GL_TEXTURE_2D,
		GL_TEXTURE_WRAP_S,
		GL_CLAMP_TO_EDGE
		);
	glTexParameteri(
		GL_TEXTURE_2D,
		GL_TEXTURE_WRAP_T,
		GL_CLAMP_TO_EDGE
		);
	glTexParameteri(
		GL_TEXTURE_2D,
		GL_TEXTURE_MIN_FILTER,
		GL_LINEAR
		);
	glTexParameteri(
		GL_TEXTURE_2D,
		GL_TEXTURE_MAG_FILTER,
		GL_LINEAR
		);
	glTexParameteri(
		GL_TEXTURE_2D,
		GL_TEXTURE_BASE_LEVEL,
		0
		);
	glTexParameteri(
		GL_TEXTURE_2D,
		GL_TEXTURE_MAX_LEVEL,
		0
		);
	glTexImage2D(
		GL_TEXTURE_2D,
		0,
		GL_RGBA,
		VIDEO_WIDTH / 2,
		VIDEO_HEIGHT,
		0,
		GL_BGRA,
		GL_UNSIGNED_INT_8_8_8_8_REV,
		NULL
		);
*/
	// Prepare YUV->RGBA shader program
	shaderLen = strlen(shader_vertex);
	vertShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertShader, 1, &shader_vertex, &shaderLen);
	glCompileShader(vertShader);
	glGetShaderiv(vertShader, GL_COMPILE_STATUS, &compile_ok);
	if (!compile_ok)
		printf("Error in vertex shader\n");

	shaderLen = strlen(shader_fragment);
	fragShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragShader, 1, &shader_fragment, &shaderLen);
	glCompileShader(fragShader);
	glGetShaderiv(fragShader, GL_COMPILE_STATUS, &compile_ok);
	if (!compile_ok)
		printf("Error in fragment shader\n");

	shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertShader);
	glAttachShader(shaderProgram, fragShader);
	glBindAttribLocation(shaderProgram, 0, "pos");
	glBindAttribLocation(shaderProgram, 1, "tex");
	glLinkProgram(shaderProgram);
	glDeleteShader(vertShader);
	glDeleteShader(fragShader);
	glUseProgram(shaderProgram);
	glUniform1i(glGetUniformLocation(shaderProgram, "UYVYtex"), 0);


	// Set the vertex pointers, which will always be the same
	glVertexAttribPointer(
		0,
		2,
		GL_FLOAT,
		GL_FALSE,
		sizeof(float)* 2,
		vert_pos
		);
	glVertexAttribPointer(
		1,
		2,
		GL_FLOAT,
		GL_FALSE,
		sizeof(float)* 2,
		vert_tex
		);
	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(1);

//	glUseProgram(0);
	// Reset the viewport, just in case it's not right already 
//	glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);


/*	while (run)
	{
		// Exit upon user request 
		while (SDL_PollEvent(&evt) > 0)
		{
			if (evt.type == SDL_QUIT)
			{
				run = 0;
			}
		}

		// Convert current YUV image to RGBA, present result
		glTexSubImage2D(
			GL_TEXTURE_2D,
			0,
			0,
			0,
			VIDEO_WIDTH / 2,
			VIDEO_HEIGHT,
			GL_BGRA,
			GL_UNSIGNED_INT_8_8_8_8_REV,
			deckLinkInputCallback.YUVData
			);
		//	printf("%.40s\n", deckLinkInputCallback.YUVData);
		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

		SDL_GL_SwapWindow(window);
	}
*/
	deckLinkInput->PauseStreams();

//	glDeleteProgram(shaderProgram);
//	glDeleteTextures(1, &texture);
	//	SDL_CloseAudioDevice(deckLinkInputCallback.audio);
//	SDL_GL_DeleteContext(context);
//	SDL_DestroyWindow(window);
//	SDL_Quit();
//	CoUninitialize();

	return 0;
}

//------------------------------------------------------------------------------
extern "C" void npCloseBMD(void* dataRef)
{
	return;
}

//------------------------------------------------------------------------------
extern "C" void npStartBMD(void* dataRef)
{
	deckLinkInput->StartStreams();
	return;
}

//------------------------------------------------------------------------------
extern "C" void npUpdateBMD(void* dataRef)
{
  if(0)
	glTexSubImage2D(
		GL_TEXTURE_2D,
		0,
		0,
		0,
		VIDEO_WIDTH / 2,
		VIDEO_HEIGHT,
		GL_BGRA,
		GL_UNSIGNED_INT_8_8_8_8_REV,
		deckLinkInputCallback.YUVData
		);

	glUseProgram(shaderProgram);
	// Set the vertex pointers, which will always be the same
	glVertexAttribPointer(
		0,
		2,
		GL_FLOAT,
		GL_FALSE,
		sizeof(float)* 2,
		vert_pos
		);
	glVertexAttribPointer(
		1,
		2,
		GL_FLOAT,
		GL_FALSE,
		sizeof(float)* 2,
		vert_tex
		);
	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(1);

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
//	glUseProgram(0);

	return;
}

