/* GPC (C) 2017-2018 Stephane Charette <stephanecharette@gmail.com>
 * $Id: Misc.cpp 2493 2018-03-21 06:37:58Z stephane $
 */

#include "GPC.hpp"

#ifdef WIN32
#include <Windows.h>
#endif


std::string descriptive_username(void)
{
	return	SystemStats::getLogonName	().toStdString() + "@" +
			SystemStats::getComputerName().toStdString();
}


std::string get_url( const std::string &url )
{
	File				output		= File::createTempFile( "gpc.out" );
	const std::string	filename	= output.getFullPathName().toStdString();
	const std::string	user_agent	= "GPC/" GPC_VERSION " (www.ccoderun.ca stephanecharette@gmail.com)";
	const std::string	cookies		= "\"" + gpc().directory.getChildFile("cookies").getFullPathName().toStdString() + "\"";

	const std::string cmd =
		std::string("curl")					+ " "
		"--silent"							+ " "
		"--cookie-jar " + cookies			+ " "
		"--max-time 60"						+ " "
		"--connect-timeout 10"				+ " "
		"--location-trusted"				+ " "
		"--insecure"						+ " "
		"--user-agent \""	+ user_agent	+ "\" "
		"--output \""		+ filename		+ "\" "
		"--url \""			+ url			+ "\" ";

	LOG_MSG( "external command: " << cmd );

	#ifdef WIN32

	STARTUPINFO si;
	PROCESS_INFORMATION pi;
	memset(&si, '\0', sizeof(si));
	memset(&pi, '\0', sizeof(pi));

	SetLastError( 0 );
	BOOL success = CreateProcess(
		NULL,				// title
		LPSTR(cmd.c_str()),	// command
		NULL,				// process attributes
		NULL,				// thread attributes
		FALSE,				// inherit handles
		CREATE_NO_WINDOW,	// do not create a window for the process (hidden!)
		NULL,				// environment
		NULL,				// current directory
		&si,				// startup info
		&pi );				// process info

	DWORD rc = GetLastError();
	if (rc)
	{
		char *msg = nullptr;
		FormatMessage(
			FORMAT_MESSAGE_ALLOCATE_BUFFER	|
			FORMAT_MESSAGE_FROM_SYSTEM		|
			FORMAT_MESSAGE_IGNORE_INSERTS	,
			NULL							,
			rc								,
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
			(LPSTR)&msg,
			0,
			NULL);

		LOG_MSG( "curl: received error #" << rc << ": " << msg );
		LocalFree( msg );
	}

	if (success)
	{
		WaitForSingleObject(pi.hProcess, INFINITE);
		GetExitCodeProcess(pi.hProcess, &rc);
	}
	CloseHandle( pi.hProcess );
	CloseHandle( pi.hThread );

	#else
	const int rc = system( cmd.c_str() );
	#endif

	std::string txt;

	if (rc)
	{
		LOG_MSG( "rc=" << rc << " when running: " << cmd );
	}

	txt = Lox::readFile( filename );
	output.deleteFile();

	return txt;
}


std::string create_new_session(std::string uuid)
{
	auto & sessions = gpc().sessions;

	// loop until we find a unique UUID to use
	while (uuid.empty() || sessions.count(uuid) != 0)
	{
		uuid = Uuid().toDashedString().toStdString();
	}

	File dir = gpc().directory.getChildFile(uuid.c_str());
	dir.createDirectory();

	SessionRecord rec;
	rec.field[SessionRecord::EField::kUUID] = uuid;
	rec.initialize();

	sessions[uuid] = rec;

	return uuid;
}


Image &flip_horizontal(Image &img)
{
	const int h = img.getHeight();
	const int w = img.getWidth();

	for (int y = 0; y < h; y++)
	{
		for (int x = 0; x < 1 + w / 2; x++)
		{
			const int x1 = x;
			const int x2 = w - x - 1;

			if (x1 < x2)
			{
				Colour c1 = img.getPixelAt(x1, y);
				Colour c2 = img.getPixelAt(x2, y);
				img.setPixelAt(x1, y, c2);
				img.setPixelAt(x2, y, c1);
			}
		}
	}

	return img;
}


Image &rotate_ccw90(Image &img)
{
	// coordinate system, height, and width referenes the *original* image

	const int old_height	= img.getHeight();
	const int old_width		= img.getWidth();
	const int new_height	= old_width;
	const int new_width		= old_height;

	// create a new image with the fixed up width/height
	Image rotated_image(Image::PixelFormat::RGB, new_width, new_height, false);

	// iterate across (on a row) with the original image,
	// and set the pixels on a column with the new image
	for (int old_y = 0; old_y < old_height; old_y++)
	{
		int new_x = old_y;
		int new_y = new_height;

		for (int old_x = 0; old_x < old_width; old_x++)
		{
			new_y --;
			rotated_image.setPixelAt(new_x, new_y, img.getPixelAt(old_x, old_y));
		}
	}

	img = rotated_image;

	return img;
}


Image &rotate_cw90(Image &img)
{
	// coordinate system, height, and width referenes the *original* image

	const int old_height	= img.getHeight();
	const int old_width		= img.getWidth();
	const int new_height	= old_width;
	const int new_width		= old_height;

	// create a new image with the fixed up width/height
	Image rotated_image(Image::PixelFormat::RGB, new_width, new_height, false);

	// CCW90 does rows, until the entire image is done, while CW90 does columns
	// until we've gone through the entire width of the image
	for (int old_x = 0; old_x < old_width; old_x++)
	{
		int new_x = new_width;
		int new_y = old_x;

		for (int old_y = 0; old_y < old_height; old_y++)
		{
			new_x --;
			rotated_image.setPixelAt(new_x, new_y, img.getPixelAt(old_x, old_y));
		}
	}

	img = rotated_image;

	return img;
}


Image &rotate_180(Image &src)
{
	const int h = src.getHeight();
	const int w = src.getWidth();

	Image dst(Image::PixelFormat::ARGB, w, h, true);

	// I know this is horribly inefficient, but the images we're
	// modifying are extremely small, so I'm not too worried about
	// doing it 1 pixel at a time
	for (int src_y = 0; src_y < h; src_y++)
	{
		int dst_x = w;
		int dst_y = h - src_y - 1;

		for (int src_x = 0; src_x < w; src_x++)
		{
			dst_x --;
			const Colour c = src.getPixelAt(src_x, src_y);
			dst.setPixelAt(dst_x, dst_y, c);
		}
	}

	// overwrite the original source image with the new rotated image
	src = dst;

	return src;
}


/** Keep track of the lock files that we've already created so we don't
 * attempt to re-create them many times.
 */
static std::map<std::string, NamedPipe*> locks_already_obtained;


void lock_file(const File &file)
{
	// This function started out using file locks, but it doesn't work
	// easily cross-platform, so in March 2018 it was changed to use
	// named pipes instead.

	std::string name = file.getFullPathName().toStdString() + ".lock";
	while (true)
	{
		const size_t pos = name.find_first_not_of(
				"_"
				"0123456789"
				"abcdefghijklmnopqrstuvwxyz"
				"ABCDEFGHIJKLMNOPQRSTUVWXYZ");
		if (pos == std::string::npos)
		{
			break;
		}

		// replace non-alphanumeric characters with "_"
		name[pos] = '_';
	}

	if (locks_already_obtained.count(name))
	{
		// we already have a lock on this file, nothing left to do
		return;
	}

	LOG_MSG("attempting to create and lock " << name);
	NamedPipe * named_pipe = new NamedPipe;
	const bool success = named_pipe->createNewPipe(name, true);	// <--- this parm *MUST* be set to *TRUE* to force a new named pipe to be created

	if (success)
	{
		// remember the named pipe so it can be undone in unlock_all_files()
		LOG_MSG("successfully obtained lock for " << name);
		locks_already_obtained[name] = named_pipe;
	}
	else
	{
		LOG_MSG("failed to create lock named \"" << name << "\"");
		delete named_pipe;

		/// @throw Lox::Exception if the lock file cannot be created or locked
		throw Lox::Exception(LOX_WHERE, "Failed to create a lock for " + file.getFullPathName().toStdString() + ". Are there duplicate copies of GPC running?");
	}

	return;
}


void unlock_all_files(void)
{
	while (locks_already_obtained.empty() == false)
	{
		const std::string name	= locks_already_obtained.begin()->first;
		NamedPipe *named_pipe	= locks_already_obtained.begin()->second;

		LOG_MSG("destroying lock named " << name);
		named_pipe->close();
		delete named_pipe;
		locks_already_obtained.erase(name);
	}

	return;
}


void save_image_as_bmp(const Image &img, const File &file)
{
	const RGBApixel white	= { 255, 255, 255, 0 };
	const RGBApixel black	= { 0, 0, 0, 0 };
	const size_t	w		= img.getWidth();
	const size_t	h		= img.getHeight();

	BMP bmp;
	bmp.SetSize(w, h);
	bmp.SetBitDepth(1);
	bmp.SetDPI(0, 0);

	for (size_t y = 0; y < h; y++)
	{
		for (size_t x = 0; x < w; x++)
		{
			const Colour c = img.getPixelAt(x, y);
			const bool pixel_is_black = (
				c.getRed()		< 0xee ||
				c.getGreen()	< 0xee ||
				c.getBlue()		< 0xee );
			bmp.SetPixel(x, y, pixel_is_black ? black : white);
		}
	}

	const std::string output_filename = file.withFileExtension(".bmp").getFullPathName().toStdString();
	const bool result = bmp.WriteToFile(output_filename.c_str());
	if (result == false)
	{
		/// @throw Lox::Exception if the file cannot be written
		throw Lox::Exception(LOX_WHERE, "Failed to write image to " + output_filename + ".");
	}

	return;
}


Image load_image_from_bmp(const File &file)
{
	const std::string input_filename = file.getFullPathName().toStdString();

	BMP bmp;
	const bool result = bmp.ReadFromFile(input_filename.c_str());
	if (result == false)
	{
		/// @throw Lox::Exception if the file cannot be read
		throw Lox::Exception(LOX_WHERE, "Failed to read image from " + input_filename + ".");
	}

	const size_t w = bmp.TellWidth();
	const size_t h = bmp.TellHeight();

	Image img(Image::PixelFormat::RGB, w, h, false);
	for (size_t y = 0; y < h; y++)
	{
		for (size_t x = 0; x < w; x++)
		{
			const RGBApixel rgba = bmp.GetPixel(x, y);
			const Colour c(rgba.Red, rgba.Green, rgba.Blue); // ignore any alpha value coming from the .bmp, make the colour completely opaque
			LOG_MSG("x=" << x << " y=" << y << " r=" << (int)c.getRed() << " g=" << (int)c.getGreen() << " b=" << (int)c.getBlue() << " a=" << (int)c.getAlpha());
			img.setPixelAt(x, y, c);
		}
	}

	return img;
}
