//--------------------------------------------------------------------------------
// <copyright file="LqrSMACLoadSaveBoostXml.h" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
// 	Template functions that implement the serializable entity use case that the
//  SMC programs typically use:
//
//  1. The load action checks the length of a file, and if it exists and the
//     length is zero, it saves a default copy of the type to the file.  Following
//     this, it deserializes from the file.
//
//  2. Save simply serializes into the file.
// </summary>
//
// <owner>Mike Cookson</owner>
//--------------------------------------------------------------------------------

#ifndef __LQRSMACLOADSAVEBOOSTXML_H__
#define __LQRSMACLOADSAVEBOOSTXML_H__

#include "LqrSMACSharedLib.h"
#include <stdexcept>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <boost/algorithm/string.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/filesystem.hpp>
#include <fcntl.h>

namespace LiquidRobotics {
namespace SMACFramework {

// Issues "md5sum -c fileName.md5" to check the integrity of fileName; returns true if successful
SHARED_LIB_PUBLIC bool CheckBoostXmlMD5(const std::string& fileName, bool throwOnError = false);

// Issues "md5sum fileName > fileName.md5" to store integrity settings for fileName
SHARED_LIB_PUBLIC bool CreateBoostXmlMD5(const std::string& fileName, bool throwOnError = false);

// Reads the content of a file into a string
SHARED_LIB_PUBLIC std::string ReadEntireFileToString(const std::string& fileName, bool throwOnError = false);

// Reads the tail of a stream to a string (optionally rewinds first)
SHARED_LIB_PUBLIC std::string ReadStreamToString(std::istream& is, bool rewind, bool throwOnError = false);

template<typename TSerializable>
void LoadBoostXml(const std::string& fileName, TSerializable& ob, const std::string& upgradeFileName, bool upgradeFile = true)
{
	using namespace std;
	using namespace boost;
	using namespace boost::archive;
	using namespace boost::filesystem;
	using namespace boost::serialization;
	using namespace boost::system;

	error_code ec;
	boost::uintmax_t fileSize = file_size(fileName, ec);

	if (fileSize == ((boost::uintmax_t) -1))
	{
		stringstream msg;
		msg << "Load<" << typeid(ob).name() << "> could not read the size for '" << fileName << "'";
		throw invalid_argument(msg.str());
	}

	if (!fileSize)
	{
		TSerializable temp;
		temp.Save(fileName);
	}

	ifstream ifs(fileName.c_str());

	if (!ifs.good())
	{
		stringstream msg;
		msg << "Load<" << typeid(ob).name() << "> could not open '" << fileName << "' for read";
		throw invalid_argument(msg.str());
	}

	xml_iarchive ia(ifs);
	ia >> make_nvp("rootlevel", ob);

	if (fileSize && upgradeFile)
	{
		string currentData = ReadStreamToString(ifs, true);
		trim(currentData);

		stringstream upgradeStream;
		xml_oarchive oa(upgradeStream);

		oa << make_nvp("rootlevel", ob);

		const string upgradeString = upgradeStream.str();
		string trimmedUpgradeString = upgradeString;
		trim(trimmedUpgradeString);

		if (currentData.compare(trimmedUpgradeString))
		{
			bool doUpgrade = true;

			try
			{
				string existingUpgradeString = ReadEntireFileToString(upgradeFileName, true);
				trim(existingUpgradeString);

				const int compareResult = trimmedUpgradeString.compare(existingUpgradeString);

				doUpgrade = !!compareResult;
			}
			catch(...)
			{
				doUpgrade = true;
			}

			// I don't care if this upgrade actually succeeds: it's just for convenience
			if (doUpgrade)
				try
				{
					ofstream ofs(upgradeFileName.c_str(), ios::trunc | ios::out);
					ofs << upgradeString;
					ofs.close();
				}
				catch(...)
				{
				}
		}
	}
}

template<typename TSerializable>
void LoadBoostXml(const std::string& fileName, TSerializable& ob, bool upgradeFile = true)
{
	using namespace std;

	stringstream stm;
	stm << fileName;
	stm << ".upgraded";

	const string upgradeFileName = stm.str();

	LoadBoostXml(fileName, ob, upgradeFileName, upgradeFile);
}

template<typename TSerializable>
void SaveBoostXml(const std::string& fileName, const TSerializable& ob)
{
	using namespace std;
	using namespace boost;
	using namespace boost::archive;
	using namespace boost::serialization;

	ofstream ofs(fileName.c_str(), ios::trunc | ios::out);

	if (!ofs.good())
	{
		stringstream msg;
		msg << "Save<" << typeid(ob).name() << "> could not open '" << fileName << "' for write";
		throw invalid_argument(msg.str());
	}

	xml_oarchive oa(ofs);
	oa << make_nvp("rootlevel", ob);
}

template<typename TSerializable>
bool LoadBoostXmlMD5(const std::string& fileName, TSerializable& ob, bool throwOnError = true)
{
	const bool result = CheckBoostXmlMD5(fileName, throwOnError);

	if (result)
		LoadBoostXml(fileName, ob);

	return result;
}

template<typename TSerializable>
bool SaveBoostXmlMD5(const std::string& fileName, const TSerializable& ob, bool throwOnError = true)
{
	SaveBoostXml(fileName, ob);
	return CreateBoostXmlMD5(fileName, throwOnError);
}

}
}

#endif
