#pragma once
using namespace std;

#include "System.h"

// The first 4 bytes is the location of the header (the header itself is at the end of the file)
// Then 4 bytes for the size of the header
// Then 2 bytes for the version number at which this save was first generated
// Then 2 bytes for the version number that the save should now be at
// ( the rest of the header is actually a footer )
#define SAVE_FILE_HEADER_SIZE 12

enum ESaveVersions
{
	// Pre-release version
	SAVE_FILE_VERSION_PRE_LAUNCH = 1,

	// This is the version at which we launched the Xbox360 version
	SAVE_FILE_VERSION_LAUNCH = 2,

	// This is the version at which we had made changes that broke older saves
	SAVE_FILE_VERSION_POST_LAUNCH = 3,

	// This is the version at which we introduced the End, and any saves older than this will have their End data deleted
	SAVE_FILE_VERSION_NEW_END = 4,

	// This is the version at which we change the stronghold generation, and any saves older than this should should the original version
	SAVE_FILE_VERSION_MOVED_STRONGHOLD = 5,

	// This is the version at which we changed the playeruid format for PS3
	SAVE_FILE_VERSION_CHANGE_MAP_DATA_MAPPING_SIZE = 6,

	// This is the version at which we changed the playeruid format for Xbox One
	SAVE_FILE_VERSION_DURANGO_CHANGE_MAP_DATA_MAPPING_SIZE = 7,

	// This is the version at which we changed the chunk format to directly save the compressed storage formats
	SAVE_FILE_VERSION_COMPRESSED_CHUNK_STORAGE,

	SAVE_FILE_VERSION_NEXT,
};

// This is the version at which we changed the playeruid format for Xbox One
#define SAVE_FILE_VERSION_DURANGO_CHANGE_MAP_DATA_MAPPING_SIZE 7

enum ESavePlatform
{
	SAVE_FILE_PLATFORM_NONE		= MAKE_FOURCC('N', 'O', 'N', 'E') ,
	SAVE_FILE_PLATFORM_X360		= MAKE_FOURCC('X', '3', '6', '0') ,
	SAVE_FILE_PLATFORM_XBONE	= MAKE_FOURCC('X', 'B', '1', '_') ,
	SAVE_FILE_PLATFORM_PS3		= MAKE_FOURCC('P', 'S', '3', '_') ,
	SAVE_FILE_PLATFORM_PS4		= MAKE_FOURCC('P', 'S', '4', '_') ,
	SAVE_FILE_PLATFORM_PSVITA	= MAKE_FOURCC('P', 'S', 'V', '_') ,
	SAVE_FILE_PLATFORM_WIN64	= MAKE_FOURCC('W', 'I', 'N', '_') ,

#if defined __PS3__
	SAVE_FILE_PLATFORM_LOCAL = SAVE_FILE_PLATFORM_PS3
#endif
};
#define SAVE_FILE_VERSION_NUMBER (SAVE_FILE_VERSION_NEXT - 1)

struct FileEntrySaveDataV1
{
public:
	wchar_t filename[64];				// 64 * 2B
	unsigned int length; // In bytes	// 4B

	// This is only valid once the save file has been written/loaded at least once
	unsigned int startOffset;			// 4B
};

// It's important that we keep the order and size of the data here to smooth updating
// 4J Stu - As of writing the tutorial level uses a V1 save file
struct FileEntrySaveDataV2
{
public:
	wchar_t filename[64];				// 64 * 2B
	unsigned int length; // In bytes	// 4B

	union
	{
		// This is only valid once the save file has been written/loaded at least once
		unsigned int startOffset;			// 4B
		// For region files stored via ConsolveSaveFileSplit, these aren't stored within the normal save file, identified by not having a name (filename[0] is 0).
		// Note: These won't be read or written as part of a file header, and should only exist wrapped up in a FileEntry class
		unsigned int regionIndex;			// 4B

	};

	__int64 lastModifiedTime;			// 8B
};

typedef FileEntrySaveDataV2 FileEntrySaveData;

class FileEntry
{
public:
	FileEntrySaveData data;

	unsigned int currentFilePointer;

	FileEntry() { ZeroMemory(&data, sizeof(FileEntrySaveData)); }

	FileEntry( wchar_t name[64], unsigned int length, unsigned int startOffset )
	{
		data.length = length;
		data.startOffset = startOffset;
		memset( &data.filename, 0, sizeof( wchar_t ) * 64 );
		memcpy( &data.filename, name, sizeof( wchar_t ) * 64 );

		data.lastModifiedTime = 0;

		currentFilePointer = data.startOffset;
	}

	unsigned int getFileSize() { return data.length; }	
	bool isRegionFile() { return data.filename[0] == 0; }				// When using ConsoleSaveFileSplit only
	unsigned int getRegionFileIndex() { return data.regionIndex; }		// When using ConsoleSaveFileSplit only

	void updateLastModifiedTime() {	data.lastModifiedTime = System::currentRealTimeMillis(); }

	/*
	Comparison function object that returns true if the first argument goes before the second argument in the specific strict weak ordering it defines, and false otherwise.
	Used in a call to std::sort in DirectoryLevelStorage.cpp
	*/
	static bool newestFirst( FileEntry *a, FileEntry *b ) { return a->data.lastModifiedTime > b->data.lastModifiedTime; }
};

// A class the represents the header of the save file
class FileHeader
{
	friend class ConsoleSaveFileOriginal;
	friend class ConsoleSaveFileSplit;
private:
	vector<FileEntry *> fileTable;
	ESavePlatform	m_savePlatform;
	ByteOrder		m_saveEndian;
#if defined(__PS3__)
	static const ByteOrder m_localEndian = BIGENDIAN;
#else
	static const ByteOrder m_localEndian = LITTLEENDIAN;
#endif

	short m_saveVersion;
	short m_originalSaveVersion;

public:
	FileEntry *lastFile;

public:
	FileHeader();
	~FileHeader();

protected:
	FileEntry *AddFile( const wstring &name, unsigned int length = 0 );
	void RemoveFile( FileEntry * );
	void WriteHeader( LPVOID saveMem );
	void ReadHeader( LPVOID saveMem, ESavePlatform plat = SAVE_FILE_PLATFORM_LOCAL );

	unsigned int GetStartOfNextData();

	unsigned int GetFileSize();

	void AdjustStartOffsets(FileEntry *file, DWORD nNumberOfBytesToWrite, bool subtract = false);

	bool fileExists( const wstring &name );

	vector<FileEntry *> *getFilesWithPrefix(const wstring &prefix);

	vector<FileEntry *> *getValidPlayerDatFiles();

#if defined(__PS3__)
	wstring getPlayerDataFilenameForLoad(const PlayerUID& pUID);
	wstring getPlayerDataFilenameForSave(const PlayerUID& pUID);
	vector<FileEntry *> *getDatFilesWithOnlineID(const PlayerUID& pUID);
	vector<FileEntry *> *getDatFilesWithMacAndUserID(const PlayerUID& pUID);
	vector<FileEntry *> *getDatFilesWithPrimaryUser();
#endif
	
	void setSaveVersion(int version) { m_saveVersion = version; }
	int getSaveVersion() { return m_saveVersion; }
	void setOriginalSaveVersion(int version) { m_originalSaveVersion = version; }
	int getOriginalSaveVersion() { return m_originalSaveVersion; }
	ESavePlatform getSavePlatform() { return m_savePlatform; }
	void setPlatform(ESavePlatform plat) { m_savePlatform = plat; }
	bool isSaveEndianDifferent() { return m_saveEndian != m_localEndian; }
	void setLocalPlatform() { m_savePlatform = SAVE_FILE_PLATFORM_LOCAL; m_saveEndian = m_localEndian; }
	ByteOrder getSaveEndian() { return m_saveEndian; }
	static ByteOrder getLocalEndian() { return m_localEndian; }
	void setEndian(ByteOrder endian) { m_saveEndian = endian; }
	static ByteOrder getEndian(ESavePlatform plat);
	bool isLocalEndianDifferent(ESavePlatform plat){return m_localEndian != getEndian(plat); }
	
};
