#include "pch.h"
#include "Video.h"
// #include "Light.h"
// #include "WAlloc.h"
#include "engine/Camera.h"
#include "engine/Engine.h"
#include "engine/Gif.h"
#include "engine/audio/AudioMixer.h"
#include "gl/Framebuffer.h"
#include "gl/Shader.h"
#include "gl/ShaderManager.h"
#include "gl/WGL.h"

void WVideo::stop() {
	// if (m_mp) {
	// 	libvlc_media_player_release(m_mp);
	// 	m_mp = nullptr;
	// }
	// if (m_media) {
	// 	libvlc_media_release(m_media);
	// 	m_media = nullptr;
	// }
}

GLuint WVideo::getVideoFrame(bool* out_updated) {
	std::lock_guard<std::mutex> lock(m_text_lock);
	if (out_updated)
		*out_updated = m_updated;
	if (m_updated) {
		std::swap(m_idx_swap, m_idx_display);
		m_updated = false;
	}
	return m_tex[m_idx_display];
}

bool WVideo::resize(void* data, const libvlc_video_render_cfg_t* cfg, libvlc_video_output_cfg_t* render_cfg) {
	WVideo* that = static_cast<WVideo*>(data);
	// if (cfg->width != that->m_width || cfg->height != that->m_height)
	// 	cleanup(data);

	// glGenTextures(3, that->m_tex);
	// glGenFramebuffers(3, that->m_fbo);
	//
	// for (int i = 0; i < 3; i++) {
	// 	glBindTexture(GL_TEXTURE_2D, that->m_tex[i]);
	// 	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cfg->width, cfg->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	// 	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);
	//
	// 	glBindFramebuffer(GL_FRAMEBUFFER, that->m_fbo[i]);
	// 	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, that->m_tex[i], 0);
	// }
	// glBindTexture(GL_TEXTURE_2D, 0);
	//
	// GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
	//
	// if (status != GL_FRAMEBUFFER_COMPLETE) {
	// 	return false;
	// }

	that->m_width = cfg->width;
	that->m_height = cfg->height;

	// glBindFramebuffer(GL_FRAMEBUFFER, that->m_fbo[that->m_idx_render]);
		
	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	render_cfg->opengl_format = GL_RGBA;
	render_cfg->full_range = true;
	render_cfg->colorspace = libvlc_video_colorspace_BT709;
	render_cfg->primaries  = libvlc_video_primaries_BT709;
	render_cfg->transfer   = libvlc_video_transfer_func_SRGB;
	render_cfg->orientation = libvlc_video_orient_top_left;
	// if(glGetError() != GL_NO_ERROR) {
	// 	int b = 4;
	// 	return false;
	// }
		
	return true;
}

bool WVideo::setup(void** data, const libvlc_video_setup_device_cfg_t* cfg, libvlc_video_setup_device_info_t* out) {
	WVideo* that = static_cast<WVideo*>(*data);
	that->m_width = 0;
	that->m_height = 0;
	return true;
}

void WVideo::cleanup(void* data) {
	WVideo* that = static_cast<WVideo*>(data);
	if (that->m_width == 0 && that->m_height == 0)
		return;

	// glDeleteTextures(3, that->m_tex);
	// glDeleteFramebuffers(3, that->m_fbo);
}

void WVideo::swap(void* data) {
	WVideo* that = static_cast<WVideo*>(data);
	// std::lock_guard<std::mutex> lock(that->m_text_lock);
	// that->m_updated = true;
	// std::swap(that->m_idx_swap, that->m_idx_render);
	// glBindFramebuffer(GL_FRAMEBUFFER, that->m_fbo[that->m_idx_render]);
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	::SwapBuffers(that->m_win);
}

bool WVideo::make_current(void* data, bool current) {
	WVideo* that = static_cast<WVideo*>(data);
	
	wglMakeCurrent(that->m_win, that->m_ctx);
	return true;
	if (current)
		return wglMakeCurrent(that->m_win, that->m_ctx) == TRUE;
		// return SDL_GL_MakeCurrent(that->m_win, that->m_ctx) == 0;
	else
		// return wglMakeCurrent(that->m_win, WEngine->wgl->glContext) == TRUE;
		return wglMakeCurrent(that->m_win, 0) == TRUE;
	// return SDL_GL_MakeCurrent(that->m_win, 0) == 0;
}

void* WVideo::get_proc_address(void*, const char* current) {
	return wglGetProcAddress(current);
	// return SDL_GL_GetProcAddress(current);
}

WVideo::WVideo(std::string_view path) {
	if(WEngine->libvlc_instance == nullptr) {
		// WEngine->libvlc_instance = LoadLibrary("D:\\Programming\\Video Game\\Levwrightian-G-lsd\\out\\x64\\libvlc.dll");
		WEngine->libvlc_instance = LoadLibrary("libvlc.dll");
		
		if (WEngine->libvlc_instance == nullptr) {
			std::cerr << "Error loading libvlc.dll" << std::endl;
			// TODO: error
			// return 1;
		}
	}
	wlog_info("Initializing video: {}", &path[0]);
	
	const char *args[] = {
		// "--verbose=3"
		// "--verbose=4",
		"--input-repeat=2",
		"--no-xlib", 
		"--no-video-title-show"
		// "--vout=gl"
		// "--vout=gl"
	};
	this->m_ctx = WEngine->wgl->glContext;
	this->m_win = WEngine->wgl->hDC;
		
	m_vlc = libvlc_new(sizeof(args) / sizeof(*args), args);
	assert(m_vlc != NULL);

	this->width = -1;
	this->height = -1;
	this->buff_idx = 0;
		
		
	// auto wglCreateContextAttribsARB = reinterpret_cast<PFNWGLCREATECONTEXTATTRIBSARBPROC>(wglGetProcAddress(
	// 	"wglCreateContextAttribsARB"));
	//
	// const int major_min = 3, minor_min = 3;
	// const int contextAttribs[] = {
	// 	WGL_CONTEXT_MAJOR_VERSION_ARB, major_min,
	// 	WGL_CONTEXT_MINOR_VERSION_ARB, minor_min,
	// 	// WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
	// 	WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
	// 	//		WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB,
	// 	0
	// };
	//
	//
	// HWND hWndNew = CreateWindowEx(
	// 	0,
	// 	"YourWindowClass",
	// 	"New OpenGL Window",
	// 	WS_OVERLAPPEDWINDOW | WS_VISIBLE,
	// 	// 0, 0, WEngine->RESX, WEngine->RESY,
	// 	0, 0, 1080, 720,
	// 	NULL, NULL, GetModuleHandle(0), NULL
	// );
	//
	// PIXELFORMATDESCRIPTOR pfd = {0};
	// pfd.nSize = sizeof(pfd);
	// pfd.nVersion = 1;
	// pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
	// pfd.iPixelType = PFD_TYPE_RGBA;
	// pfd.cColorBits = 32;
	// pfd.cAlphaBits = 8;
	// pfd.cDepthBits = 24;
	// HDC hdc = GetDC(hWndNew);
	//
	// int format = ChoosePixelFormat(hdc, &pfd);
	// SetPixelFormat(hdc, format, &pfd);
	//
	// this->m_ctx = wglCreateContextAttribsARB(WEngine->wgl->hDC, 0, contextAttribs);
	// this->m_win = hdc;
		
	// this->m_win = hdc;
		
		
	// HINSTANCE hInstance = ;
	//
	// LPCSTR CLASS_NAME = "LeviathanClass";
	//
	// 	this->hwnd = CreateWindow(CLASS_NAME, 0,
	// 		// WS_POPUP | WS_VISIBLE | WS_POPUP | WS_VISIBLE | WS_CAPTION,
	// 		WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU,
	// 		xPos, yPos, wr.right - wr.left, wr.bottom - wr.top, 0, 0, 0, 0);
	// this->m_win = WEngine->wgl->hDC;



	// HDC hDc = ::GetDC(WEngine->wgl->hwnd);
		
	// HGLRC hRCNew;
	// HWND hWndNew;
	//
	// const int pf = ::ChoosePixelFormat(hDc, &pfd);
	// // if (pf == 0)
	// // 	return false;
	// ReleaseDC(WEngine->wgl->hwnd, hDc);
	// hDc = ::GetDC(WEngine->wgl->hwnd);
	// this->m_ctx = wglCreateContext(WEngine->wgl->hDC);
	// this->m_win = WEngine->wgl->hDC;
		
	// if (!g_hRC)
		
		


	m_media = libvlc_media_new_location(&path[0]);
	assert(m_media != NULL);
	if (m_media == NULL) {
		fprintf(stderr, "unable to create media %s", &path[0]);
		// return false;
	}
	m_mp = libvlc_media_player_new_from_media(m_vlc, m_media);
	assert(m_mp != NULL);
	if (m_mp == NULL) {
		fprintf(stderr, "unable to create media player");
		libvlc_media_release(m_media);
		// return false;
	}
	libvlc_media_release(m_media);


	// libvlc_media_t* media = libvlc_media_player_get_media(m_mp);
	// libvlc_media_get_filestat()
	// libvlc_media_event_manager()
		

		
	// Define the opengl rendering callbacks
	// libvlc_video_set_output_callbacks(m_mp, libvlc_video_engine_opengl,
	// 		setup, cleanup, nullptr, resize, swap,
	// 		make_current, get_proc_address, nullptr, nullptr,
	// 		this);
		
		
	// libvlc_video_set_output_callbacks(m_mp, libvlc_video_engine_opengl,
	// 		setup, cleanup, nullptr, resize, swap,
	// 		make_current, get_proc_address, nullptr, nullptr,
	// 		this);

	libvlc_video_set_callbacks(m_mp, begin_vlc_rendering, end_vlc_rendering, videoDisplayCallback, this);
		
		
	// libvlc_video_set_format(m_mp, "RV24", 1080, 720, 1080*3);
	libvlc_video_set_format(m_mp, "RV24", 1080, 1920, 1080*3);

	// this->buffer = new uint8_t[1920*1080*3*4];
	finished_loading.store(false);

	// libvlc_media_player


	// libvlc_media_list_player_set_playback_mode(
	// 	this->m_mp,
	// 	libvlc_playback_mode_loop
	// 	);
	// Play the video
	libvlc_media_player_play(m_mp);

	{
		std::unique_lock<std::mutex> lock(this->finished_loading_mtx);
		this->finished_loading_cv.wait(lock, [&] {return this->finished_loading.load();});
	}
	// finished_loading.wait(false);
		
	// this->width = 1080;
	// this->height = 720;
		

	
	this->fb = new Framebuffer(FBDesc{
		.textures = {
			WEngine->alloc_textures.push(TextureDesc{
				.resx = uint32_t(this->width), .resy = uint32_t(this->height), .resz = 1,
				// .internal_format = InternalFormat::RGBA8, .tex_access = TexAccess::RW
				.internal_format = InternalFormat::SRGB8, .tex_access = TexAccess::RW
			}).ptr
		}, .name = "Video"
	});
	this->tex = this->fb->textures[0];
		
	// LIBVLC_API char *libvlc_media_get_meta( libvlc_media_t *p_md,
	//                                              libvlc_meta_t e_meta );

	// m_media
		
		
}

void WVideo::update() {
	this->mutex.lock();
	bool vid_need_update =  this->needUpdate;
	int vid_idx = (this->buff_idx  + 3 - 1)%3;
	this->mutex.unlock();

	// libvlc_play
	
	// libvlc_media_player_pause();

	if(!this->paused) {
		libvlc_media_player_play(m_mp);
	}
	if(vid_need_update) {
		int buff_offs = this->width*this->height*3*vid_idx;

		
		this->mutex.lock();
		this->needUpdate = false;
		this->mutex.unlock();
		// p_pixels[0] = &video->buffer[video->width*video->height*3*video->buff_idx];
		
		for(int i = 0 ; i < this->width*this->height; i++) {
			vid_data[i*3 + 0] = this->buffer[i*3 + 0 + buff_offs];
			vid_data[i*3 + 1] = this->buffer[i*3 + 1 + buff_offs];
			vid_data[i*3 + 2] = this->buffer[i*3 + 2 + buff_offs];
			// vid_data[i*4 + 3] = 1;
		}
		// vid->mutex.unlock();
		this->tex->upload_data(&vid_data[0]);
	}
		
}

void* WVideo::begin_vlc_rendering(void* data, void** p_pixels) {
	// Lock pixels. Wait for vlc to draw a frame inside.
	WVideo* video = (WVideo*)data;

	unsigned vid_w;
	unsigned vid_h;
	libvlc_video_get_size(video->m_mp, 0, &vid_w, &vid_h);
		
	if(video->width < 0) {
		libvlc_video_set_format(video->m_mp, "RV24", vid_w, vid_h, vid_w*3);
		// libvlc_video_set_format(video->m_mp, "RV32", vid_w, vid_h, vid_w*3);
		// libvlc_video_set_format(video->m_mp, "YUYV", vid_w, vid_h, vid_w*3);
	// WVideo* vid;
		video->width = vid_w;
		video->height = vid_h;
		video->buffer = new uint8_t[video->width*video->height*3*3];
		video->vid_data.resize(video->width*video->height*4);
		for(int i = 0; i < video->vid_data.size(); i++) {
			video->vid_data[i] = 255;
		}
		// video->finished_loading.store(true);
		// video->finished_loading.notify_one();
		{
			std::lock_guard<std::mutex> lock(video->finished_loading_mtx);
			// video->finished_loading.store(true);
			video->finished_loading = true;
		}
		video->finished_loading_cv.notify_one();
	}
		
	video->mutex.lock();
	// *p_pixels = video->buffer;
	video->buff_idx = (video->buff_idx + 1) % 3;
	p_pixels[0] = &video->buffer[video->width*video->height*3*video->buff_idx];
	video->needUpdate = true;
	video->mutex.unlock();

	
	// libvlc_video_set_format(video->m_mp, "RV24", video->width, video->height, video->width*3);
		
	return NULL; // Not used
}

void WVideo::end_vlc_rendering(void* data, void* id, void* const* p_pixels) {
	// Frame drawn. Unlock pixels.
	WVideo* video = (WVideo*)data;
	// video->mutex.lock();
	// video->mutex.unlock();
}
