#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "draw.h"
#include "fireworks.h"
#include "level.h"
#include "objects.h"

static char *level_map[] = {
#if 1
	"###############",
	"#o.....#.....o#",
	"#.####.#.####.#",
	"#..#...#...#..#",
	"##.#.#####.#.##",
	" #.#.......#.# ",
	" #.#.## ##.#.# ",
	" #...#g g#...# ",
	" #.#.#g g#.#.# ",
	" #.#.#####.#.# ",
	"##.#.#...#.#.##",
	"#..#o..#...#..#",
	"#.######.####p#",
	"#.............#",
	"###############",

	
#else
	"####################",
	"#..................#",
	"#.#######..#######.#",
	"#.#o............o#.#",
	"#.#.#.########.#.#.#",
	"#.#.#..........#.#p#",
	"#.#.#..........#.#.#",
	"#.#.#.###  ###.#.#.#",
	"#.#.#.# g  g #.#.#.#",
	"#...#.# g  g #.#...#",
	"#...#.#      #.#...#",
	"#.#.#.########.#.#.#",
	"#.#.#..........#.#.#",
	"#.#.#####..#####.#.#",
	"#.#.#..........#.#.#",
	"#.#.#.########.#.#.#",
	"#.#o............o#.#",
	"#.#######..#######.#",
	"#..................#",
	"####################",
#endif
};

Level *level_new()
{
	Level *level = malloc(sizeof(Level));
	LevelSquare *sq;
	int x, y;

	memset(level, 0xff, sizeof(Level));
	level->h = sizeof(level_map) / sizeof(*level_map);

	for (level->w=0, y=0; y<level->h; ++y)
		if (strlen(level_map[y]) > level->w)
			level->w = strlen(level_map[y]);

	level->data = malloc(sizeof(LevelSquare) * level->w * level->h);

	level->time = 0;
	level->player = NULL;
	level->gameover = 0;
	level->draw_fireworks = 0;
	level->dots_remaining = 0;
	level->tourmode = 0;
	level->chasecam.enabled = 1;
	level->chasecam.x = level->chasecam.y = level->chasecam.angle = 0;
	level->objects = NULL;
	
	for (y=0, sq=level->data; y<level->h; ++y) {
		char *p;

		for (x=0,p=level_map[y]; *p; ++x, ++p, ++sq) {
			*sq = SQUARE_EMPTY;
			switch (*p) {
			case '.':
				*sq = SQUARE_DOT;
				++level->dots_remaining;
				break;
			case 'o':
				*sq = SQUARE_PILL;
				break;
			case '#':
				*sq = SQUARE_WALL;
				break;
			case 'g':
				ghost_new(level, x, y);
				break;
			case 'p':
				player_new(level, x, y);
				break;
			default:
					
			}
		}
		for (; x<level->w; ++x, ++sq)
			*sq = SQUARE_EMPTY;
	}

	// set up mountains
	
	for (x=0; x<NUM_MOUNTAINS; ++x) {
		level->mountains[x] = (rand() % 256) / 32.0 - 2;
	}
	
	fireworks_init(level);
	
	return level;
}

static inline LevelSquare get_square(Level *lev, int x, int y)
{
	if (x < 0 || y < 0 || x >= lev->w || y >= lev->h)
		return SQUARE_EMPTY;

	return lev->data[y * lev->w + x];
}

#define WALL_HEIGHT 0.4

static inline float get_powerup_z(Level *level, float x, float y)
{
	float i = (level->time / 10.0) + (x / 3.0) + (y / 3.0);

	return (WALL_HEIGHT/2) + (WALL_HEIGHT * sin(i*2) / 4);
}

static void draw_panel(GLfloat x, GLfloat y, GLfloat z)
{
	if (z)
		glColor3f(1, 0, 0);
	else
		glColor3f(0, 0, 1);
	
	glNormal3f(0, 0, 1);
	glBegin(GL_POLYGON);
	glVertex3f(x, y, z);
	glVertex3f(x+1, y, z);
	glVertex3f(x+1, y+1, z);
	glVertex3f(x, y+1, z);
	glEnd();
}

static void draw_wall(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
{
	glBegin(GL_POLYGON);
//	glColor3f(0, 0, 1);
	glVertex3f(x1, y1, 0);
	glVertex3f(x2, y2, 0);
//	glColor3f(1, 0, 0);
	glVertex3f(x2, y2, WALL_HEIGHT);
	glVertex3f(x1, y1, WALL_HEIGHT);
	glEnd();
}

static void level_draw_map(Level *level)
{
	int x, y;

	{
		GLfloat material_diffuse[] = {0.6, 0.6, 0.6, 1};
		glMaterialfv(GL_FRONT, GL_DIFFUSE, material_diffuse);
	}

	for (y=0; y<level->h; ++y) {
		for (x=0; x<level->w; ++x) {
			glColor3f(0, 0, 1);

			switch (get_square(level, x, y)) {
			case SQUARE_WALL:
				glColor3f(1, 0, 0);
				if (get_square(level, x, y-1) != SQUARE_WALL) {
					glNormal3f(0, -1, 0);
					draw_wall(x, y, x+1, y);
				}
				if (get_square(level, x-1, y) != SQUARE_WALL) {
					glNormal3f(-1, 0, 0);
					draw_wall(x, y, x, y+1);
				}
				if (get_square(level, x+1, y) != SQUARE_WALL) {
					glNormal3f(1, 0, 0);
					draw_wall(x+1, y, x+1, y+1);
				}
				if (get_square(level, x, y+1) != SQUARE_WALL) {
					glNormal3f(0, 1, 0);
					draw_wall(x, y+1, x+1, y+1);
				}

				draw_panel(x, y, WALL_HEIGHT);

				break;
				
			case SQUARE_PILL:
				draw_panel(x, y, 0);
				glPushMatrix();
				glTranslatef(x+.5, y+.5,
					     get_powerup_z(level, x, y));
				glScalef(0.3, 0.3, 0.1);
				glColor3f(1, 1, 0);
				glRotatef(90, 1, 0, 0);
				draw_cylinder(30);
				glPopMatrix();
				break;
			case SQUARE_EMPTY:
				draw_panel(x, y, 0);
				break;
			case SQUARE_DOT:
				draw_panel(x, y, 0);
				glPushMatrix();
				glTranslatef(x+.5, y+.5,
					     get_powerup_z(level, x, y));
				glScalef(0.1, 0.1, 0.1);
				glColor3f(1, 1, 1);
				draw_sphere(4, 4);
				glPopMatrix();
				break;
							  
			}
		}
	}
}

inline float get_frac_coord(float x1, float x2, float frac)
{
	return (x1 * (1-frac)) + (x2 * frac);
}

inline int get_frac_angle(int a1, int a2, float frac)
{
	if (a2 - a1 > 180)
		return 360+get_frac_angle(a1, a2-360, frac);

	if (a1 - a2 > 180)
		return 360+get_frac_angle(a1-360, a2, frac);

	return (a1 * (1-frac)) + a2 * frac;
}

inline void get_obj_coords(LevelObject *obj, float *x, float *y)
{
	*x = get_frac_coord(obj->target_x, obj->x,
			    ((float) obj->countdown) / obj->speed);
	*y = get_frac_coord(obj->target_y, obj->y,
			    ((float) obj->countdown) / obj->speed);	
}

static void level_draw_objects(Level *level)
{
	LevelObject *obj;

	for (obj = level->objects; obj; obj = obj->next) {
		if (obj->draw) {
			float x, y;

			get_obj_coords(obj, &x, &y);

			glPushMatrix();
			glTranslatef(x+.5, y+.5, get_powerup_z(level,x, y)-.24);
			glRotatef(obj->angle, 0, 0, 1);
			
			obj->draw(level, obj);
			glPopMatrix();
		}
	}
}

static void level_draw_landscape(Level *level)
{
	int i;
	
	glPushMatrix();

	glScalef(level->w, level->h, 1.0);
	glTranslatef(0.5, 0.5, 0.0);

	for (i=0; i<NUM_MOUNTAINS; ++i) {
		int i2 = (i + 1) % NUM_MOUNTAINS;
		float ang1 = (i * 2 * M_PI) / NUM_MOUNTAINS;
		float ang2 = (i2 * 2 * M_PI) / NUM_MOUNTAINS;

		glColor3f(0, 0.5, 0);
		
		glBegin(GL_POLYGON);

		glNormal3f(0, 0, 0);
		glVertex3f(0, 0, -5);
		glNormal3f(3*cos(ang1), 3*sin(ang1), level->mountains[i]);

		glVertex3f(cos(ang1) * 3, sin(ang1) * 3, level->mountains[i]);
		glNormal3f(3*cos(ang2), 3*sin(ang2), level->mountains[i2]);
		glVertex3f(cos(ang2) * 3, sin(ang2) * 3, level->mountains[i2]);

		glEnd();
	}
	
	glPopMatrix();
}

void level_draw(Level *level)
{
	int chasecam = level->chasecam.enabled;
	int tourphase = 0;
	
	if (level->gameover)
		chasecam = 0;

	if (level->tourmode) {
		tourphase = (level->tourmode / 100) + 1;

		// phase 1: chasecam
		// phase 2: overhead view
		// phase 3: fireworks
		// phase 4: landscape

		if (tourphase == 1)
			chasecam = 1;
		else
			chasecam = 0;
	}
	
	glClearColor(0, 0, 0.5, 1);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	//glFrustum(-0.4, 0.4, -0.3, 0.3, 1, 200);

	glShadeModel(GL_SMOOTH);

	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
	
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);

	glEnable(GL_LIGHTING);

	// lighting
	
	{
		GLfloat light_position[] = {
			12 * cos(level->time/25.0),
			12 * sin(-level->time/25.0),
			3,
			1
		};
		GLfloat light_ambient[] = {0.1, 0.1, 0.1, 1};
		GLfloat light_diffuse[] = {0.3, 0.3, 0.3, 1};
		GLfloat light_specular[] = {0.1, 0.1, 0.1, 1};
		
		glPushMatrix();
		
//		glScalef(1.0 / level->w, 1.0 / level->h, 0.1);	
		glTranslatef(level->w / 2.0, level->h / 2.0, 0);

		glEnable(GL_LIGHT0);
		glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
		glLightfv(GL_LIGHT0, GL_POSITION, light_position);
		glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
		
		glPopMatrix();
	}

	if (tourphase == 4) {
		gluLookAt(0, 0, 0,
			  cos(level->time/20.0),
			  sin(level->time/20.0),
			  0.1,
			  0, 0, 1);
	} else if (!chasecam) {
		gluLookAt(10 * cos(level->time/200.0),
			  10 * sin(level->time/200.0),
			  10,
			  0, 0, 0,
			  0, 0, 1);
		glTranslatef(-level->w / 2.0, -level->h / 2.0, 0);
	} else {
		// chasecam

		// get player position

		float x, y;
		GLfloat ang;

		get_obj_coords(level->player, &x, &y);
		
		// chasecam follows by moving toward that position
		// and the player angle
		
		level->chasecam.x = get_frac_coord(level->chasecam.x, x, 0.3);
		level->chasecam.y = get_frac_coord(level->chasecam.y, y, 0.3);

		level->chasecam.angle = get_frac_angle(level->chasecam.angle,
						       level->player->angle,
						       0.4);

		ang = level->chasecam.angle * M_PI / 180;

		gluLookAt(level->chasecam.x - 3*cos(ang),
			  level->chasecam.y - 3*sin(ang),
			  1.5,
			  x, y, 0.5,
			  0, 0, 1);
	}

	level_draw_landscape(level);

	if (tourphase == 4)       // landscape only
		return;

	level_draw_map(level);

	// after gameover, dont draw objects

	if (!level->gameover && tourphase != 3) {
		level_draw_objects(level);
	}
	
	if (level->draw_fireworks || tourphase == 3)
		fireworks_draw();
}

void level_run(Level *level)
{
	LevelObject *obj;

	++level->time;

	if (level->tourmode) {
		++level->tourmode;
		if (level->tourmode > 400)
			level->tourmode = 0;
	}
	
	if (!level->gameover) {
		for (obj=level->objects; obj; obj = obj->next) {
			--obj->countdown;
			if (obj->countdown <= 0) {
				obj->x = obj->target_x;
				obj->y = obj->target_y;
				obj->move(level, obj);
				obj->countdown = obj->speed;
			}
		}
	}
	
	if (level->draw_fireworks || level->tourmode)
		fireworks_run();
}

void level_destroy(Level *level)
{
	while (level->objects) {
		LevelObject *obj = level->objects;

		level->objects = obj->next;

		if (obj->destructor)
			obj->destructor(obj);
		
		free(obj);
	}

	free(level->data);
	free(level);
}
