/*
 * OpenClonk, http://www.openclonk.org
 *
 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
 * Copyright (c) 2013-2016, The OpenClonk Team and contributors
 *
 * Distributed under the terms of the ISC license; see accompanying file
 * "COPYING" for details.
 *
 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
 * See accompanying file "TRADEMARK" for details.
 *
 * To redistribute this file separately, substitute the full license texts
 * for the above references.
 */
// network resource: data needed for the game (scenario, plr files, definitions...)

#ifndef INC_C4Network2Res
#define INC_C4Network2Res

#include "lib/StdAdaptors.h"
#include "platform/StdSync.h"

#include "lib/SHA1.h"

#include <atomic>

const uint32_t C4NetResChunkSize = 10U * 1024U;

const int32_t C4NetResDiscoverTimeout = 10, // (s)
              C4NetResDiscoverInterval = 1, // (s)
              C4NetResStatusInterval = 1, // (s)
              C4NetResMaxLoad = 5,
              C4NetResLoadTimeout = 60, // (s)
              C4NetResDeleteTime = 60, // (s)
              C4NetResMaxBigicon = 20; // maximum size, in KB, of bigicon

const int32_t C4NetResIDAnonymous = -2;

enum C4Network2ResType
{
	NRT_Null=0,
	NRT_Scenario,
	NRT_Dynamic,
	NRT_Player,
	NRT_Definitions,
	NRT_System,
	NRT_Material
};

const StdEnumEntry<C4Network2ResType> C4Network2ResType_EnumMap[] =
{
	{ "Scenario", NRT_Scenario },
	{ "Dynamic", NRT_Dynamic },
	{ "Player", NRT_Player },
	{ "Definitions", NRT_Definitions },
	{ "System", NRT_System },
	{ "Material", NRT_Material },
};

// damn circular dependencies
#include "network/C4PacketBase.h"
#include "network/C4Network2IO.h"
class C4Network2ResList;
class C4Network2ResChunk;

// classes
class C4Network2ResCore : public C4PacketBase
{
public:
	C4Network2ResCore();

protected:
	C4Network2ResType eType{NRT_Null};
	int32_t iID{-1}, iDerID{-1};
	StdCopyStrBuf FileName;
	bool fLoadable{false};
	uint32_t iFileSize, iFileCRC, iContentsCRC;
	uint8_t fHasFileSHA{false};
	uint8_t FileSHA[SHA_DIGEST_LENGTH];
	uint32_t iChunkSize;

public:
	C4Network2ResType getType()   const { return eType; }
	bool          isNull()        const { return eType == NRT_Null; }
	int32_t       getID()         const { return iID; }
	int32_t       getDerID()      const { return iDerID; }
	bool          isLoadable()    const { return fLoadable; }
	uint32_t      getFileSize()   const { return iFileSize; }
	uint32_t      getFileCRC()    const { return iFileCRC; }
	uint32_t      getContentsCRC()const { return iContentsCRC; }
	bool          hasFileSHA()    const { return !!fHasFileSHA; }
	const uint8_t*getFileSHA()    const { return FileSHA; }
	const char *  getFileName()   const { return FileName.getData(); }
	uint32_t      getChunkSize()  const { return iChunkSize; }
	uint32_t      getChunkCnt()   const { return iFileSize && iChunkSize ? (iFileSize - 1) / iChunkSize + 1 : 0; }

	void Set(C4Network2ResType eType, int32_t iResID, const char *strFileName, uint32_t iContentsCRC);
	void SetID(int32_t inID)            { iID = inID; }
	void SetDerived(int32_t inDerID)    { iDerID = inDerID; }
	void SetLoadable(uint32_t iSize, uint32_t iCRC);
	void SetFileSHA(BYTE *pSHA)         { memcpy(FileSHA, pSHA, SHA_DIGEST_LENGTH); fHasFileSHA = true; }
	void Clear();

	void CompileFunc(StdCompiler *pComp) override;

};

class C4Network2ResLoad
{
	friend class C4Network2Res;
public:
	C4Network2ResLoad(int32_t iChunk, int32_t iByClient);
	~C4Network2ResLoad();

protected:
	// chunk download data
	int32_t iChunk;
	time_t Timestamp;
	int32_t iByClient;

	// list (C4Network2Res)
	C4Network2ResLoad *pNext;

public:
	int32_t     getChunk()        const { return iChunk; }
	int32_t     getByClient()     const { return iByClient; }

	C4Network2ResLoad *Next() const { return pNext; }

	bool CheckTimeout();

};

class C4Network2ResChunkData : public C4PacketBase
{
public:
	C4Network2ResChunkData();
	C4Network2ResChunkData(const C4Network2ResChunkData &Data2);
	~C4Network2ResChunkData() override;

	C4Network2ResChunkData &operator =(const C4Network2ResChunkData &Data2);
protected:
	int32_t iChunkCnt{0}, iPresentChunkCnt{0};

	// present chunk ranges
	struct ChunkRange { int32_t Start, Length; ChunkRange *Next; };
	ChunkRange *pChunkRanges{nullptr};
	int32_t iChunkRangeCnt{0};

public:
	int32_t getChunkCnt()         const { return iChunkCnt; }
	int32_t getPresentChunkCnt()  const { return iPresentChunkCnt; }
	int32_t getPresentPercent()   const { return iPresentChunkCnt * 100 / iChunkCnt; }
	bool isComplete()         const { return iPresentChunkCnt == iChunkCnt; }

	void SetIncomplete(int32_t iChunkCnt);
	void SetComplete(int32_t iChunkCnt);

	void AddChunk(int32_t iChunk);
	void AddChunkRange(int32_t iStart, int32_t iLength);
	void Merge(const C4Network2ResChunkData &Data2);

	void Clear();

	int32_t GetChunkToRetrieve(const C4Network2ResChunkData &Available, int32_t iLoadingCnt, int32_t *pLoading) const;

protected:
	// helpers
	bool MergeRanges(ChunkRange *pRange);
	void GetNegative(C4Network2ResChunkData &Target) const;
	int32_t getPresentChunk(int32_t iNr) const;

public:
	void CompileFunc(StdCompiler *pComp) override;
};

class C4Network2Res
{
	friend class C4Network2ResList;
	friend class C4Network2ResChunk;
public:

	// helper for reference-holding
	class Ref
	{
	public:
		Ref() = default;
		Ref(C4Network2Res *pRes) : pRes(pRes) { if (pRes) pRes->AddRef(); }
		Ref(const Ref &rCopy) : pRes(rCopy.pRes) { if (pRes) pRes->AddRef(); }
		~Ref() { Clear(); }
		Ref &operator = (C4Network2Res *pnRes) { Set(pnRes); return *this; }
		Ref &operator = (const Ref &rCopy) { Set(rCopy.pRes); return *this; }
	private:
		C4Network2Res *pRes{nullptr};
	public:
		operator C4Network2Res *() const { return pRes; }
		bool operator ! () const { return !pRes; }
		C4Network2Res * operator ->() const { return pRes; }
		void Clear() { if (pRes) pRes->DelRef(); pRes = nullptr; }
		void Set(C4Network2Res *pnRes) { if (pRes == pnRes) return; Clear(); pRes = pnRes; if (pRes) pRes->AddRef(); }
	};

	C4Network2Res(C4Network2ResList *pnParent);
	~C4Network2Res();

protected:
	// core, chunk data
	C4Network2ResCore Core;
	C4Network2ResChunkData Chunks; // (only valid while loading)
	bool fDirty;

	// local file data
	CStdCSec FileCSec;
	char szFile[_MAX_PATH + 1], szStandalone[_MAX_PATH + 1];
	bool fTempFile, fStandaloneFailed;

	// references
	std::atomic_long iRefCnt;
	bool fRemoved;

	// being load?
	int32_t iLastReqTime;

	// loading
	bool fLoading;
	struct ClientChunks { C4Network2ResChunkData Chunks; int32_t ClientID; ClientChunks *Next; }
	*pCChunks;
	time_t iDiscoverStartTime;
	C4Network2ResLoad *pLoads;
	int32_t iLoadCnt;

	// list (C4Network2ResList)
	C4Network2Res *pNext;
	C4Network2ResList *pParent;

public:
	C4Network2ResType getType() const { return Core.getType(); }
	const C4Network2ResCore &getCore() const { return Core; }
	bool        isDirty()       const { return fDirty; }
	bool        isAnonymous()   const { return getResID() == C4NetResIDAnonymous; }
	int32_t     getResID()      const { return Core.getID(); }
	int32_t     getResClient()  const { return Core.getID() >> 16; }
	const char *getFile()       const { return szFile; }
	CStdCSec   *getFileCSec()         { return &FileCSec; }
	int32_t     getLastReqTime()const { return iLastReqTime; }
	bool        isRemoved()     const { return fRemoved; }
	bool        isLoading()     const { return fLoading; }
	bool        isComplete()    const { return !fLoading; }
	int32_t     getPresentPercent() const { return fLoading ? Chunks.getPresentPercent() : 100; }
	bool        isTempFile()    const { return fTempFile; }

	bool SetByFile(const char *strFilePath, bool fTemp, C4Network2ResType eType, int32_t iResID, const char *szResName = nullptr, bool fSilent = false);
	bool SetByGroup(C4Group *pGrp, bool fTemp, C4Network2ResType eType, int32_t iResID, const char *szResName = nullptr, bool fSilent = false);
	bool SetByCore(const C4Network2ResCore &nCore, bool fSilent = false, const char *szAsFilename = nullptr, int32_t iRecursion=0);
	bool SetLoad(const C4Network2ResCore &nCore);

	bool SetDerived(const char *strName, const char *strFilePath, bool fTemp, C4Network2ResType eType, int32_t iDResID);

	void ChangeID(int32_t inID);

	bool IsBinaryCompatible();
	bool GetStandalone(char *pTo, int32_t iMaxL, bool fSetOfficial, bool fAllowUnloadable = false, bool fSilent = false);
	bool CalculateSHA();

	bool SaveBackFile();
	C4Network2Res::Ref Derive();
	bool FinishDerive();
	bool FinishDerive(const C4Network2ResCore &nCore);

	bool SendStatus(C4Network2IOConnection *pTo = nullptr);
	bool SendChunk(uint32_t iChunk, int32_t iToClient);

	// references
	void AddRef(); void DelRef();

	// events
	void OnDiscover(C4Network2IOConnection *pBy);
	void OnStatus(const C4Network2ResChunkData &rChunkData, C4Network2IOConnection *pBy);
	void OnChunk(const C4Network2ResChunk &rChunk);
	bool DoLoad();

	bool NeedsDiscover();

	C4Group *OpenAsGrp() const;

	void Remove();
	void Clear();

protected:
	int32_t OpenFileRead(); int32_t OpenFileWrite();

	void StartNewLoads();
	bool StartLoad(int32_t iFromClient, const C4Network2ResChunkData &Chunks);
	void EndLoad();
	void ClearLoad();

	void RemoveLoad(C4Network2ResLoad *pLoad);
	void RemoveCChunks(ClientChunks *pChunks);

	bool OptimizeStandalone(bool fSilent);

};

class C4Network2ResChunk : public C4PacketBase
{
public:
	C4Network2ResChunk();
	~C4Network2ResChunk() override;

protected:
	int32_t iResID;
	uint32_t iChunk;
	StdBuf Data;

public:
	int32_t   getResID()   const { return iResID; }
	uint32_t  getChunkNr() const { return iChunk; }

	bool Set(C4Network2Res *pRes, uint32_t iChunk);
	bool AddTo(C4Network2Res *pRes, C4Network2IO *pIO) const;

	void CompileFunc(StdCompiler *pComp) override;
};

class C4Network2ResList : protected CStdCSecExCallback // run by network thread
{
	friend class C4Network2Res;
	friend class C4Network2;
public:
	C4Network2ResList();
	~C4Network2ResList() override;

protected:

	C4Network2Res *pFirst{nullptr};
	CStdCSecEx ResListCSec;
	CStdCSec ResListAddCSec;

	int32_t iClientID{-1}, iNextResID;
	CStdCSec ResIDCSec;

	// timings
	int32_t iLastDiscover{0}, iLastStatus{0};

	// object used for network i/o
	C4Network2IO *pIO{nullptr};

public:

	// initialization
	bool Init(int32_t iClientID, C4Network2IO *pIOClass); // by main thread
	void SetLocalID(int32_t iClientID); // by both

protected:
	int32_t nextResID(); // by main thread

	C4Network2Res *getRes(int32_t iResID); // by both
	C4Network2Res *getRes(const char *szFile, bool fLocalOnly); // by both

public:
	// returns referenced resource ptrs
	C4Network2Res::Ref getRefRes(int32_t iResID); // by both
	C4Network2Res::Ref getRefRes(const char *szFile, bool fLocalOnly = false); // by both
	C4Network2Res::Ref getRefNextRes(int32_t iResID); // by both

	void Add(C4Network2Res *pRes); // by both
	C4Network2Res::Ref AddByFile(const char *strFilePath, bool fTemp, C4Network2ResType eType, int32_t iResID = -1, const char *szResName = nullptr, bool fAllowUnloadable = false); // by both
	C4Network2Res::Ref AddByGroup(C4Group *pGrp, bool fTemp, C4Network2ResType eType, int32_t iResID = -1, const char *szResName = nullptr, bool fAllowUnloadable = false); // by both
	C4Network2Res::Ref AddByCore(const C4Network2ResCore &Core, bool fLoad = true); // by main thread
	C4Network2Res::Ref AddLoad(const C4Network2ResCore &Core); // by main thread

	void RemoveAtClient(int32_t iClientID); // by main thread
	void Clear(); // by main thread

	bool SendDiscover(C4Network2IOConnection *pTo = nullptr); // by both
	void OnClientConnect(C4Network2IOConnection *pConn); // by main thread

	// interface for C4Network2IO
	void HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn);
	void OnTimer();

	// CStdCSecExCallback
	void OnShareFree(CStdCSecEx *pCSec) override;

	// for C4Network2Res
	C4Network2IO *getIOClass() { return pIO; }

protected:
	void OnResComplete(C4Network2Res *pRes);

	// misc
	bool CreateNetworkFolder();
	bool FindTempResFileName(const char *szFilename, char *pTarget);

};

// * Packets *

class C4PacketResStatus : public C4PacketBase
{
public:
	C4PacketResStatus();
	C4PacketResStatus(int32_t iResID, const C4Network2ResChunkData &nChunks);

protected:
	int32_t iResID;
	C4Network2ResChunkData Chunks;

public:
	int32_t getResID() const { return iResID; }
	const C4Network2ResChunkData &getChunks() const { return Chunks; }

	void CompileFunc(StdCompiler *pComp) override;
};

class C4PacketResDiscover : public C4PacketBase
{
public:
	C4PacketResDiscover();

protected:
	int32_t iDisIDs[16], iDisIDCnt{0};

public:
	int32_t getDisIDCnt()       const { return iDisIDCnt; }
	int32_t getDisID(int32_t i) const { return iDisIDs[i]; }
	bool isIDPresent(int32_t iID) const;

	bool AddDisID(int32_t iID);

	void CompileFunc(StdCompiler *pComp) override;
};

class C4PacketResRequest : public C4PacketBase
{
public:
	C4PacketResRequest(int32_t iID = -1, int32_t iChunk = -1);

protected:
	int32_t iReqID, iReqChunk;

public:
	int32_t getReqID()    const { return iReqID; }
	int32_t getReqChunk() const { return iReqChunk; }

	void CompileFunc(StdCompiler *pComp) override;
};

#endif // INC_C4Network2Res
