/*
	Copyright (C) 2018-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"



void mtKit::get_binary_dir ( std::string & path )
{
	path = "";

	char dest[PATH_MAX] = {0};

	if ( -1 == readlink ( "/proc/self/exe", dest, sizeof(dest) ) )
	{
		return;
	}

	char * ch = strrchr ( dest, MTKIT_DIR_SEP );
	if ( ch )
	{
		// Extract path, lose binary name
		ch[1] = 0;
		path += dest;
	}
	else
	{
		// No MTKIT_DIR_SEP separator so no path
	}
}

void mtKit::get_data_dir (
	std::string		& path,
	char	const * const	data
	)
{
	path = "";

	if ( data && data[0] == '.' )
	{
		// Relative path is being used so get binary path as a base
		get_binary_dir ( path );
	}

	path += data;
}

int mtKit::get_user_name ( std::string &name )
{
	name.clear ();

	try
	{
		struct passwd	* p;

		p = getpwuid ( getuid () );
		if ( p )
		{
			if ( p->pw_gecos && p->pw_gecos[0] )
			{
				name = p->pw_gecos;
			}
			else if ( p->pw_name && p->pw_name[0] )
			{
				name = p->pw_name;
			}
		}

		size_t const comma = name.find ( "," );

		if ( comma != std::string::npos )
		{
			name.resize ( comma );
		}

		if ( name.size () < 1 )
		{
			throw 123;
		}
	}
	catch ( ... )
	{
		return 1;
	}

	return 0;
}

std::string mtKit::realpath ( std::string const & path )
{
	std::string res;

	char * real = ::realpath ( path.c_str (), NULL );
	if ( real )
	{
		try
		{
			res = real;
		}
		catch ( ... )
		{
		}

		free ( real );
		real = NULL;
	}

	return res;
}

std::string mtKit::basename ( std::string const &path )
{
	size_t const found = path.find_last_of ( MTKIT_DIR_SEP );

	if ( found == std::string::npos )
	{
		return path;
	}

	return path.substr ( found + 1 );
}

int mtKit::string_strip_extension (
	std::string		&filename,
	char	const * const	extension
	)
{
	if ( ! extension )
	{
		char const * const st = filename.c_str ();
		char const * const pos = strrchr ( st, '.' );
		char const * const sep = strrchr ( st, MTKIT_DIR_SEP );

		if ( pos > sep && pos > st )
		{
			filename.resize ( (size_t)(pos - st) );
			return 1;
		}

		return 0;
	}

	std::string scan ("*.");
	scan += extension;

	int const len = (int)filename.length ();
	int const pos = mtkit_strmatch ( filename.c_str (), scan.c_str (), 0 );

	if ( pos > 0 )
	{
		if ( len > pos )
		{
			filename.resize ( (size_t)pos );
			return 1;
		}
	}

	return 0;
}

std::string mtKit::string_set_extension (
	char	const * const	filename,
	char	const * const	extension1,
	char	const * const	extension2
	)
{
	if ( ! filename )
	{
		return "";
	}

	if ( ! extension1 )
	{
		return filename;
	}

	char * const buf = mtkit_set_filename_extension ( filename, extension1,
		extension2, nullptr );

	if ( buf )
	{
		try
		{
			std::string txt ( buf );
			free ( buf );
			return txt;
		}
		catch (...)
		{
			free ( buf );
			throw;	// No memory to create a string so give up
		}
	}

	return filename;
}


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


mtKit::Random::Random ()
	:
	m_seed (0)
{
	set_seed_by_time ();
}

void mtKit::Random::set_seed_by_time ()
{
	struct timeval tv;

	gettimeofday ( &tv, NULL );

	m_seed = 1000000 * (uint64_t)tv.tv_sec + (uint64_t)tv.tv_usec;

	get_int ();
	get_int ();
}

int mtKit::Random::get_int ()
{
	m_seed = m_seed * ((uint64_t)6364136223846793005) +
		(uint64_t)1442695040888963407;

	return (int)(m_seed >> 32);
}

int mtKit::Random::get_int ( int const modulo )
{
	// Return an integer from 0 to (modulo - 1) ensuring that it is evenly
	// distributed.

	if ( modulo < 2 )
	{
		return 0;
	}

	int const floor = ((INT_MAX - modulo) + 1) % modulo;

	int res = 0;

	do
	{
		// Lose negative int's
		res = get_int () & INT_MAX;

	} while ( res < floor );

	return (res % modulo);
}

void mtKit::Random::get_data (
	uint8_t		* const	buf,
	size_t		const	buflen
	)
{
	uint8_t * const end1 = buf + buflen - (buflen % 4);
	uint8_t * const end2 = buf + buflen;
	uint8_t * dest = buf;

	for ( ; dest < end1; )
	{
		int const d = get_int ();

		*dest++ = (uint8_t)(d);
		*dest++ = (uint8_t)(d >> 8);
		*dest++ = (uint8_t)(d >> 16);
		*dest++ = (uint8_t)(d >> 24);
	}

	for ( ; dest < end2; dest++ )
	{
		*dest++ = (uint8_t)get_int ();
	}
}


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


mtKit::FileLock::FileLock ()
	:
	m_id		( -1 )
{
}

mtKit::FileLock::~FileLock ()
{
	unset ();
}

int mtKit::FileLock::set ( std::string const &filename )
{
	unset ();

	if ( mtkit_file_lock ( filename.c_str (), &m_id ) )
	{
		std::cerr << "Unable to lock file '" << filename << "'\n";
		return 1;
	}

	m_filename = filename;

	return 0;
}

void mtKit::FileLock::unset ()
{
	if ( -1 != m_id )
	{
		mtkit_file_unlock ( &m_id );
		remove ( m_filename.c_str () );
		m_filename = "";
	}
}


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


double mtKit::Clock::now ()
{
	struct timespec ts;

	if ( -1 == clock_gettime ( CLOCK_MONOTONIC_RAW, &ts ) )
	{
		return 0.0;
	}

	return (double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0;
}

void mtKit::ClockFrame::init ()
{
	m_frame.time_start = m_clock.now();

	m_phase.init();
	m_phase_stats.init();
}

int mtKit::ClockFrame::add_frame ()
{
	m_phase.frames++;

	double const time_end_of_work = m_clock.now ();
	double const elapsed_seconds = time_end_of_work - m_frame.time_start;
	double delay;

	if ( m_frame.fps_delay > elapsed_seconds )
	{
		delay = m_frame.fps_delay - elapsed_seconds;
		m_phase.idle_seconds += delay;

		mtkit_time_delay_seconds ( delay );
	}
	else
	{
		delay = 0.0;
	}

	m_phase.time_seconds += (elapsed_seconds + delay);
	m_frame.time_start = time_end_of_work + delay;

	if ( m_phase.time_seconds >= m_phase.duration_secs )
	{
		m_phase_stats.calculate ( m_frame, m_phase );

		m_phase.init();

		return 1;
	}

	return 0;
}

int mtKit::ClockFrame::set_fps_max ( int const fps )
{
	if ( fps >= FPS_MAX_MIN && fps <= FPS_MAX_MAX )
	{
		if ( fps == m_frame.fps_max )
		{
			return 0;
		}

		if ( fps > 0 )
		{
			m_frame.fps_delay = 1.0 / (double)fps;
		}
		else
		{
			m_frame.fps_delay = 0.0;
		}

		m_frame.fps_max = fps;

		init();

		return 0;
	}

	return 1;
}

int mtKit::ClockFrame::set_phase_seconds ( int const secs )
{
	if ( secs >= PHASE_MIN && secs <= PHASE_MAX )
	{
		m_phase.duration_secs = secs;
		return 0;
	}

	return 1;
}

void mtKit::ClockFrame::Phase::init ()
{
	frames = 0;
	time_seconds = 0.0;
	idle_seconds = 0.0;
}

void mtKit::ClockFrame::PhaseStats::init ()
{
	frames_skipped = 0;
	fps = 0.0;
	ips = 0.0;
}

void mtKit::ClockFrame::PhaseStats::calculate (
	Frame const & frame,
	Phase const & phase
	)
{
	fps = (double)phase.frames / phase.time_seconds;
	ips = phase.idle_seconds / phase.time_seconds;

	if ( frame.fps_max > 0 )
	{
		frames_skipped = frame.fps_max - phase.frames;
	}
	else
	{
		frames_skipped = 0;
	}
}



#include <iostream>
#include <fstream>



int mtkit_file_copy (
	char	const * const	filename_dest,
	char	const * const	filename_src
	)
{
	try
	{
		std::ifstream src ( filename_src, std::ios::binary );
		std::ofstream dest ( filename_dest, std::ios::binary );

		dest << src.rdbuf ();
	}
	catch ( ... )
	{
		return 1;
	}

	return 0;
}

