/*
	Copyright (C) 2025-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.
*/

#ifndef PIZOR_BE_H_
#define PIZOR_BE_H_

#undef BIN_NAME
#define BIN_NAME "mtpizor"
#undef APP_NAME
#define APP_NAME "mtPizor"

#include <mtkit.h>
#include <mtpixy.h>



// Functions return: 0 = success, NULL = fail; unless otherwise stated.



#ifdef __cplusplus
extern "C" {
#endif

// C API



#define OUTPUT_DIR_FILENAME_EXTENSION	".png"



enum
{
	MODE_WORKING		= 0,
	MODE_PAUSED		= 1,

	THREAD_TOTAL_MIN	= -1,		// -1, 0 Detect from CPU
	THREAD_TOTAL_MAX	= 16,
	THREAD_TOTAL_DEFAULT	= -1,

	PIXMAP_SIZE_MIN		= 100,
	PIXMAP_SIZE_MAX		= 100,

	FPS_DEFAULT		= 20,
	FPS_MIN			= 1,
	FPS_MAX			= 60,
};



typedef struct PixmapWork	PixmapWork;
typedef struct PrndWork		PrndWork;



struct PixmapWork
{
	unsigned char		* dest;
	unsigned char	const	* src;
	int			w, h;
	int			ystart;
	int			ylim;
	uint8_t			* prnd;
	int			prnd_mask;
};



struct PrndWork
{
	uint8_t			* prnd;
	size_t			buflen;
};



int pizor_pixmap_create_palette ( mtPixmap * pixmap );
void do_pixmapwork ( PixmapWork const * work );



#define PREFS_WINDOW_MAXIMIZED		"main.window_max"
#define PREFS_WINDOW_X			"main.window_x"
#define PREFS_WINDOW_Y			"main.window_y"
#define PREFS_WINDOW_W			"main.window_w"
#define PREFS_WINDOW_H			"main.window_h"



#ifdef __cplusplus
}

// C++ API

#include <atomic>



class CommandLine;
class MemPrefs;
class PixelZoo;



struct mtPrndFree
{
	void operator() ( uint8_t * const prng )
	{
		free ( prng );
	}
};

using Prnd = std::unique_ptr< uint8_t, mtPrndFree >;



class MemPrefs
{
public:
	int	window_x		= 0;
	int	window_y		= 0;
	int	window_w		= 0;
	int	window_h		= 0;
	int	window_maximized	= 0;

private:
};



class CommandLine
{
public:
	int parse ( int argc, char const * const * argv );
		// 0 = Continue running (start UI)
		// 1 = Terminate program, returning exit.value()

	char const * get_input_filename () const{ return m_input_filename; }
	char const * get_output_dir () const	{ return m_output_dir; }
	char const * get_output_filename () const { return m_output_filename; }
	int get_thread_total () const		{ return m_thread_total; }
	int get_verbose () const		{ return m_verbose; }

	// Accessors

	mtKit::Exit & exit ()			{ return m_exit; }

	// uprefs must be destroyed before mprefs, so it appears below it:
	MemPrefs		mprefs;
	mtKit::UserPrefs	uprefs;

/// ----------------------------------------------------------------------------

private:
	void prefs_init ();

/// ----------------------------------------------------------------------------

	mtKit::Exit	m_exit;

	char	const	* m_input_filename	= nullptr;
	char	const	* m_output_dir		= nullptr;
	char	const	* m_output_filename	= nullptr;
	char	const	* m_prefs_filename	= nullptr;
	int		m_thread_total		= THREAD_TOTAL_DEFAULT;
	int		m_verbose		= 0;
};



class PixelZoo
{
public:
	PixelZoo ( CommandLine & cline, size_t const threads )
		:
		m_cline		( cline )
	{
		if ( m_cline.get_output_dir() )
		{
			m_output_file_prefix = m_cline.get_output_dir();
		}

		init_threads ( threads );
	}

	mtPixmap * get_pixmap() const { return m_flip_pixmap.get_active(); }
	int mode() const { return m_mode; }

	void set_fps ( int const fps )
	{
		m_batch_pixmap.set_fps ( fps );
		m_batch_prnd.set_fps ( fps );
	}

	void save_screenshot ( char const * filename ) const;

	// Only call one of these once on startup, throws on error
	void init_image ( int w, int h );
	void init_image ( char const * filename );

	void cycle_pixmap ();
	void plot_pixel ( int x, int y );

	int toggle_mode ()
	{
		switch ( m_mode )
		{
		case MODE_WORKING:
			m_mode = MODE_PAUSED;
			break;
		case MODE_PAUSED:
			m_mode = MODE_WORKING;
			break;
		}
		return m_mode;
	}

private:
	// Throws on error
	void init_prnd ();
	void init_threads ( size_t threads );

	void plot_points ();
	void prepare_next_pixmap ();

	void save_animation_frame ();

/// ----------------------------------------------------------------------------

	CommandLine	& m_cline;
	mtKit::Random	m_random;	// Only used by init_prnd & m_pool_prnd

	std::string	m_output_file_prefix;
	int		m_output_filenum = 0;

	int 		m_mode = MODE_WORKING;


template < typename Tcontainer >
class Flipper
{
public:
	Flipper() : m_status ( FLIP_STATUS_EMPTY ) {}

	using Tdata = typename Tcontainer::element_type;

	void add ( Tdata * d )
	{
		switch ( m_status )
		{
		case FLIP_STATUS_EMPTY:
			m_status = FLIP_STATUS_EMPTY_1;
			m_item[0].reset ( d );
			break;

		case FLIP_STATUS_EMPTY_1:
			m_status = FLIP_STATUS_0_AND_1;
			m_item[1].reset ( d );
			break;

		default:
			throw 123;
		}
	}

	int has_data () const
	{
		return FLIP_STATUS_EMPTY == m_status ? 0 : 1;
	}

	Tdata * get ( int i ) const { return m_item[i].get(); }

	Tdata * get_active () const
	{
		switch ( m_status )
		{
		case FLIP_STATUS_EMPTY_1:
		case FLIP_STATUS_0_NO_1:
		case FLIP_STATUS_0_AND_1:
			return m_item[0].get();

		case FLIP_STATUS_1_NO_0:
		case FLIP_STATUS_1_AND_0:
			return m_item[1].get();
		}

		return nullptr;
	}

	Tdata * get_inactive () const
	{
		switch ( m_status )
		{
		case FLIP_STATUS_0_NO_1:
		case FLIP_STATUS_0_AND_1:
			return m_item[1].get();

		case FLIP_STATUS_1_NO_0:
		case FLIP_STATUS_1_AND_0:
			return m_item[0].get();
		}

		return nullptr;
	}

	void inactive_is_ready ()
	{
		switch ( m_status )
		{
		case FLIP_STATUS_0_NO_1:
			m_status = FLIP_STATUS_0_AND_1;
			break;

		case FLIP_STATUS_1_NO_0:
			m_status = FLIP_STATUS_1_AND_0;
			break;
		}
	}

	int flip ()
	{
		switch ( m_status )
		{
		case FLIP_STATUS_0_AND_1:
			m_status = FLIP_STATUS_1_NO_0;
			return 0;

		case FLIP_STATUS_1_AND_0:
			m_status = FLIP_STATUS_0_NO_1;
			return 0;

		default:
			return 1;
		}
	}

	int can_flip () const
	{
		switch ( m_status )
		{
		case FLIP_STATUS_0_AND_1:
		case FLIP_STATUS_1_AND_0:
			return 1;

		default:
			return 0;
		}
	}

private:
	enum
	{
		FLIP_STATUS_EMPTY,	// No data in 0 or 1
		FLIP_STATUS_EMPTY_1,	// No data in 1
		FLIP_STATUS_0_NO_1,	// In 0 but not 1 (not ready to flip)
		FLIP_STATUS_0_AND_1,	// In 0 and 1 (ready to flip to 1)
		FLIP_STATUS_1_NO_0,	// In 1 but not 0 (not ready to flip)
		FLIP_STATUS_1_AND_0,	// In 1 and 0 (ready to flip to 0)
	};

	Tcontainer	m_item[2];
	int		m_status;
};

	// Pixmaps: one for displaying, one to be worked on
	Flipper<mtPixy::Pixmap>		m_flip_pixmap;
	mtKit::ThreadBatch< PixmapWork > m_batch_pixmap;
	int				m_pool_pixmap_toflip = 0;

	// Pseudo random number data: one for using, one to be worked on
	Flipper<Prnd>			m_flip_prnd;
	size_t				m_prnd_buflen = 0;
	int				m_prnd_mask = 0;
	mtKit::ThreadBatch< PrndWork >	m_batch_prnd;
	int				m_pool_prnd_toflip = 0;

	struct PixelPoint
	{
		int	x, y;
	};

	enum
	{
		PTS_MAX = 100
	};

	PixelPoint	m_pts[ PTS_MAX ];
	int		m_pts_tot = 0;
};



#endif		// C++ API



#endif		// PIZOR_BE_H_

