/*
===========================================================================

			Raycasting 3-D engine
			=====================

	Credits(so far,in order of.. appearance)
	----------------------------------------

	Giorgos Sfikas 		alias Tyrkov,email    powerslave_gr@yahoo.com
	Kostas Michalopoulos	alias BadSector,email kom000@otenet.gr
	Fillipos Papadopoulos	alias Urban,email     dx@emu.net

	Probably to join in
	-------------------

	Panagiotis Papapetrou
	Giannis Sioutis


  Notes: 1) The map legend is in MAPLEG.TXT
	 2) Function explanations are in the header files



===========================================================================
*/

#include "ioanhead.h"
#include "video.h"
#include "3d.h"
#include "trig.h"
#include "mouse.h"
#include "tb_input.h"
#include "version.h"

#define TEXTURES_NUM	9

// Global variables
int		i;					// A helping hand
byte far *	dbuffer;				// Double buffer(Buffer for the VGA)
bitmap		texture[TEXTURES_NUM];                  // textures(bitmap defined in video.h)
enum		{
			DEFAULT_TEXTURE = 0,
			DUNGEON_WALL,
			LIRAS,            //2
			PASXAS,
			HEIL_LIRAS,       //4
			CREDITS,
			HMIZ,             //6
			KILLERS_GO_HOME,
			DARK_WALL         //8
		};


#define	default_bmp	"wall1.bmp"
char *		texturefile[TEXTURES_NUM] ={   default_bmp,
						"wall1.bmp",       //1
						default_bmp,       //2
						"pasxos.bmp",      //3
						default_bmp,       //4
						"wall1hm.bmp",     //5
						"wall1msg.bmp",    //6
						"wall1kil.bmp",    //7
						"wall2.bmp"	   //8
						};

char *		demofile = "lev1demo.dat";
word		universe[MAP_HEIGHT][MAP_WIDTH];	// Game map
boolean		MapStatus	= false;
#ifdef	TRIGTABLES
double * 	TanTable;		// Trigonometry tables to speed up
double *	CosTable;		// tan,cos,sin calculations
double *	SinTable;
#endif


int InsideMap(point c)
{
	return(!(c.x > MAP_WIDTH * BLOCK_SIZE
			|| c.y > MAP_HEIGHT * BLOCK_SIZE
			|| c.x < 0 || c.y < 0));
}

int HitWall(point c)
{
	if( ((c.x + 1)%BLOCK_SIZE == 0) && ((c.y + 1)%BLOCK_SIZE == 0))
		return( (universe[c.y >> LG_BLOCK_SIZE][c.x >> LG_BLOCK_SIZE] & 0x000F) ||
			(universe[(c.y >> LG_BLOCK_SIZE) + 1][c.x >> LG_BLOCK_SIZE] & 0x000F) ||
			(universe[c.y >> LG_BLOCK_SIZE][(c.x >> LG_BLOCK_SIZE) + 1] & 0x000F) ||
			(universe[(c.y >> LG_BLOCK_SIZE) + 1][(c.x >> LG_BLOCK_SIZE) + 1] & 0x000F) );
	return(universe[c.y >> LG_BLOCK_SIZE][c.x >> LG_BLOCK_SIZE] & 0x000F);
}


void DrawWallSlice(int x,double distance,int TextureName,int offset,boolean shading,boolean candlelight)
{
	int	y;		// h tetagmeni tou simeiou pou sxediazetai
	int	sheight;	// to ipsos ths sthlhs pou sxediazetai
	int 	counter;	// enas metritis
	point	BitPixel;	// to pixel pou tha parthei apo to bitmap
	byte 	color;		// to xroma pou tha zografistei
	int	ShadnDistortion;

	sheight = (int) (((float) BLOCK_SIZE * (float) PLAYER_DIST) / distance);	// ipologismos tou ipsous ths sthlhs me geometria
	#ifndef DEBUG
	for(counter = 0,y = (SCREEN_HEIGHT - sheight) / 2 ; counter< sheight ; counter++,y++)
	if(y >= 0 && y <= SCREEN_HEIGHT)
	{
		BitPixel.x = offset;
		BitPixel.y = (int)( (float)counter * (float)texture[TextureName].height / (float)sheight );
		color = texture[TextureName].data[BitPixel.y * texture[TextureName].width + BitPixel.x];
			if(shading == true && (ShadnDistortion = (int)distance >> SHADN_MODIFIER) ) {
				if ( ((color + ShadnDistortion) >> 4 == color >> 4) )
					color += ShadnDistortion;
				else
					color = ((color >> 4) << 4) + 15;
				}
		plot_pixel(x,y,color,dbuffer);
	}

	// Draw a simple ceiling..
	if(SCREEN_HEIGHT > sheight) {
		for(y = 0 ; y< (SCREEN_HEIGHT - sheight) / 2 ; y++)
			plot_pixel(x,y,1,dbuffer);
	// and a simple floor.
		for(y = (SCREEN_HEIGHT - sheight) / 2 + sheight ; y < SCREEN_HEIGHT ; y++)
			plot_pixel(x,y,1,dbuffer);
		}



	#endif
	#ifdef	DEBUG
	if (getch() == 'q') exit(1);
//	for(counter = 0,y = ROUND(((float)SCREEN_HEIGHT - sheight) / 2.0) ; counter<(int) sheight ; counter++,y++)
//	{
//		BitPixel.x = offset;
//		BitPixel.y = ROUND( (float)counter * (float)texture[0].height / (float)sheight );
//		printf("%d:\t%d\t%d\t%d\n",x,BitPixel.x,BitPixel.y);
//	}
//	printf("%d:\t%d\n",x,offset);
	#endif

}


void DrawWalls(vector position,int MiniMapX,boolean shading,boolean candlelight)
{
	point		HorSection;		// des ta sxolia tou
	point		VerSection;		// Permadi gia afta edo..
	double		angle;       		// i gonia pou rixnoume tin aktina apo ton paikti ston toixo
	double		Verdistance;		// ..kai gia
	double		Hordistance;		// afta!
	int		x,y;
	int		i,j;
	float		CorrectDistance;
	point		CorrectSection;

	angle = position.f + (float) FOV/2.0;
	for( x = 0 ; x < SCREEN_WIDTH ; angle -= (float) FOV/ (float) SCREEN_WIDTH,x++)
	{
		if( ABS(tan(angle)) < .0001 )       // if tan(a) is 0,then
			HorSection.x = INFINITE;     // "deactivate" HorSection
		else {
		HorSection.y = ((int) (position.y / BLOCK_SIZE)) * BLOCK_SIZE - 1;
		if(!LookinUp(angle))
			HorSection.y += BLOCK_SIZE + 1;
		HorSection.x = position.x + (int) ((position.y-HorSection.y) / tan(angle));
		}

		while(1)
		{

			if(!InsideMap(HorSection))
			{
				HorSection.x = INFINITE;  // dil. theto ousiastika
				HorSection.y = INFINITE;  // Hordistance >> Verdistance
				break;			  // etsi oste CorrectDistance = Verdistance
			}
			if(HitWall(HorSection))
				break;
			HorSection.x += SIGN(LookinRight(angle) - 0.5) * (int) (BLOCK_SIZE / ABS(tan(angle)));
			HorSection.y -= SIGN(   LookinUp(angle) - 0.5) * BLOCK_SIZE;

		}

		VerSection.x = ((int) (position.x / BLOCK_SIZE)) * BLOCK_SIZE - 1;
		if(LookinRight(angle))
			VerSection.x += BLOCK_SIZE + 1;
		VerSection.y = position.y + (int) ((position.x - VerSection.x) * tan(angle));


		while(1)
		{
			if(!InsideMap(VerSection))
			{
				VerSection.x = INFINITE;  // Paromoivs me to parapano
				VerSection.y = INFINITE;
				break;
			}
			if(HitWall(VerSection))
				break;
			VerSection.x += SIGN(LookinRight(angle) - 0.5) * BLOCK_SIZE;
			VerSection.y -= SIGN(   LookinUp(angle) - 0.5) * (int) (BLOCK_SIZE * ABS(tan(angle)));

		}
		Hordistance = (int) sqrt(SQR(position.x - HorSection.x) + SQR(position.y - HorSection.y));
		Verdistance = (int) sqrt(SQR(position.x - VerSection.x) + SQR(position.y - VerSection.y));

	#ifdef DEBUG
		printf("%d:\tHor:%f\tVer:%f\n",x,Hordistance,Verdistance);
	#endif

		CorrectDistance = (float) MIN(Verdistance,Hordistance);
		CorrectSection.x = (Verdistance<Hordistance) ? VerSection.x : HorSection.x;
		CorrectSection.y = (Verdistance<Hordistance) ? VerSection.y : HorSection.y;

		DrawWallSlice(x,CorrectDistance * cos(position.f - angle),universe[CorrectSection.y >> LG_BLOCK_SIZE][CorrectSection.x >> LG_BLOCK_SIZE] & 0x000F,
		(Verdistance<Hordistance) ? VerSection.y%BLOCK_SIZE : HorSection.x%BLOCK_SIZE,
		shading,candlelight);

	}

	// Draw the minimap

	for (i=0;i<64;i++)
		for (j=0;j<64;j++) if(i+MiniMapX >= 0 && (universe[j][i] & 0x000F) )
			plot_pixel(i+MiniMapX, j+5, (universe[j][i] & 0x000F)?33:0, dbuffer);
	if(position.x/BLOCK_SIZE+MiniMapX >= 0)
		plot_pixel(position.x/BLOCK_SIZE+MiniMapX, position.y/BLOCK_SIZE+5, 55, dbuffer);

	//

	wait_for_retrace(1);			   	// Perimenoume na ftasei to pistoli ilektronivn kato-dexia
	memcpy(VGA,dbuffer,SCREEN_WIDTH*SCREEN_HEIGHT); // ..kai tote apeikonizetai to skiniko

}

void	Turn(float direction,float angle,vector *position)
{
	position->f += TURNING_SPEED * direction * angle * DEGREE ;
}

void	Move(int direction,int step,vector *position)
{
	point	Added;

	Added.x = WALKING_SPEED * direction * (int) ((step * Cos(position->f)) / 1);
	Added.y = -WALKING_SPEED * direction * (int) ((step * Sin(position->f)) / 1);

	if(!(universe[(position->y + COLLISION_DIST*Added.y) / BLOCK_SIZE][position->x / BLOCK_SIZE]))
		position->y += Added.y;
	if(!(universe[position->y / BLOCK_SIZE][(position->x + COLLISION_DIST*Added.x) / BLOCK_SIZE]))
		position->x += Added.x;

}

void	LoadMap(char *mapname,vector *position)
{
    FILE *	fl;
    int		i,j;

    fl = fopen(mapname, "rb");
	fskip(fl,4);           // to skip the header
	for(i = 0; i<MAP_WIDTH ; i++)
		for(j = 0 ; j<MAP_HEIGHT ; j++)
			universe[j][i] = (word) getc(fl);
	fclose(fl);

	for (i=0;i<MAP_WIDTH;i++)
	for (j=0;j<MAP_HEIGHT;j++) {
	  if ((universe[j][i] & 0x00F0) == 0x20) {
	    position->x=(i+0.5)*BLOCK_SIZE;
	    position->y=(j+0.5)*BLOCK_SIZE;
	    position->f = NORTH;
	    universe[j][i]=0;
	    break;
	  }
	  if ((universe[j][i] & 0x00F0) == 0x30) {
	    position->x=(i+0.5)*BLOCK_SIZE;
	    position->y=(j+0.5)*BLOCK_SIZE;
	    position->f = SOUTH;
	    universe[j][i]=0;
	    break;
	  }
	  if ((universe[j][i] & 0x00F0) == 0x40) {
	    position->x=(i+0.5)*BLOCK_SIZE;
	    position->y=(j+0.5)*BLOCK_SIZE;
	    position->f = WEST;
	    universe[j][i]=0;
	    break;
	  }
	  if ((universe[j][i] & 0x00F0) == 0x50) {
	    position->x=(i+0.5)*BLOCK_SIZE;
	    position->y=(j+0.5)*BLOCK_SIZE;
	    position->f = EAST;
	    universe[j][i]=0;
	    break;
	  }
	}
}

int main(void)
{
	int		c;
	vector		PlayerPos;
	MouseStat *	MouseStatus;
	boolean		MouseActive;
	int		MiniMapX	= -(MAP_WIDTH+1);
	FILE *		df;			 // demofile
	boolean		shading 	= true;  // TODO:these are to be read
	boolean		candlelight	= false; // from the map.

#ifdef	TRIGTABLES
	CreateAllTables();
#endif
#ifndef DEBUG
	// desmevoume mnimi 64k gia ton double buffer
	dbuffer = InitDoubleBuffer(SCREEN_WIDTH,SCREEN_HEIGHT);
	set_mode(VGA_256_COLOR_MODE);
#endif

	LoadMap("level1.map",&PlayerPos);
	#ifdef	PLAY_DEMO
		df = Fopen(demofile,"rb");
	#endif
	#ifdef	RECORD_DEMO
		df = Fopen(demofile,"wb");
	#endif
	// fortonontai ta textures
	#ifndef	DEBUG
	for ( c = 0 ; c < TEXTURES_NUM ; c++)
		load_bitmap(texturefile[c],&texture[c]);
	set_palette(texture[0].palette);
	#else
	PlayerPos.x = 3380;
	PlayerPos.y = 4512;
	PlayerPos.f = 5.939575;
	#endif

	#ifdef	RECORD_DEMO
		fwrite(&PlayerPos,sizeof(vector),1,df);
	//	fwrite(&MiniMapX,sizeof(vector),1,df);
	#endif
	#ifdef	PLAY_DEMO
		fread(&PlayerPos,sizeof(vector),1,df);
	//	fread(&MiniMapX,sizeof(vector),1,df);
	#endif

	// elegxos an exei egkatastathei mouse - an nai,MouseActive = true
	MouseActive = InitMouse(MouseStatus);
  while(1)
  {
	DrawWalls(PlayerPos,MiniMapX,shading,candlelight);

	if (MapStatus == true && MiniMapX < 5)
		MiniMapX += 5;
	if (MapStatus == false && MiniMapX > -65)
		MiniMapX -= 5;


	#ifdef	RECORD_DEMO
		fwrite(&PlayerPos,sizeof(vector),1,df);
	//	fwrite(&MiniMapX,sizeof(vector),1,df);
	#endif


#ifndef	PLAY_DEMO
	// elegxos an patithike pliktro (keyboard)
	if(!CheckKbStatus(&PlayerPos)) break;
	// kai an kounithike to mouse i an patithike pliktro tou
	if(MouseActive)
		CheckMouseStatus(&PlayerPos);

#else

	if (!feof(df)) {
		fread(&PlayerPos,sizeof(vector),1,df);
	//	fread(&MiniMapX,sizeof(vector),1,df);
		}
	else
		break;
	if( kbhit() ) {
		c = getch();
		if(c == 'q') break;
		}


#endif
    }

//////////////////////////////
//
// End of program,now clean up
//
//////////////////////////////


#ifndef DEBUG
	// eleftheronetai i mnimi gia dbuffer
	free(dbuffer);
	for(i = 0 ; i< TEXTURES_NUM ; i++)
		free(texture[i].data);
#endif
#ifdef	TRIGTABLES
	// elefthorenetai i mnimi gia tous trigonometrikous pinakes
	free(TanTable);
	free(CosTable);
	free(SinTable);
#endif
	set_mode(TEXT_MODE);
#if 	defined(PLAY_DEMO) || defined(RECORD_DEMO)
	fclose(df);
#endif
#ifdef	SHOWCOORDINATES
	printf("PlayerPos.x:\t%d\n",PlayerPos.x);
	printf("PlayerPos.y:\t%d\n",PlayerPos.y);
	printf("PlayerPos.f:\t%f\n",PlayerPos.f);
#endif

#ifdef	SHOWGOESON
	printf("\t\tA 3D engine demo,brought to you by:\n");
	printf("\t\t===================================\n\n\n");
	printf("\t\tGiorgos Sfikas\t\t(Tyrkov)\tcsst9952@zeus.cs.uoi.gr\n");
	printf("\t\tKostas Michalopoulos\t(Bad Sector)\tkom000@otenet.gr\n\n");
	printf("\t\tFor more info,read \"Readme.txt\"\n\n\n\n");
	c = getch();
#endif

	return 0;
}

