/* GPC (C) 2017-2018 Stephane Charette <stephanecharette@gmail.com>
 * $Id: SessionRecord.cpp 2514 2018-04-07 07:31:31Z stephane $
 */

#include "GPC.hpp"


bool SessionRecord::configuration_needs_to_be_saved = false;


std::string format_timestamp(const std::time_t tt)
{
	char buffer[100] = "";
	strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", localtime(&tt));

	return buffer;
}


SessionRecord::~SessionRecord(void)
{
	return;
}


SessionRecord::SessionRecord(void)
{
	const std::time_t tt		= time(nullptr);
	creationEpoch				= tt;
	recentlyUsedEpoch			= tt;
	printer_number				= 1;
	field[EField::kUsername]	= descriptive_username();

	initialize();

	return;
}


std::string str_from_json(Json &json, const std::string name, const std::string default_value="")
{
	if (json.count(name) == 1)
	{
		return json[name];
	}

	LOG_MSG("failed to read \"" << name << "\" from json; using default value \"" << default_value << "\"");

	return default_value;
}


uint64_t num_from_json(Json &json, const std::string name, const uint64_t default_value=0)
{
	if (json.count(name) == 1)
	{
		return json[name].get<uint64_t>();
	}

	LOG_MSG("failed to read \"" << name << "\" from json; using default value \"" << default_value << "\"");

	return default_value;
}


SessionRecord::SessionRecord(Json &json) :
	SessionRecord()
{
	/// @note The json fields used in this method must match exactly the ones in @ref save_all_sessions().

	LOG_MSG("loading session from json:" << std::endl << json.dump(1, '\t'));

	field[EField::kUUID				]	=	str_from_json(json, "uuid"				);
	field[EField::kIjsFilename		]	=	str_from_json(json, "ijs"				);
	field[EField::kNumberOfUses		]	= 	str_from_json(json, "number_of_uses"	);
	field[EField::kNumberOfImages	]	=	str_from_json(json, "number_of_images"	);
	field[EField::kUsername			]	=	str_from_json(json, "username"			);
	field[EField::kDescription		]	=	str_from_json(json, "description"		);
	creationEpoch						=	num_from_json(json, "epoch_created"		);
	recentlyUsedEpoch					=	num_from_json(json, "epoch_used"		);
	printer_number						=	num_from_json(json, "printer_number"	);

	if (json.count("image_descriptions") == 1)
	{
		Json & j = json["image_descriptions"];
		for (auto iter = j.begin(); iter != j.end(); iter++)
		{
			const std::string key = iter.key();
			image_descriptions[key] = str_from_json(j, key);
		}
	}

	initialize();

	return;
}


SessionRecord &SessionRecord::initialize(void)
{
	File dir = gpc().directory.getChildFile(uuid().c_str());

	field[SessionRecord::EField::kDirectory				] = dir.getFullPathName().toStdString();

	field[SessionRecord::EField::kCreationTimestamp		] = format_timestamp		(creationEpoch		);
	field[SessionRecord::EField::kRecentlyUsedTimestamp	] = format_timestamp		(recentlyUsedEpoch	);

	field[SessionRecord::EField::kCreationText			] = Lox::approximateTime	(creationEpoch		);
	field[SessionRecord::EField::kRecentlyUsedText		] = Lox::approximateTime	(recentlyUsedEpoch	);

	field[SessionRecord::EField::kPrinterName			] =	printer_number == 1 ? cfg().get_str("print_controller_1_name") :
															printer_number == 2 ? cfg().get_str("print_controller_2_name") : "?";

	if (field[EField::kNumberOfUses].empty() ||
		field[EField::kNumberOfUses].find_first_not_of("0123456789") != std::string::npos)
	{
		field[EField::kNumberOfUses] = "0";
	}

	if (field[EField::kNumberOfImages].empty() ||
		field[EField::kNumberOfImages].find_first_not_of("0123456789") != std::string::npos)
	{
		field[EField::kNumberOfImages] = "0";
	}

	return *this;
}


void SessionRecord::schedule_sessions_to_be_saved(void)
{
	configuration_needs_to_be_saved = true;

	LOG_MSG("starting a timer to save session records in 5 seconds");
	gpc().startTimer(1000 * 5);	// in milliseconds

	return;
}


SessionRecord &get_session_record(const std::string &uuid)
{
	MSessions & sessions = gpc().sessions;

	for (auto iter = sessions.begin(); iter != sessions.end(); iter++)
	{
		SessionRecord & session_record = iter->second;

		if (session_record.uuid() == uuid)
		{
			return session_record;
		}
	}

	/// @throw Lox::Exception if a session with the given uuid cannot be found
	throw Lox::Exception(LOX_WHERE, "Failed to find a session with the uuid " + uuid);
}


void test_save_image_as_png(const Image &img, const std::string &filename)
{
	MemoryOutputStream os;
	PNGImageFormat().writeImageToStream(img, os);

	File file1(filename.c_str());
	File file2 = file1.withFileExtension(".png");
	file2.replaceWithData(os.getData(), os.getDataSize());

	return;
}


void save_all_sessions(void)
{
	Json root;

	const auto &sessions = gpc().sessions;
	for (auto iter = sessions.begin(); iter != sessions.end(); iter++)
	{
		const std::string &uuid = iter->first;
		const SessionRecord &rec = iter->second;

		/// @note The json fields used in this function must match exactly the ones in @ref SessionRecord::SessionRecord(Json &json).

		Json j;
		j["uuid"			] = uuid;
		j["epoch_created"	] = rec.creationEpoch;
		j["epoch_used"		] = rec.recentlyUsedEpoch;
		j["printer_number"	] = rec.printer_number;
		j["ijs"				] = rec.field[SessionRecord::EField::kIjsFilename];
		j["number_of_uses"	] = rec.field[SessionRecord::EField::kNumberOfUses];
		j["number_of_images"] = rec.field[SessionRecord::EField::kNumberOfImages];
		j["username"		] = rec.field[SessionRecord::EField::kUsername];
		j["description"		] = rec.field[SessionRecord::EField::kDescription];

		for (const auto i : rec.image_descriptions)
		{
			const std::string & key = i.first;
			const std::string & val = i.second;

			if (not val.empty())
			{
				j["image_descriptions"][key] = val;
			}
		}

		root["sessions"][uuid] = j;
	}

	const std::string text = root.dump(1, '\t');
	LOG_MSG("created JSON from session records:" << std::endl << text);

	File dir = gpc().directory;
	dir.createDirectory();
	File json_file = dir.getChildFile("gpc.json");

	// make certain we are the only ones trying to edit this file,
	// otherwise we risk losing data or corruption if multiple processes
	// are messing around with config
	lock_file(json_file);

	const bool result = json_file.replaceWithText(text);
	if (result == false)
	{
		/// @throw Lox::Exception if the .json file cannot be saved to disk
		throw Lox::Exception(LOX_WHERE, "Failed to save file " + json_file.getFullPathName().toStdString());
	}
	else
	{
		LOG_MSG("session records have been saved to " << json_file.getFullPathName().toStdString());
	}

	SessionRecord::configuration_needs_to_be_saved = false;

	return;
}


void load_all_sessions(void)
{
	if (SessionRecord::configuration_needs_to_be_saved)
	{
		// ignore the request to reload all sessions since we're waiting to save the sessions
		return;
	}

	MSessions &m = gpc().sessions;
	m.clear();

	File dir = gpc().directory;
	File json_file = dir.getChildFile("gpc.json");

	try
	{
		// make certain we are the only ones trying to edit this file,
		// otherwise we risk losing data or corruption if multiple processes
		// are messing around with config
		lock_file(json_file);

		const std::string text = json_file.loadFileAsString().toStdString();
		Json root = Json::parse(text);
		for (auto j : root["sessions"])
		{
			LOG_MSG("loading session # " << (m.size() + 1) << " of " << root["sessions"].size() << ":");
			SessionRecord rec(j);
			m[rec.uuid()] = rec;
		}
	}
	CATCH_LOG_SHOW_AND_RETHROW

	return;
}
