#pragma once

#include "Common/CsdkStringConvert.h"
#include "Common/ServerOpCode.h"
#include "Tools/CsdkCommonTools.h"


class FTransData
{
	struct FTransHead
	{
		int32 PackLen = 0;
		int16 HeaderSize = 26;
		int16 Version = 0;
		ServerEOpCode::Type Operation =ServerEOpCode::None;
		int32 Seq = 0;
		FString  ProductId;

		TArray<uint8> Serialize() const
		{
			TArray<uint8> Buff;
			Buff.Reserve(HeaderSize);
			Buff.Append(GetBinaryArray(PackLen));
			Buff.Append(GetBinaryArray(HeaderSize));
			Buff.Append(GetBinaryArray(Version));
			Buff.Append(GetBinaryArray(Operation));
			Buff.Append(GetBinaryArray(Seq));
			Buff.Append(GetBinaryArray(ProductId));
			Buff.AddZeroed(HeaderSize-Buff.Num());
			return Buff;
		}
		static FTransHead Deserialize(const uint8 * const StartPos)
		{
			FTransHead Head;
			Head.PackLen = CompatGetNum<int32>(StartPos);
			Head.HeaderSize = CompatGetNum<int16>(StartPos+4);
			Head.Version = CompatGetNum<int16>(StartPos+6);
			Head.Operation = CompatGetNum<ServerEOpCode::Type>(StartPos+8) ;
			Head.Seq = CompatGetNum<int32>(StartPos+12);
			Head.ProductId =  GetString(StartPos+16,10);
			return Head;
		}
	private:
		template<typename  T>
		static TArray<uint8> GetBinaryArray(T Num)
		{
			TArray<uint8> Buff;
			Buff.Reserve(sizeof(T));
			if(	FGenericPlatformProperties::IsLittleEndian())
			{
				for(int i =0 ;i<sizeof(T); ++i)
				{
					int32 Pos =	(sizeof(T)-1-i)*8;
					Buff.Add(Num>>Pos);
				}
			}
			else
			{
				for(int i =0 ;i<sizeof(T); ++i)
				{
					Buff.Add(Num>>i*8);
				}
			}
			return  Buff;
		}

		static TArray<uint8> GetBinaryArray(const FString& Str)
		{
			const std::string StdStr = FSTRING_TO_STD_STRING(Str);
			TArray<uint8> Buff;
			Buff.Reserve(StdStr.length());

			for(auto It = StdStr.cbegin();It!=StdStr.cend();++It)
			{
				Buff.Add(static_cast<uint8>(*It));
			}
			return  Buff;
		}

		template<typename T>
		static T CompatGetNum(const uint8* const StartPos)
		{
			T Value{};
			TArray<uint8> Temp{ StartPos,sizeof(T)};
			if(	FGenericPlatformProperties::IsLittleEndian())
			{
				for(int32 i=0;i<Temp.Num()/2;++i)
				{
					Temp.Swap(i,Temp.Num()-i-1);
				}
			}
			std::memcpy(&Value, Temp.GetData(), sizeof(T));
			return Value;
		}

		static FString GetString(const uint8* const StartPos,const uint32 Num)
		{
			const std::string Str{reinterpret_cast<const char*>(StartPos),Num};
			return  STD_STRING_TO_FSTRING(Str);
		}
	};
public:
	FTransData(const ServerEOpCode::Type Type,TArray<uint8>&& PbContent)
	{
		Head.PackLen = Head.HeaderSize+PbContent.Num();
		Head.Operation = Type;
		Head.Seq = GetSequence();
		Head.ProductId = FCsdkCommonTools::GetProductId();
		Body =MoveTemp(PbContent);
		
	}

   TArray<uint8> Serialize() const
	{
		TArray<uint8> Buff;
		Buff.Reserve(Head.PackLen);
		Buff.Append(Head.Serialize());
		Buff.Append(Body);
		return Buff;
	}

	ServerEOpCode::Type GetOpCode() const
	{
		return Head.Operation;
	}
	

	static FTransData Deserialize(const TArray<uint8>& Array)
	{
		FTransData Data{Array};
		return Data;
		
	}

	const TArray<uint8>& GetBody() const
	{
		return Body;
	}
private:
	FTransData(const TArray<uint8>& Array)
	{
		Head = FTransHead::Deserialize(Array.GetData());
		Body = TArray<uint8>{Array.GetData()+26,Array.Num()-26};
	}
	
	FTransHead Head;
	TArray<uint8> Body;
	static int32 GetSequence()
	{
		static int32 SequenceCounter = 0;
		return SequenceCounter++;
	}

};
