#pragma once

#include <vector>

#include <jenga/include/common/Exception.h>

#include <BoostSerializationSupport.h>

#include <Binary.h>

class UserProc;

class Schedule
{
public:
	enum Type
	{
		None = 10000,
		GlobalVar,		// グローバル変数スケジュール
		DataTable,		// データテーブル スケジュール
		CatchAddress,	// Catchアドレス スケジュール
		Relocation,		// リロケーション情報スケジュール
		UserProc,		// ユーザ定義関数呼び出し側スケジュール
		AddressOf,		// ユーザ定義関数位置スケジュール
		DllProc,		// DLL関数位置スケジュール
		Vtbl,			// vtblスケジュール
		TypeInfo,		// TypeInfoスケジュール
	};

private:
	Type type;
	long offset;

	union{
		LONG_PTR lpValue;
		const ::UserProc *pUserProc;
		const ::DllProc *pDllProc;
		const ::CClass *pClass;
	};

	// XMLシリアライズ用
private:
	friend class boost::serialization::access;
	template<class Archive> void serialize(Archive& ar, const unsigned int version)
	{
		trace_for_serialize( "serializing - Schedule" );

		ar & BOOST_SERIALIZATION_NVP( type );
		ar & BOOST_SERIALIZATION_NVP( offset );

		switch( type )
		{
		case UserProc:
		case AddressOf:
		case CatchAddress:
			ar & boost::serialization::make_nvp("pUserProc", const_cast<::UserProc *&>(pUserProc));
			break;
		case DllProc:
			ar & boost::serialization::make_nvp("pDllProc", const_cast<::DllProc *&>(pDllProc));
			break;
		case Vtbl:
		case TypeInfo:
			ar & boost::serialization::make_nvp("pClass", const_cast<::CClass *&>(pClass));
			break;
		default:
			ar & BOOST_SERIALIZATION_NVP( lpValue );
			break;
		}
	}

public:
	Schedule()
	{
	}
	Schedule( Type type, long offset, LONG_PTR lpValue = 0 )
		: type( type )
		, offset( offset )
		, lpValue( lpValue )
	{
	}
	Schedule( const ::UserProc *pUserProc, long offset )
		: type( Schedule::UserProc )
		, offset( offset )
		, pUserProc( pUserProc )
	{
	}
	Schedule( const ::DllProc *pDllProc, long offset )
		: type( Schedule::DllProc )
		, offset( offset )
		, pDllProc( pDllProc )
	{
	}
	Schedule( const ::CClass *pClass, long offset )
		: type( Schedule::Vtbl )
		, offset( offset )
		, pClass( pClass )
	{
	}
	Schedule( Type type, const ::CClass *pClass, long offset )
		: type( type )
		, pClass( pClass )
		, offset( offset )
	{
	}
	~Schedule()
	{
	}

	Type GetType() const
	{
		return type;
	}
	long GetOffset() const
	{
		return offset;
	}
	LONG_PTR GetLongPtrValue() const
	{
		return lpValue;
	}
	const ::DllProc &GetDllProc() const
	{
		if( type != Schedule::DllProc )
		{
			SetError();
		}
		return *pDllProc;
	}
	const ::UserProc &GetUserProc() const
	{
		if( !( type == Schedule::UserProc || type == Schedule::AddressOf || type == Schedule::CatchAddress ) )
		{
			SetError();
		}
		return *pUserProc;
	}
	const ::CClass &GetClass() const
	{
		if( !( type == Schedule::Vtbl || type == Schedule::TypeInfo ) )
		{
			SetError();
		}
		return *pClass;
	}

	void SpecifyAddressOf()
	{
		if( type != Schedule::UserProc )
		{
			SetError();
		}
		type = Schedule::AddressOf;
	}
	void SpecifyCatchAddress()
	{
		if( type != Schedule::UserProc )
		{
			SetError();
		}
		type = Schedule::CatchAddress;
	}
};
typedef std::vector<Schedule> Schedules;

#define CODETYPE_SYSTEMPROC		0x0001
#define CODETYPE_DEBUGPROC		0x0002
class SourceLine
{
	int lineNum;
	long nativeCodePos;
	int sourceIndex;
	long sourceCodePos;
	DWORD codeType;

	// XMLシリアライズ用
private:
	friend class boost::serialization::access;
	template<class Archive> void serialize(Archive& ar, const unsigned int version)
	{
		trace_for_serialize( "serializing - SourceLine" );

		ar & BOOST_SERIALIZATION_NVP( lineNum );
		ar & BOOST_SERIALIZATION_NVP( nativeCodePos );
		ar & BOOST_SERIALIZATION_NVP( sourceIndex );
		ar & BOOST_SERIALIZATION_NVP( sourceCodePos );
		ar & BOOST_SERIALIZATION_NVP( codeType );
	}

public:
	SourceLine( int lineNum, int nativeCodePos, int sourceIndex, int sourceCodePos, DWORD codeType )
		: lineNum( lineNum )
		, nativeCodePos( nativeCodePos )
		, sourceIndex( sourceIndex )
		, sourceCodePos( sourceCodePos )
		, codeType( codeType )
	{
	}
	SourceLine()
	{
	}

	int GetLineNum() const
	{
		return lineNum;
	}
	long GetNativeCodePos() const
	{
		return nativeCodePos;
	}
	int GetSourceIndex() const
	{
		return sourceIndex;
	}
	void SetSourceIndex( int sourceIndex )
	{
		this->sourceIndex = sourceIndex;
	}
	long GetSourceCodePos() const
	{
		return sourceCodePos;
	}
	void SetSourceCodePos( int sourceCodePos )
	{
		this->sourceCodePos = sourceCodePos;
	}
	DWORD GetCodeType() const
	{
		return codeType;
	}
	bool IsInSystemProc() const
	{
		return ( (codeType&CODETYPE_SYSTEMPROC) != 0 );
	}
	bool IsInDebugProc() const
	{
		return ( (codeType&CODETYPE_DEBUGPROC) != 0 );
	}
};
typedef std::vector<SourceLine> SourceLines;

class NativeCode : public Binary
{
	// リンカで解決しなければならないスケジュール
	Schedules schedules;

	// ソースコード行番号とネイティブコード位置の対応情報
	SourceLines sourceLines;

	// XMLシリアライズ用
private:
	friend class boost::serialization::access;
	template<class Archive> void serialize(Archive& ar, const unsigned int version)
	{
		trace_for_serialize( "serializing(load) - NativeCode" );

		ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP( Binary );
		ar & BOOST_SERIALIZATION_NVP( schedules );
		ar & BOOST_SERIALIZATION_NVP( sourceLines );
	}

public:
	NativeCode()
		: Binary()
	{
	}
	NativeCode( const NativeCode &nativeCode )
		: Binary()
	{
		PutEx( nativeCode );
	}
	NativeCode( const char *codeBuffer, int size )
		: Binary( codeBuffer, size )
	{
	}
	~NativeCode()
	{
	}

	void operator =( const NativeCode &nativeCode )
	{
		Clear();
		PutEx( nativeCode );
	}

	const Schedules &GetSchedules() const
	{
		return schedules;
	}

	void PutEx( const NativeCode &nativeCode );
	void PutEx( long l, Schedule::Type scheduleType );
	void PutUserProcSchedule( const UserProc *pUserProc, bool isCall );
	void PutCatchAddressSchedule( const UserProc *pUserProc, long codePos );
	void PutDllProcSchedule( const DllProc *pDllProc );
	void PutVtblSchedule( const CClass *pClass );

	const SourceLines &GetSourceLines() const
	{
		return sourceLines;
	}
	void NextSourceLine();

	void ResetDataSectionBaseOffset( long dataSectionBaseOffset );
	void ResetSourceIndexes( long sourceIndexBase );
};
