/*
	Copyright (C) 2021-2026 Mark Tyler

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program in the file COPYING.
*/

#include "private.h"



mtGin::Window::Window ( int const canvas )
	:
	m_canvas		( canvas )
{
}

mtGin::Window::~Window ()
{
	emit_signal ( GIN_SIGNAL_DESTROY_WINDOW, nullptr );

	SDL_DestroyTexture ( m_texture );
	m_texture = nullptr;

	if ( m_sdl_window )
	{
		if ( m_canvas == GIN_WINDOW_CANVAS_OPENGL )
		{
			SDL_GL_DeleteContext ( m_glcontext );
		}

		if ( m_sdl_renderer )
		{
			SDL_DestroyRenderer ( m_sdl_renderer );
			m_sdl_renderer = nullptr;
		}

		SDL_DestroyWindow ( m_sdl_window );
		m_sdl_window = nullptr;
	}
}

char const * mtGin::Window::name() const
{
	return "mtGin::Window";
}

void mtGin::Window::get_geometry (
	int	& x,
	int	& y,
	int	& w,
	int	& h,
	Uint32	& flags
	)
{
	border_store_hack ();

	SDL_GetWindowPosition ( m_sdl_window, &x, &y );
	SDL_GetWindowSize ( m_sdl_window, &w, &h );

	flags = border_restore_hack ();
}

void mtGin::Window::set_size_min (
	int	const	w,
	int	const	h
	)
{
	border_store_hack ();

	SDL_SetWindowMinimumSize ( m_sdl_window, MAX( GIN_WINDOW_WIDTH_MIN, w ),
		MAX( GIN_WINDOW_HEIGHT_MIN, h ) );

	border_restore_hack ();
}

void mtGin::Window::init (
	char	const * const	title,
	int 		const	x,
	int 		const	y,
	int		const	w,
	int		const	h,
	Uint32			flags
	)
{
	if ( m_sdl_window )
	{
		std::cerr << "mtGin::Window::init: "
			"Only one main window is allowed\n";
		return;
	}

	if ( m_canvas == GIN_WINDOW_CANVAS_OPENGL )
	{
		flags |= SDL_WINDOW_OPENGL;
	}

	m_sdl_window = SDL_CreateWindow ( title, x, y, w, h, flags );
	if ( ! m_sdl_window )
	{
		std::cerr << "Window::init - Unable to create the SDL window: '"
			<< (title ? title : "")
			<< "' flags=" << flags
			<< " x=" << x
			<< " y=" << y
			<< " w=" << w
			<< " h=" << h
			<< " SDL(" << SDL_GetError() << ")"
			<< "\n";
		throw 123;
	}

	border_store_hack ();

	if ( m_canvas == GIN_WINDOW_CANVAS_OPENGL )
	{
		m_glcontext = SDL_GL_CreateContext ( m_sdl_window );

		SDL_GL_MakeCurrent ( m_sdl_window, m_glcontext );
	}

	SDL_SetWindowMinimumSize ( m_sdl_window, GIN_WINDOW_WIDTH_MIN,
		GIN_WINDOW_HEIGHT_MIN );

	border_restore_hack ();

	m_sdl_renderer = SDL_CreateRenderer ( m_sdl_window, -1,
		SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC );

	if ( ! m_sdl_renderer )
	{
		SDL_CreateRenderer ( m_sdl_window, -1, 0 );
	}

	if ( ! m_sdl_renderer )
	{
		std::cerr << "Unable to set up any window renderer!\n";
	}
}

int mtGin::Window::render_scale (
	mtPixmap const * const	pixmap,
	int		const	scale,
	int		const	background
	)
{
	if ( ! m_sdl_window || ! m_sdl_renderer )
	{
		return 1;
	}

	int const w = pixy_pixmap_get_width ( pixmap );
	int const h = pixy_pixmap_get_height ( pixmap );
	int const bpp = pixy_pixmap_get_bytes_per_pixel ( pixmap );

	if ( w < 1 || h < 1 || bpp < 1 )
	{
		return 1;
	}

	if ( scale )
	{
		SDL_SetHint ( SDL_HINT_RENDER_SCALE_QUALITY, "0" );
		SDL_RenderSetIntegerScale ( m_sdl_renderer, SDL_TRUE );
	}

	SDL_DestroyTexture ( m_texture );

	m_texture = SDL_CreateTexture ( m_sdl_renderer,
		SDL_PIXELFORMAT_RGB24,
		SDL_TEXTUREACCESS_STREAMING, w, h );

	m_texture_bpp = 3;

	if ( ! m_texture )
	{
		return 1;
	}

	SDL_RenderSetLogicalSize ( m_sdl_renderer, w, h );
	m_texture_w = w;
	m_texture_h = h;

	SDL_SetRenderDrawColor ( m_sdl_renderer,
		(Uint8)pixy_int_2_red( background ),
		(Uint8)pixy_int_2_green ( background ),
		(Uint8)pixy_int_2_blue ( background ),
		SDL_ALPHA_OPAQUE );

	return 0;
}

int mtGin::Window::render_init_threads ( int const threads )
{
	if ( threads < RENDER_THREADS_MIN || threads > RENDER_THREADS_MAX )
	{
		return 1;
	}

	try
	{
		m_batch_render.reset ( new RenderBatch );
	}
	catch (...)
	{
		std::cerr << "mtGin::Window::render_init_threads error - "
			"couldn't create thread work stuff\n";
		return 1;
	}

	size_t const t = (threads == 0) ? (size_t)mtkit_cpu_threads()
		: (size_t)threads;
	auto * batch = m_batch_render.get();
	batch->init_threads ( t, [this]( GinRenderWork & work )
	{
		gin_render_pixmap ( &work );
	});

	return 0;
}

int mtGin::Window::render_pixmap (
	mtPixmap const	* pixmap,
	int	const	present
	)
{
	if ( ! m_sdl_window || ! m_sdl_renderer || ! m_texture || ! pixmap )
	{
		return 1;
	}

	int const w = pixy_pixmap_get_width ( pixmap );
	int const h = pixy_pixmap_get_height ( pixmap );
	int const bpp = pixy_pixmap_get_bytes_per_pixel ( pixmap );
	unsigned char const * const data = pixy_pixmap_get_canvas ( pixmap );

	if ( w < 1 || h < 1 || bpp < 1 || !data
		|| w != m_texture_w || h != m_texture_h
		)
	{
		// Sanity check failure
		return 1;
	}

	void		* pixels;
	int		pitch;

	if ( SDL_LockTexture ( m_texture, NULL, &pixels, &pitch) < 0 )
	{
		// error
		return 1;
	}

	GinRenderWork work = { (unsigned char *)pixels, pitch, data,
		pixy_pixmap_get_palette_const(pixmap)->color, w, h, 0, h, bpp };

	RenderBatch * batch = m_batch_render.get();
	if ( batch )
	{
		int const tot = MIN( h, (int)batch->thread_total());
		work.ylim = 0;
		for ( int i=0; i < tot; i++ )
		{
			work.ystart = work.ylim;
			work.ylim = (i+1)*h / tot;

			if ( batch->send_work ( work ) )
			{
				std::cerr << "Failed to push work to threads\n";
				gin_render_pixmap ( &work );
			}
		}

		batch->wait_for_empty_queue ();
	}
	else	// No worker threads, so render in a single chunk in main thread
	{
		gin_render_pixmap ( &work );
	}

	SDL_UnlockTexture ( m_texture );
	SDL_RenderClear ( m_sdl_renderer );
	SDL_RenderCopy ( m_sdl_renderer, m_texture, NULL, NULL);

	if ( present )
	{
		SDL_RenderPresent ( m_sdl_renderer );
	}

	return 0;
}

int mtGin::Window::render_texture ()
{
	SDL_RenderClear ( m_sdl_renderer );

	if ( ! m_texture )
	{
		return 1;
	}

	SDL_RenderCopy ( m_sdl_renderer, m_texture, NULL, NULL);

	return 0;
}

int mtGin::Window::set_icon ( mtPixmap const * const pixmap )
{
	SDL_Surface * surface = surface_from_pixmap ( pixmap );

	if ( surface )
	{
		SDL_SetWindowIcon ( m_sdl_window, surface );
		SDL_FreeSurface ( surface );
	}
	else
	{
		return 1;
	}

	return 0;
}

int mtGin::Window::set_icon ( char const * const filename )
{
	mtPixy::SVG	svg;

	if ( svg.load ( filename ) )
	{
		return 1;
	}

	int const w = 64;
	int const h = 64;
	void * const argb = svg.render ( w, h );

	if ( ! argb )
	{
		return 1;
	}

	SDL_Surface * const surface = SDL_CreateRGBSurfaceFrom (
		argb, w, h, 32, 4*w,
		0x00FF0000,
		0x0000FF00,
		0x000000FF,
		0xFF000000
		);

	if ( ! surface )
	{
		return 1;
	}

	SDL_SetWindowIcon ( m_sdl_window, surface );
	SDL_FreeSurface ( surface );

	return 0;
}

mtPixmap * mtGin::Window::dump_to_pixmap () const
{
	int w = 0, h = 0;

	SDL_GetWindowSize ( m_sdl_window, &w, &h );

	mtPixy::Pixmap pm ( pixy_pixmap_new_rgb ( w, h ) );
	unsigned char * const dest = pixy_pixmap_get_canvas ( pm.get() );

	if ( ! dest )
	{
		std::cerr << "export_image: Unable to allocate pixmap\n";
		return nullptr;
	}

	if ( GIN_WINDOW_CANVAS_OPENGL == m_canvas )
	{
		unsigned char * pixels = (unsigned char *)malloc (
			size_t(4 * w * h) );

		if ( ! pixels )
		{
			std::cerr <<"export_image: Unable to allocate pixels\n";
			goto finish;
		}

		glReadPixels ( 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels );

		for ( int y = 0; y < h; y++ )
		{
			unsigned char * d = dest + y * w * 3;
			unsigned char const * s = pixels + (h - y - 1) * w * 4;
			// OpenGL dumps image upside down, so reverse this

			for ( int x = 0; x < w; x++ )
			{
				*d++ = *s++;
				*d++ = *s++;
				*d++ = *s++;
				s++;
			}
		}

		free ( pixels );
	}
	else if ( GIN_WINDOW_CANVAS_2D == m_canvas )
	{
		SDL_Rect rect;
		rect.x = 0;
		rect.y = 0;
		rect.w = w;
		rect.h = h;

		SDL_RenderReadPixels ( m_sdl_renderer, &rect,
			SDL_PIXELFORMAT_RGB24, dest, 3*w );
	}
	else
	{
		std::cerr << "mtGin::Window::dump_to_pixmap Unknown canvas"
			" type\n";
	}

finish:
	return pm.release();
}

Uint32 mtGin::Window::border_store_hack ()
{
	m_border_hack = SDL_GetWindowFlags ( m_sdl_window );

	SDL_SetWindowBordered ( m_sdl_window, SDL_FALSE );

	return m_border_hack;
}

Uint32 mtGin::Window::border_restore_hack ()
{
	Uint32 const state = (m_border_hack & SDL_WINDOW_BORDERLESS);

	SDL_SetWindowBordered ( m_sdl_window, state ? SDL_FALSE : SDL_TRUE );

	return m_border_hack;
}

