#include "driver2.h"
#include "replays.h"
#include "map.h"
#include "spool.h"
#include "system.h"
#include "cutscene.h"
#include "players.h"
#include "glaunch.h"
#include "mission.h"
#include "director.h"
#include "camera.h"
#include "civ_ai.h"
#include "cutrecorder.h"

char AnalogueUnpack[16] = { 
	0, -51, -63, -75, -87, -99, -111, -123,
	0,  51,  63,  75,  87,  99,  111,  123
};

int gOutOfTape = 0;

REPLAY_PARAMETER_BLOCK *ReplayParameterPtr = NULL;

REPLAY_STREAM ReplayStreams[MAX_REPLAY_STREAMS];
int NumReplayStreams = 1;

char *ReplayStart;
char *replayptr = NULL;
int ReplaySize = 0;

int PingBufferPos = 0;
PING_PACKET *PingBuffer = NULL;

SXYPAIR *PlayerWayRecordPtr = NULL;
PLAYBACKCAMERA *PlaybackCamera = NULL;

int TimeToWay = 0;
short PlayerWaypoints = 0;
int way_distance = 0;
int gUseStoredPings = 1;
char ReplayMode = 0;

#define REPLAY_IDENT (('D' << 24) | ('2' << 16) | ('R' << 8) | 'P' )

// [D] [T]
void InitPadRecording(void)
{
	char *bufferEnd;

	int remain, cutsSize;
	int i;

	// initialize chases
	cutsSize = CalcInGameCutsceneSize();

	gOutOfTape = 0;

	if (gLoadedReplay == 0 && 
		CurrentGameMode != GAMEMODE_REPLAY &&
		CurrentGameMode != GAMEMODE_DIRECTOR)
	{
		NumReplayStreams = 0;

		ReplayStart = (char*)_replay_buffer;
		ReplayParameterPtr = (REPLAY_PARAMETER_BLOCK *)ReplayStart;

		PlayerWayRecordPtr = (SXYPAIR *)(ReplayParameterPtr + 1);

		PlaybackCamera = (PLAYBACKCAMERA *)(PlayerWayRecordPtr + MAX_REPLAY_WAYPOINTS);

		PingBuffer = (PING_PACKET *)(PlaybackCamera + MAX_REPLAY_CAMERAS);
		setMem8((u_char*)PingBuffer, -1, sizeof(PING_PACKET) * MAX_REPLAY_PINGS);

		replayptr = (char*)(PingBuffer + MAX_REPLAY_PINGS);

		// FIXME: is that correct?
		bufferEnd = replayptr-13380;
		remain = (u_int)ReplayStart - (u_int)bufferEnd - cutsSize;

		for (i = 0; i < NumPlayers; i++)
		{
			AllocateReplayStream(&ReplayStreams[i], remain / sizeof(PADRECORD) / NumPlayers);
			NumReplayStreams++;
		}

		ReplaySize = (replayptr - ReplayStart);
	}
	else
	{
		// reset stream count as cutscene/chase can increase it
		NumReplayStreams = NumPlayers;
		
		for (i = 0; i < NumReplayStreams; i++)
		{
			ReplayStreams[i].playbackrun = 0;
			ReplayStreams[i].PadRecordBuffer = ReplayStreams[i].InitialPadRecordBuffer;
		}
	}

	TimeToWay = 150;
	PlayerWaypoints = 0;
	way_distance = 100;

	InitDirectorVariables();
	InitInGameCutsceneVariables();
}


// [D] [T]
int SaveReplayToBuffer(char *buffer)
{
	REPLAY_STREAM_HEADER *sheader;
	REPLAY_SAVE_HEADER *header;

	if (buffer == NULL)
		return 0x3644;

	char* pt = buffer;
	header = (REPLAY_SAVE_HEADER*)pt;
	pt += sizeof(REPLAY_SAVE_HEADER);

	header->magic = DRIVER2_REPLAY_MAGIC;
	header->GameLevel = GameLevel;
	header->GameType = GameType;
	header->MissionNumber = gCurrentMissionNumber;

	header->NumReplayStreams = NumReplayStreams - NumCutsceneStreams; 
	header->NumPlayers = NumPlayers;
	header->CutsceneEvent = -1;
	header->RandomChase = gRandomChase;
	
	header->gCopDifficultyLevel = gCopDifficultyLevel;
	header->ActiveCheats = ActiveCheats;

	header->wantedCar[0] = wantedCar[0];
	header->wantedCar[1] = wantedCar[1];

	memcpy((u_char*)&header->SavedData, (u_char*)&MissionEndData, sizeof(MISSION_DATA));

	// write each stream data
	for (int i = 0; i < NumPlayers; i++)
	{
		sheader = (REPLAY_STREAM_HEADER *)pt;
		pt += sizeof(REPLAY_STREAM_HEADER);

		REPLAY_STREAM* srcStream = &ReplayStreams[i];

		// copy source type
		memcpy((u_char*)&sheader->SourceType, (u_char*)&srcStream->SourceType, sizeof(STREAM_SOURCE));
		sheader->Size = srcStream->padCount * sizeof(PADRECORD); // srcStream->PadRecordBufferEnd - srcStream->InitialPadRecordBuffer;
		sheader->Length = srcStream->length;

		int size = (sheader->Size + sizeof(PADRECORD)) & -4;

		// copy pad data to write buffer
		memcpy((u_char*)pt, (u_char*)srcStream->InitialPadRecordBuffer, size);

		pt += size;
	}

	memcpy((u_char*)pt, (u_char*)ReplayParameterPtr, sizeof(REPLAY_PARAMETER_BLOCK));
	pt += sizeof(REPLAY_PARAMETER_BLOCK);

	memcpy((u_char*)pt, (u_char*)PlayerWayRecordPtr, sizeof(SXYPAIR) * MAX_REPLAY_WAYPOINTS);
	pt += sizeof(SXYPAIR) * MAX_REPLAY_WAYPOINTS;

	memcpy((u_char*)pt, (u_char*)PlaybackCamera, sizeof(PLAYBACKCAMERA) * MAX_REPLAY_CAMERAS);
	pt += sizeof(PLAYBACKCAMERA) * MAX_REPLAY_CAMERAS;

	memcpy((u_char*)pt, (u_char*)PingBuffer, sizeof(PING_PACKET) * MAX_REPLAY_PINGS);
	pt += sizeof(PING_PACKET) * MAX_REPLAY_PINGS;

	// [A] is that ever valid?
	if (gHaveStoredData)
	{
		header->HaveStoredData = 0x91827364;	// -0x6e7d8c9c
		memcpy((u_char*)pt, (u_char*)&MissionStartData, sizeof(MISSION_DATA));
	}

#ifdef PSX
	return 0x3644;		// size?
#else
	return pt - buffer;
#endif
}

// [D] [T]
int LoadReplayFromBuffer(char *buffer)
{
	REPLAY_SAVE_HEADER *header;
	REPLAY_STREAM_HEADER *sheader;

	char* pt = buffer;

	header = (REPLAY_SAVE_HEADER*)pt;

	if (header->magic != DRIVER2_REPLAY_MAGIC)
		return 0;

	ReplayStart = replayptr = (char*)_replay_buffer;

	GameLevel = header->GameLevel;
	GameType = (GAMETYPE)header->GameType;

	gCurrentMissionNumber = header->MissionNumber;

	NumReplayStreams = header->NumReplayStreams;
	NumPlayers = header->NumPlayers;
	gRandomChase = header->RandomChase;
	CutsceneEventTrigger = header->CutsceneEvent;
	gCopDifficultyLevel = header->gCopDifficultyLevel;
	ActiveCheats = header->ActiveCheats; // TODO: restore old value

	wantedCar[0] = header->wantedCar[0];
	wantedCar[1] = header->wantedCar[1];

	memcpy((u_char*)&MissionEndData, (u_char*)&header->SavedData, sizeof(MISSION_DATA));

	pt = (char*)(header+1);

	int maxLength = 0;
	for (int i = 0; i < NumReplayStreams; i++)
	{
		sheader = (REPLAY_STREAM_HEADER *)pt;
		pt += sizeof(REPLAY_STREAM_HEADER);

		REPLAY_STREAM* destStream = &ReplayStreams[i];
		
		// copy source type
		memcpy((u_char*)&destStream->SourceType, (u_char*)&sheader->SourceType, sizeof(STREAM_SOURCE));

		int size = (sheader->Size + sizeof(PADRECORD)) & -4;
		
		// init buffers
		destStream->InitialPadRecordBuffer = (PADRECORD*)replayptr;
		destStream->PadRecordBuffer = (PADRECORD*)replayptr;
		destStream->PadRecordBufferEnd = (PADRECORD*)(replayptr + size);
		destStream->playbackrun = 0;
		destStream->padCount = sheader->Size / sizeof(PADRECORD);

		// copy pad data and advance buffer
		memcpy((u_char*)replayptr, (u_char*)pt, size);
		replayptr += size;

		pt += size;

		destStream->length = sheader->Length;

		if (sheader->Length > maxLength)
			maxLength = sheader->Length;
	}

	ReplayParameterPtr = (REPLAY_PARAMETER_BLOCK *)replayptr;
	memcpy((u_char*)ReplayParameterPtr, (u_char*)pt, sizeof(REPLAY_PARAMETER_BLOCK));
	pt += sizeof(REPLAY_PARAMETER_BLOCK);

	PlayerWayRecordPtr = (SXYPAIR *)(ReplayParameterPtr + 1);
	memcpy((u_char*)PlayerWayRecordPtr, (u_char*)pt, sizeof(SXYPAIR) * MAX_REPLAY_WAYPOINTS);
	pt += sizeof(SXYPAIR) * MAX_REPLAY_WAYPOINTS;
		
	PlaybackCamera = (PLAYBACKCAMERA *)(PlayerWayRecordPtr + MAX_REPLAY_WAYPOINTS);
	memcpy((u_char*)PlaybackCamera, (u_char*)pt, sizeof(PLAYBACKCAMERA) * MAX_REPLAY_CAMERAS);
	pt += sizeof(PLAYBACKCAMERA) * MAX_REPLAY_CAMERAS;

	PingBufferPos = 0;
	PingBuffer = (PING_PACKET *)(PlaybackCamera + MAX_REPLAY_CAMERAS);
	memcpy((u_char*)PingBuffer, (u_char*)pt, sizeof(PING_PACKET) * MAX_REPLAY_PINGS);
	pt += sizeof(PING_PACKET) * MAX_REPLAY_PINGS;

	replayptr = (char*)(PingBuffer + MAX_REPLAY_PINGS);

	if (header->HaveStoredData == 0x91827364)	// -0x6e7d8c9c
	{
		memcpy((u_char*)&MissionStartData, (u_char*)pt, sizeof(MISSION_DATA));
		gHaveStoredData = 1;
	}

	return 1;
}

#ifndef PSX
int LoadUserAttractReplay(int mission, int userId)
{
	char customFilename[64];
	
	if (userId >= 0 && userId < gNumUserChases)
	{
		sprintf(customFilename, "REPLAYS\\User\\%s\\ATTRACT.%d", gUserReplayFolderList[userId], mission);

		if (FileExists(customFilename))
		{
			if (Loadfile(customFilename, (char*)_other_buffer))
				return LoadReplayFromBuffer((char*)_other_buffer);
		}
	}

	return 0;
}
#endif

// [D] [T]
int LoadAttractReplay(int mission)
{
	char filename[32];

#ifndef PSX
	int userId = -1;
	
	// [A] REDRIVER2 PC - custom attract replays
	if (gNumUserChases)
	{
		userId = rand() % (gNumUserChases + 1);

		if (userId == gNumUserChases)
			userId = -1;
	}

	if (LoadUserAttractReplay(mission, userId))
	{
		printInfo("Loaded custom attract replay (%d) by %s\n", mission, gUserReplayFolderList[userId]);
		return 1;
	}
#endif

	sprintf(filename, "REPLAYS\\ATTRACT.%d", mission);

	if (!FileExists(filename))
		return 0;

	if (!Loadfile(filename, (char*)_other_buffer))
		return 0;

	return LoadReplayFromBuffer((char*)_other_buffer);
}

// [D] [T]
char GetPingInfo(char *cookieCount)
{
	char retCarId;
	PING_PACKET *pp;

	retCarId = -1;

	if (PingBuffer && PingBufferPos < MAX_REPLAY_PINGS)
	{
		pp = &PingBuffer[PingBufferPos];
		
		// accept only valid car pings
		if (pp->frame != 0xFFFF)
		{
			if (CameraCnt - frameStart < pp->frame) 
				return -1;

			retCarId = pp->carId;
			*cookieCount = pp->cookieCount;
		}

		PingBufferPos++;
	}

	return retCarId;
}

// [A] returns 1 if can use ping buffer
int IsPingInfoAvailable()
{
	if (!_CutRec_IsPlaying() && (gUseStoredPings == 0 || gInGameChaseActive == 0))// && gLoadedReplay == 0)
		return 0;
	
	return PingBuffer != NULL && PingBufferPos < MAX_REPLAY_PINGS;
}

// [D] [T]
int valid_region(int x, int z)
{
	XYPAIR region_coords;

	int region;

	region_coords.x = (x >> 16) + regions_across / 2;
	region_coords.y = (z >> 16) + regions_down / 2;

	if (region_coords.x >= 0 && region_coords.x <= regions_across && 
		region_coords.y >= 0 && region_coords.y <= regions_down) 
	{
		region = region_coords.x + region_coords.y * regions_across;

		if (region != regions_unpacked[0])
		{
			if (region == regions_unpacked[1])
				return region + 1;

			if (region == regions_unpacked[2])
				return region + 1;

			if (region != regions_unpacked[3])
				return 0;
		}

		return region + 1;
	}

	return 0;
}

// [D] [T]
int Get(int stream, u_int *pt0)
{
	REPLAY_STREAM* rstream;

	if (stream < NumReplayStreams)
	{
		rstream = &ReplayStreams[stream];

		if (rstream->PadRecordBuffer + 1 <= rstream->PadRecordBufferEnd)
		{
			u_int t0 = (rstream->PadRecordBuffer->pad << 8) | rstream->PadRecordBuffer->analogue;
			*pt0 = t0;

			if (rstream->playbackrun < rstream->PadRecordBuffer->run)
			{
				rstream->playbackrun++;
			}
			else
			{
				rstream->PadRecordBuffer++;
				rstream->playbackrun = 0;
			}

			return 1;
		}
	}

	*pt0 = 0x10;

	return 0;
}

// [D] [T]
int Put(int stream, u_int*pt0)
{
	REPLAY_STREAM *rstream;
	u_int t0;
	PADRECORD *padbuf;

	rstream = &ReplayStreams[stream];

	if (rstream->PadRecordBuffer + 1 >= rstream->PadRecordBufferEnd)
		return 0;

	padbuf = rstream->PadRecordBuffer;
	t0 = *pt0;

	if (CameraCnt != 0 && padbuf->run != 0xEE)
	{
		if (padbuf->pad == ((t0 >> 8) & 0xff) &&
			padbuf->analogue == (t0 & 0xff) &&
			padbuf->run != 143)
		{
			padbuf->run++;
			return 1;
		}

		padbuf++;

		padbuf->pad = (t0 >> 8) & 0xFF;
		padbuf->analogue = t0 & 0xFF;
		padbuf->run = 0;

		rstream->PadRecordBuffer = padbuf;
		rstream->padCount++;

		return 1;
	}

	padbuf->pad = (t0 >> 8) & 0xFF;
	padbuf->analogue = t0 & 0xFF;
	padbuf->run = 0;

	return 1;
}

// [D] [T]
int cjpPlay(int stream, u_int *ppad, char *psteer, char *ptype)
{
	int ret;
	int t1;
	u_int t0;

#ifdef CUTSCENE_RECORDER
	if (stream < 0)
		stream = -stream;
#endif

	ret = Get(stream, &t0);

	t1 = (t0 >> 8) & 0xF;

	*ppad = t0 & 0xF0FC;

	if (t1 == 0) 
	{
		*psteer = 0;
		*ptype = 0;
	}
	else 
	{
		*psteer = AnalogueUnpack[t1];
		*ptype = 4;
	}

	return ret;
}

// [D] [T]
void cjpRecord(int stream, u_int *ppad, char *psteer, char *ptype)
{
	int tmp;
	int t1;
	u_int t0;

	if (stream > -1 && stream < NumReplayStreams) 
	{
		RecordWaypoint();

		if ((*ptype & 4U) == 0) 
		{
			t1 = 0;
		}
		else 
		{
			if (*psteer < -45) 
			{
				tmp = -45 - *psteer >> 0x1f;	// [A] still need to figure out this
				t1 = (((-45 - *psteer) / 6 + tmp >> 1) - tmp) + 1;
			}
			else if (*psteer < 46)
			{
				t1 = 8;
			}
			else
			{
				tmp = *psteer - 45 >> 0x1f;	// [A] still need to figure out this
				t1 = (((*psteer - 45) / 6 + tmp >> 1) - tmp) + 9;
			}
		}

		t0 = (t1 & 0xF) << 8 | *ppad & 0xF0FC;

		if (Put(stream, &t0) == 0)
		{
			gOutOfTape = 1;
		}
		else if(NoPlayerControl == 0)
		{
			ClearCameras = 1;

			if (ReplayMode != 3 && ReplayMode != 8)
			{
				ReplayStreams[stream].length = CameraCnt;
				ReplayParameterPtr->RecordingEnd = CameraCnt;
			}
		}

		t1 = (t0 >> 8) & 0xF;

		if (t1 == 0) 
		{
			*psteer = 0;
			*ptype = 0;
		}
		else 
		{
			*psteer = AnalogueUnpack[t1];
			*ptype = 4;
		}

		*ppad = t0 & 0xF0FC;
	}
}

// [D] [T]
void AllocateReplayStream(REPLAY_STREAM *stream, int maxpad)
{
	stream->playbackrun = 0;
	stream->length = 0;
	stream->padCount = 0;

	stream->InitialPadRecordBuffer = (PADRECORD*)replayptr;
	stream->PadRecordBuffer = (PADRECORD*)replayptr;

	stream->PadRecordBufferEnd = (PADRECORD *)(replayptr + maxpad * sizeof(PADRECORD));

	if (NoPlayerControl == 0)
	{
		*replayptr = 0;

		stream->InitialPadRecordBuffer->analogue = 0;
		stream->InitialPadRecordBuffer->run = 238;
	}

	replayptr = (char *)(((u_int)replayptr + (maxpad+1) * sizeof(PADRECORD)) & -4);
}

// [D] [T]
void RecordWaypoint(void)
{
	if (TimeToWay > 0)
	{
		TimeToWay--;
		return;
	}

	if (PlayerWaypoints < MAX_REPLAY_WAYPOINTS)
	{
		TimeToWay = way_distance;

		PlayerWayRecordPtr->x = (player[0].pos[0] >> 10);
		PlayerWayRecordPtr->y = (player[0].pos[2] >> 10);

		PlayerWayRecordPtr++;
		PlayerWaypoints++;
	}
}
