/*
 *	Race Mapper
 *
 *	Created by Thomas Harte on 07/10/2005.
 *	Copyright (c) 2005 __MyCompanyName__. All rights reserved.
 */

#include "allegro.h"
#include "Tables.h"
#include "SegmentPlate.h"
#include "FloorCaster.h"
#include <math.h>

BITMAP *back;
volatile bool Quit;
void QuitFunc() {Quit = true;}
int *MouseOver;
int MouseDist;
bool TrackDirty = true;
int XOffset = 0;
#define CIRC_RADIUS 3

struct Path
{
	int Points[8];
	int Curvature, Divisions;
	Path *Next;
} *PathList, *MousePath;

#define get32(f, v)\
	v = fgetc(f);\
	v |= fgetc(f) << 8;\
	v |= fgetc(f) << 16;\
	v |= fgetc(f) << 24

void Setup()
{
	bool firstpoint = true;
	int subtr = 0;

	FILE *input = fopen("nodes.dat", "rb");
	if(input)
	{
		int numnodes;

		get32(input, numnodes);
		Path **EPtr = &PathList;
		while(numnodes--)
		{
			*EPtr = new Path;

			int ic = 8;
			while(ic--)
			{
				get32(input, (*EPtr)->Points[ic]);
			}
			
			if(firstpoint) {subtr = (*EPtr)->Points[0]; firstpoint = false;}
			ic = 4;
			while(ic--)
				(*EPtr)->Points[ic << 1] -= subtr;

			get32(input, (*EPtr)->Curvature);
			
			EPtr = &(*EPtr)->Next;
		}
		*EPtr = NULL;
		fclose(input);
	}
	else
	{
		PathList = new Path;
		PathList->Points[0] = 0; PathList->Points[1] = 0;
		PathList->Points[2] = 20; PathList->Points[3] = 0;
		PathList->Points[4] = 40; PathList->Points[5] = 0;
		PathList->Points[6] = 60; PathList->Points[7] = 0;
		PathList->Next = NULL;
	}
}

#define put32(f, v)\
	fputc((v)&0xff, f);\
	fputc(((v) >> 8)&0xff, f);\
	fputc(((v) >> 16)&0xff, f);\
	fputc((v) >> 24, f)

void FileWrite(void *file, int x, int y, int curv)
{
	put32((FILE *)file, curv); // HIGHLY inefficient storage decision!
	put32((FILE *)file, x);
	put32((FILE *)file, y);
}

void GeneratePoints(void (*OutputFunc)(void *arg, int x, int y, int curv), void *arg)
{
	Path *Out = PathList;
	while(Out)
	{
		int Diff = abs(Out->Points[6] - Out->Points[0]) >> 1;
		Out->Divisions = Diff;
		int *XT = new int[Diff];
		int *YT = new int[Diff];
		int TPoints[8];

		int ic = 8;
		while(ic--)
			TPoints[ic] = Out->Points[ic] << 16;

		calc_spline(TPoints, Diff, XT, YT);

		for(int c = 0; c < (Diff-1); c++)	/* no need for last point, it is duplicated as first of next spline */
			OutputFunc(arg, XT[c], YT[c], Out->Curvature);

		delete[] XT;
		delete[] YT;
		Out = Out->Next;
	}
}

void Shutdown()
{
	/* write nodes file that this program understands */
	FILE *output = fopen("nodes.dat", "wb");
	if(output)
	{
		int numnodes = 0;
		Path *P = PathList;
		while(P)
		{
			numnodes++;
			P = P->Next;
		}
		put32(output, numnodes);

		P = PathList;
		while(P)
		{
			int ic = 8;
			while(ic--)
			{
				put32(output, P->Points[ic]);
			}
			put32(output, P->Curvature);
			P = P->Next;
		}
		fclose(output);
	}

	/* write patches file that the real game understands */
	output = fopen("track.dat", "wb");
	if(output)
	{	
		GeneratePoints(FileWrite, output);
/*		Path *Out = PathList;
		while(Out)
		{
			int Diff = (Out->Points[6] - Out->Points[0]) >> 1;
			int *XT = new int[Diff];
			int *YT = new int[Diff];

			int ic = 8;
			while(ic--)
				Out->Points[ic] <<= 16;

			calc_spline(Out->Points, Diff, XT, YT);

			for(int c = 0; c < (Diff-1); c++)	// no need for last point, it is duplicated as first of next spline
			{
				put32(output, Out->Curvature); // HIGHLY inefficient storage decision!
				put32(output, XT[c]);
				put32(output, YT[c]);
			}

			delete[] XT;
			delete[] YT;
			Out = Out->Next;
		}*/
	}

	while(PathList)
	{
		Path *Next;
		Next = PathList->Next;
		delete PathList;
		PathList = Next;
	}
}

Path *CameraPath;
void DrawPath()
{
	MouseDist = -1;
	MouseOver = NULL;

	Path *I = PathList;
	while(I)
	{
		int NewPoints[8];
		int c = 4;
		while(c--)
		{
			NewPoints[c << 1] = (I->Points[c << 1] >> 1) + XOffset;
			NewPoints[1+(c << 1)] = (SCREEN_H >> 3) - (I->Points[(c << 1)+1] >> 1);
		}

		line(back, NewPoints[0], NewPoints[1], NewPoints[2], NewPoints[3], makecol(64, 64, 64));
		line(back, NewPoints[4], NewPoints[5], NewPoints[6], NewPoints[7], makecol(64, 64, 64));
		spline(back, NewPoints, (I == CameraPath) ? makecol(128, 255, 255) : makecol(255, 255, 255));

		c = 4;
		while(c--)
		{
			circle(back, NewPoints[c << 1], NewPoints[(c << 1)+1], CIRC_RADIUS, makecol(255, 0, 0));
			
			int NewDist = (mouse_x - NewPoints[c << 1])*(mouse_x - NewPoints[c << 1]) + (mouse_y - NewPoints[1+(c << 1)])*(mouse_y - NewPoints[1+(c << 1)]);
			if(NewDist < CIRC_RADIUS*CIRC_RADIUS)
			{
				if(NewDist < MouseDist || !MouseOver)
				{
					MouseDist = NewDist;
					MouseOver = &I->Points[c << 1];
					MousePath = I;
				}
			}
		}

		I = I->Next;
	}

	if(MouseOver)
		circle(back, (MouseOver[0] >> 1) + XOffset, (SCREEN_H >> 3) - (MouseOver[1] >> 1), CIRC_RADIUS, makecol(255, 255, 0));
}

float CameraY, CameraZ;

float PlayerZ = 0;
float PlayerY = 0.2;
float PlayerAng = 0;
Segment *AllSegments, **ASPtr;

#define FConv(v)	(v / (65536.0f*4.0f))
int Buffer[4], BufPtr;
void MakeDisplay(void *arg, int x, int y, int Curvature)
{
	if(BufPtr == 2)
	{
		Buffer[0] = Buffer[2]; Buffer[1] = Buffer[3];
		Buffer[BufPtr] = x; Buffer[BufPtr+1] = y;
		*ASPtr = NewSegment();
		(*ASPtr)->Create(FConv(Buffer[0]), FConv(Buffer[1]), FConv(Buffer[2]), FConv(Buffer[3]), (float)Curvature / 320.0f);
		ASPtr = &(*ASPtr)->Next;
	}
	else
	{
		BufPtr += 2;
		Buffer[BufPtr] = x; Buffer[BufPtr+1] = y;
	}
}

BITMAP *tex;
void DrawTrack(BITMAP *b)
{
	if(TrackDirty)
	{
		TrackDirty = false;
		BufPtr = 0;
		ResetSegments();

		AllSegments = NULL;
		ASPtr = &AllSegments;
		GeneratePoints(MakeDisplay, NULL);
		AllSegments->AlignTextures();
	}

	Segment *StartSegment;
	StartSegment = AllSegments;
	int Hops = 0;
	while(StartSegment->Next && (PlayerZ > StartSegment->Next->OffsetAdjustment))
	{
		StartSegment = StartSegment->Next;
		Hops++;
	}
	CameraPath = PathList;
	while(Hops > CameraPath->Divisions)
	{
		Hops -= CameraPath->Divisions;
		CameraPath = CameraPath->Next;
	}

	float CrossingRatio = (PlayerZ - StartSegment->OffsetAdjustment) / StartSegment->Length;
	float BaseAngle;
	if(StartSegment->Next)
		BaseAngle = StartSegment->Angle + CrossingRatio*(StartSegment->Next->Angle - StartSegment->Angle);
	else
		BaseAngle = StartSegment->Angle;

	CameraY = StartSegment->Height[0] + CrossingRatio*(StartSegment->Height[1] - StartSegment->Height[0]);
	CameraZ = StartSegment->Depth[0] + CrossingRatio*(StartSegment->Depth[1] - StartSegment->Depth[0]);

	CameraY += PlayerY*StartSegment->Normal[1];
	CameraZ += PlayerY*StartSegment->Normal[0];

	int y =  DrawFloor(b, tex, 0, CameraY, CameraZ, BaseAngle+PlayerAng, StartSegment);
	rectfill(b, 0, 0, b->w, y, makecol(0, 0, 32));
}

void AddSegment(Path *OneBefore)
{
	Path *Next = OneBefore->Next;
	OneBefore->Next = new Path;

	/* copied point */
	OneBefore->Next->Points[0] = OneBefore->Points[6];
	OneBefore->Next->Points[1] = OneBefore->Points[7];

	/* keep curve */
	OneBefore->Next->Points[2] = OneBefore->Points[6] + (OneBefore->Points[6]-OneBefore->Points[4]);
	OneBefore->Next->Points[3] = OneBefore->Points[7] + (OneBefore->Points[7]-OneBefore->Points[5]);

	/* make two up for now !*/
	if(!Next)
	{
		OneBefore->Next->Points[4] = OneBefore->Next->Points[2] + 20;
		OneBefore->Next->Points[6] = OneBefore->Next->Points[2] + 40;
		OneBefore->Next->Points[5] =
		OneBefore->Next->Points[7] = OneBefore->Next->Points[3];
	}
	else
	{
		Path *I = Next;
		while(I)
		{
			int ic = 4;
			while(ic--)
				I->Points[ic << 1] += 80;
			I = I->Next;
		}
		OneBefore->Next->Points[6] = Next->Points[0];
		OneBefore->Next->Points[7] = Next->Points[1];
		OneBefore->Next->Points[4] = Next->Points[0] - (Next->Points[2] - Next->Points[0]);
		OneBefore->Next->Points[5] = Next->Points[1] - (Next->Points[3] - Next->Points[1]);
	}

	OneBefore->Next->Next = Next;
}

int main(int argc, const char *argv[])
{
	allegro_init();
	set_close_button_callback(QuitFunc);
	install_keyboard();
	install_mouse();

	int bpp = desktop_color_depth();
	set_color_depth(bpp ? bpp : 32);
	if (set_gfx_mode(GFX_AUTODETECT_WINDOWED, 800, 600, 0, 0)) {
		allegro_message("Error setting 320x200x8 gfx mode:\n%s\n", allegro_error);
		return -1;
	}
	back = create_bitmap(SCREEN_W, SCREEN_H);

	tex = load_bmp("asphalt.bmp", NULL);
	BITMAP *TrackTarget = create_bitmap((SCREEN_W * 3) >> 2, (SCREEN_H * 3) >> 2);
	SetupTables(TrackTarget);
	SetupFloorLogging();
	Setup();

	int *OldMouseOver;
	int o_mouse_b = mouse_b;

	while(!Quit)
	{
		clear(back);

		OldMouseOver = (mouse_b&1) ? MouseOver : NULL;

		DrawTrack(TrackTarget);
		blit(TrackTarget, back, 0, 0, (SCREEN_W >> 2), (SCREEN_H >> 2), TrackTarget->w, TrackTarget->h);

		DrawPath();
		circle(back, (int)(CameraZ*2.0f) + XOffset, (SCREEN_H >> 3) - (int)(CameraY*2.0f), CIRC_RADIUS, makecol(0, 0, 255));
		if(OldMouseOver) MouseOver = OldMouseOver;

		hline(back, 0, SCREEN_H >> 2, SCREEN_W, makecol(255, 255, 255));
		textprintf_ex(back, font, 0, 0, makecol(128, 128, 128), -1, "Elevation");
		textprintf_ex(back, font, SCREEN_W >> 2, 1+(SCREEN_H >> 2), makecol(128, 128, 128), -1, "Realtime view");
		textprintf_ex(back, font, 0, 1+(SCREEN_H >> 2), makecol(128, 128, 128), -1, "Objects");

		show_mouse(back);
		blit(back, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
		show_mouse(NULL);

		if((mouse_b&1) && MouseOver)
		{
			MouseOver[0] = (mouse_x - XOffset) << 1;
			MouseOver[1] = ((SCREEN_H >> 3) - mouse_y) << 1;
			TrackDirty = true;

			if((MouseOver - MousePath->Points) == 6 && MousePath->Next)
			{
				MouseOver[-2] -= MousePath->Next->Points[0] - MouseOver[0];
				MouseOver[-1] -= MousePath->Next->Points[1] - MouseOver[1];
				MousePath->Next->Points[2] -= MousePath->Next->Points[0] - MouseOver[0];
				MousePath->Next->Points[3] -= MousePath->Next->Points[1] - MouseOver[1];

				MousePath->Next->Points[0] = MouseOver[0];
				MousePath->Next->Points[1] = MouseOver[1];
			}
			if((MouseOver - MousePath->Points) == 4 && MousePath->Next)
			{
				MousePath->Next->Points[2] = MousePath->Next->Points[0] + (MouseOver[2] - MouseOver[0]);
				MousePath->Next->Points[3] = MousePath->Next->Points[1] + (MouseOver[3] - MouseOver[1]);
			}
			if((MouseOver - MousePath->Points) == 2 && MousePath != PathList)
			{
				Path *Preceeding = PathList;
				while(Preceeding->Next != MousePath) Preceeding = Preceeding->Next;
				Preceeding->Points[4] = MousePath->Points[0] - (MousePath->Points[2] - MousePath->Points[0]);
				Preceeding->Points[5] = MousePath->Points[1] - (MousePath->Points[3] - MousePath->Points[1]);
			}
		}
		if((mouse_b^o_mouse_b)&mouse_b&2)
			AddSegment(MousePath);
		o_mouse_b = mouse_b;

		if(key[KEY_LEFT]) XOffset += 5;
		if(key[KEY_RIGHT]) XOffset -= 5;

		if(key[KEY_W]) PlayerY+=0.2;
		if(key[KEY_S]) PlayerY-=0.2;
		if(key[KEY_Q]) PlayerAng+=0.2;
		if(key[KEY_A]) PlayerAng-=0.2;
		if(key[KEY_UP]) PlayerZ+=0.3;
		if(key[KEY_DOWN]) PlayerZ-=0.3;
		if(PlayerZ < 0) PlayerZ = 0;
		if(PlayerY < 0.2) PlayerY = 0.2;

		if(CameraPath)
		{
			if(key[KEY_P]) CameraPath->Curvature -= 10;
			if(key[KEY_O]) CameraPath->Curvature += 10;
			TrackDirty = true;
		}
	}

	destroy_bitmap(tex);
	destroy_bitmap(TrackTarget);
	destroy_bitmap(back);
	Shutdown();
	ShutdownFloorLogging();
	FreeTables();
	FreeSegments();
	return 0;
}
END_OF_MAIN();
