/*********************************************************\
         __      __           __  __
        / /     / / _____    / / / / ______   __     __
       / /     / / /___  \  / / / / / ___  | / /    / /
      / /_____/ / _____| / / / / / / /__/ / / /    / /
     / ______  / / ___  / / / / / / _____/ / /    / /
    / /     / / / /__/ / / / / / / |____  / |____/ /
   /_/     /_/ |______/ /_/ /_/  \_____/  \_____  /
                                         ______/ /
            High Level Game Framework   /_______/

  ------------------------------------------------------

  Copyright (c) 2009 - Rodrigo Braz Monteiro.
  This file is subject to the terms of license.txt.

\*********************************************************/

#pragma once

#ifdef _MSC_VER
#pragma warning(disable: 4099)
#endif

#include <boost/archive/basic_binary_oarchive.hpp>
#include <boost/archive/basic_binary_iarchive.hpp>
#include <SDL.h>
#include "utils.h"

namespace Halley {

	class InputBinaryArchive : public boost::archive::basic_binary_iarchive<InputBinaryArchive>
	{
		friend class boost::archive::load_access;

	public:
		void load(char & t) { load_integer<char, Sint8>(t); }
		void load(unsigned char & t) { load_integer<unsigned char, Uint8>(t); }

		void load(short & t) { load_integer<short, Sint16>(t); }
		void load(unsigned short & t) { load_integer<unsigned short, Uint16>(t); }

		void load(int & t) { load_integer<int, Sint32>(t); }
		void load(unsigned int & t) { load_integer<unsigned int, Uint32>(t); }

		void load(long & t) { load_integer<long, Sint64>(t); }
		void load(unsigned long & t) { load_integer<unsigned long, Uint64>(t); }

		void load(long long & t) { load_integer<long long, Sint64>(t); }
		void load(unsigned long long & t) { load_integer<unsigned long long, Uint64>(t); }

		void load(float & t) { load_float(t); }
		void load(double & t) { load_float(t); }
		void load(long double & t) { load_float(t); }

		void load(bool & t) {
			char v;
			load(v);
			t = v != 0;
		}

		void load(std::string & t) {
			unsigned long size;
			load(size);
			char *tmp = new char[size+1];
			load_binary(tmp, size+1);
			t = tmp;
			delete[] tmp;
		}

		template <typename T>
		void load_override(T& t, int version) {
			boost::archive::basic_binary_iarchive<InputBinaryArchive>::load_override(t, version);
		}

		void load_override(boost::archive::class_name_type &t, int) {
			std::string tmp;
			load(tmp);
			t = boost::archive::class_name_type(tmp.c_str());
		}

		///////////////

		template <typename T, typename U>
		void load_integer(T & t)
		{
			U v;
			load_binary(&v, sizeof(U));
			t = (T)(FixEndian(v));
		}

		template <typename T>
		void load_float(T & t)
		{
			load_binary(&t, sizeof(T));
		}

		//////////////

		std::istream & sb;

	public:
		InputBinaryArchive(std::istream & _sb, int flags=0)
			: sb(_sb) , boost::archive::basic_binary_iarchive<InputBinaryArchive>(flags)
		{}

		void load_binary(void *address, std::size_t count)
		{
			sb.read((char*) address, count);
		}
	};
	
	class OutputBinaryArchive : public boost::archive::basic_binary_oarchive<OutputBinaryArchive>
	{
		friend class boost::archive::save_access;

		void save(char const & t) { save_integer<char, Sint8>(t); }
		void save(unsigned char const & t) { save_integer<unsigned char, Uint8>(t); }

		void save(short const & t) { save_integer<short, Sint16>(t); }
		void save(unsigned short const & t) { save_integer<unsigned short, Uint16>(t); }

		void save(int const & t) { save_integer<int, Sint32>(t); }
		void save(unsigned int const & t) { save_integer<unsigned int, Uint32>(t); }

		void save(long const & t) { save_integer<long, Sint64>(t); }
		void save(unsigned long const & t) { save_integer<unsigned long, Uint64>(t); }

		void save(long long const & t) { save_integer<long long, Sint64>(t); }
		void save(unsigned long long const & t) { save_integer<unsigned long long, Uint64>(t); }

		void save(float const & t) { save_float(t); }
		void save(double const & t) { save_float(t); }
		void save(long double const & t) { save_float(t); }

		void save(bool const & t) {
			char v = t ? 1 : 0;
			save(v);
		}

		void save(std::string const & t) {
			unsigned long size = t.size();
			save(size);
			save_binary(t.c_str(), size+1);
		}

		///////////////

		template <typename T, typename U>
		void save_integer(const T & t)
		{
			U v = FixEndian((U)t);
			save_binary(&v, sizeof(U));
		}

		template <typename T>
		void save_float(const T & t)
		{
			save_binary(&t, sizeof(T));
		}

		//////////////

		std::ostream & sb;

	public:
		OutputBinaryArchive(std::ostream & _sb, int flags=0)
			: sb(_sb) , boost::archive::basic_binary_oarchive<OutputBinaryArchive>(flags)
		{}

		void save_binary(const void *address, std::size_t count)
		{
			sb.write((const char*) address, count);
		}
	};

}
