/*
	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.
*/

#include "pizor_be.h"



void PixelZoo::save_screenshot ( char const * const filename ) const
{
	if ( ! filename )
	{
		std::cerr << "Error: no output filename supplied for "
			"screenshot\n";
		return;
	}

	mtPixmap const * const pixmap = get_pixmap ();
	if ( ! pixmap )
	{
		std::cerr << "Error: no pixmap available for screenshot\n";
		return;
	}

	if ( pixy_pixmap_save_png ( pixmap, filename, 5 ) )
	{
		std::cerr << "Error: unable to save pixmap to filename: "
			<< filename << "\n";
		return;
	}

	if ( m_cline.get_verbose() )
	{
		std::cout << "Screenshot saved to file:" << filename << "\n";
	}
}

void PixelZoo::init_image ( int const w, int const h )
{
	if ( w < PIXMAP_SIZE_MIN || h < PIXMAP_SIZE_MIN )
	{
		throw 123;
	}

	if ( m_flip_pixmap.has_data() )
	{
		// You can only call this function once
		std::cerr << "PixelZoo::create_image: already created image\n";
		throw 123;
	}

	m_flip_pixmap.add ( pixy_pixmap_new_indexed ( w, h ) );
	if ( ! m_flip_pixmap.has_data() )
	{
		std::cerr << "PixelZoo::create_image: unable to create image\n";
		throw 123;
	}

	pizor_pixmap_create_palette ( m_flip_pixmap.get(0) );

	init_prnd();
}

void PixelZoo::init_image ( char const * filename )
{
	if ( m_flip_pixmap.has_data() )
	{
		// You can only call this function once
		std::cerr << "PixelZoo::load_image: already created image\n";
		throw 123;
	}

	m_flip_pixmap.add ( pixy_pixmap_load ( filename, nullptr ) );
	mtPixmap const * const pixmap = m_flip_pixmap.get(0);

	if ( ! m_flip_pixmap.has_data() || ! pixy_pixmap_get_canvas ( pixmap ) )
	{
		std::cerr << "PixelZoo::load_image: unable to load image\n";
		throw 123;
	}

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

	if ( w < PIXMAP_SIZE_MIN || h < PIXMAP_SIZE_MIN )
	{
		throw 123;
	}

	if ( 1 != pixy_pixmap_get_bytes_per_pixel ( pixmap ) )
	{
		std::cerr << "PixelZoo::load_image: wrong image format\n";
		throw 123;
	}

	init_prnd();
}

void PixelZoo::init_prnd ()
{
	if ( m_flip_prnd.has_data() )
	{
		std::cerr << "PixelZoo::prepare_memory: memory already created\n";
		throw 123;
	}

	mtPixmap const * const pixmap = m_flip_pixmap.get(0);

	if ( ! pixmap || m_flip_pixmap.get(1) )
	{
		std::cerr << "PixelZoo::prepare_memory: 2nd pixmap already created\n";
		throw 123;
	}

	m_flip_pixmap.add ( pixy_pixmap_duplicate ( pixmap ) );
	if ( ! m_flip_pixmap.get(1) )
	{
		std::cerr << "PixelZoo::prepare_memory: 2nd pixmap creation failed\n";
		throw 123;
	}

	int const w = pixy_pixmap_get_width ( pixmap );
	int const h = pixy_pixmap_get_height ( pixmap );
	m_prnd_buflen = (size_t)(w*h);

	m_flip_prnd.add ( (uint8_t *)malloc ( m_prnd_buflen ) );
	m_flip_prnd.add ( (uint8_t *)malloc ( m_prnd_buflen ) );

	if ( ! m_flip_prnd.get(0) || ! m_flip_prnd.get(1) )
	{
		std::cerr << "PixelZoo::prepare_memory: malloc creation failed\n";
		throw 123;
	}

	m_random.set_seed_by_time ();
	m_random.get_data ( m_flip_prnd.get(0), m_prnd_buflen );

#ifdef DEBUG
	mtKit::Clock clock;
#endif

	m_random.get_data ( m_flip_prnd.get(1), m_prnd_buflen );

#ifdef DEBUG
	printf ( "time taken to fill %i bytes with PRND is %f secs\n",
		(int)m_prnd_buflen, clock.seconds() );
#endif

	m_prnd_mask = 1;

	save_animation_frame();		// Save the initial frame
}

void PixelZoo::init_threads ( size_t const threads )
{
	m_batch_pixmap.init_threads ( threads, [this]( PixmapWork & work )
	{
		do_pixmapwork ( &work );
	});

	m_batch_prnd.init_threads ( 1, [this]( PrndWork & work )
	{
		m_random.get_data ( work.prnd, work.buflen );
	});
}

void PixelZoo::plot_pixel ( int const x, int const y )
{
	if ( m_pts_tot >= PTS_MAX )
	{
		return;
	}

	m_pts[ m_pts_tot ].x = x;
	m_pts[ m_pts_tot ].y = y;

	m_pts_tot++;
}

void PixelZoo::cycle_pixmap ()
{
	if ( MODE_WORKING == m_mode )
	{
		prepare_next_pixmap ();
	}
}

void PixelZoo::plot_points ()
{
	if ( m_pts_tot < 1 )
	{
		return;
	}

	mtPixmap * const pixmap = m_flip_pixmap.get_inactive();
	int const w = pixy_pixmap_get_width ( pixmap );
	int const h = pixy_pixmap_get_height ( pixmap );
	unsigned char * const dest = pixy_pixmap_get_canvas ( pixmap );

	for ( int i=0; i < m_pts_tot; i++ )
	{
		if (	m_pts[i].x < 0 || m_pts[i].y < 0 ||
			m_pts[i].x >= w || m_pts[i].y >= h
			)
		{
			continue;
		}

		dest[ m_pts[i].x + w * m_pts[i].y ] = 15;
	}

	m_pts_tot = 0;
}

void PixelZoo::prepare_next_pixmap ()
{
	if ( m_batch_pixmap.todo() > 0 )
	{
		std::cerr << "The pixmap pool is still working, so we can't do "
			"anything yet\n";
		return;
	}

	if ( m_pool_pixmap_toflip )
	{
		plot_points ();

		// Work has finished since we were last here, so enable flip
		m_flip_pixmap.inactive_is_ready ();

		// Don't enable another flip until the queue has been set up
		m_pool_pixmap_toflip = 0;

		// Switch images if ready
		if ( m_flip_pixmap.can_flip() )
		{
			m_flip_pixmap.flip();

			save_animation_frame ();
		}
	}

	if ( m_prnd_mask > 64 && m_batch_prnd.todo() > 0 )
	{
		std::cerr << "The PRND pool is still working, so we can't do "
			"anything yet\n";
		return;
	}

	if ( m_pool_prnd_toflip )
	{
		// Work has finished since we were last here, so enable flip
		m_flip_prnd.inactive_is_ready ();

		// Don't enable another flip until the queue has been set up
		m_pool_prnd_toflip = 0;
	}

	mtPixmap	const * const	pold = m_flip_pixmap.get_active();
	unsigned char	const * const	src = pixy_pixmap_get_canvas ( pold );

	mtPixmap		* const	pnew = m_flip_pixmap.get_inactive();
	unsigned char		* const	dest = pixy_pixmap_get_canvas ( pnew );

	uint8_t * prnd = m_flip_prnd.get_active();
	int const w = pixy_pixmap_get_width ( pold );
	int const h = pixy_pixmap_get_height ( pold );
	int const thread_tot = (int)m_batch_pixmap.thread_total();
	PixmapWork pmwork = { dest, src, w, h, 0, 0, prnd, m_prnd_mask };

	for ( int i=0; i<thread_tot; i++ )
	{
		pmwork.ystart = pmwork.ylim;
		pmwork.ylim = (i+1)*h / thread_tot;

		if ( m_batch_pixmap.send_work ( pmwork ) )
		{
			std::cerr << "Failed to push work to threads\n";
			do_pixmapwork ( &pmwork );
		}

		// The queue is full, so enable a flip when finished
		m_pool_pixmap_toflip = 1;
	}

//m_batch_pixmap.wait_for_empty_queue();

	m_prnd_mask *= 2;

	if ( m_prnd_mask > 128 )
	{
		m_flip_prnd.flip();

		PrndWork wk = { prnd, m_prnd_buflen };
		if ( m_batch_prnd.send_work ( wk ) )
		{
			std::cerr << "Failed to push data onto m_pool_prnd\n";
			m_random.get_data ( prnd, m_prnd_buflen );
		}

		m_prnd_mask = 1;

		// The queue is full, so enable a flip when finished
		m_pool_prnd_toflip = 1;
	}

//m_batch_prnd.wait_for_empty_queue();
}

void PixelZoo::save_animation_frame ()
{
	if ( m_output_file_prefix.size() < 1 )
	{
		return;
	}

	std::string	filename ( m_output_file_prefix );
	char		num[16];

	snprintf ( num, sizeof(num), "%06i", m_output_filenum );
	filename += num;
	filename += OUTPUT_DIR_FILENAME_EXTENSION;

	save_screenshot ( filename.c_str() );

	m_output_filenum++;
}

