/* 
    This file contains the core of the game, the controls in which everything 
    is run, it contains the global variables that store the current game state
    if contains the main loop, it contains the collision detection routines
    and the functions to divide up the game area, basically it is the game.

    Copyright, 2003 Caleb Moore
    
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/

#include <vector>
using namespace std;

#include "ball.h"
#include "arch.h"
#include "square.h"
#include "graphics.h"
#include "scorescreen.h"
#include "game.h"
#include <cstdlib>
#include "slicer.h"
#include "sidebar.h"
#include "player.h"
#include "explosiveball.h"
#include <fstream>
#include <iostream>
#include "menu.h"
#include "killerball.h"
#include "magnet.h"
#include "highscores.h"

#include <SDL/SDL.h>

using namespace std;

vector<ball> ballz;
static vector<ball> ballzdue;
static vector<unsigned int> ballzcondemned;

static vector<square> squarez;
static vector<explosion> boomz;

slicer chopper;
magnetpair magnets;

static player you;

bool activechop;

const int gashwidth = 5;

static int dead;

int clearnessneeded;
int clearness;
int currentlevel;
int lives;
int lazershots;
int magnetshots;
int score;
int bonuslevelsdue;
int isbonuslevel;

float sincelastprize;

//options
extern int cheat;
extern int zappy;
extern int hardness;

const int startlives = 5;

const float betweengoodys = 200000;
//const float betweengoodys = 50;
//loads up a level from a text file

void bonuslevel()
{
  isbonuslevel = 1;

  //change the background
  set_bg("bonus.jpg");
  set_backimage("bonus.jpg");

  //make the open space cover all the screen
  squarez.push_back(square());
  squarez[0].topy = 0;
  squarez[0].bottomy = 600;
  squarez[0].leftx = 0;
  squarez[0].rightx = 700;

  ball tempball;  

  for (int i = 0; i < 30; i++)
    {
      add_goody();
    }

  bonuslevelsdue--;

  clearness = 0;

  if (zappy == 2) // everyone will want to make a vertical cut first
    you.setdirection('h');
  else	
    you.setdirection('v');
	
}

void loadlevel(char * filename, int level)
{
  //shut down the current level
  ballz.clear();
  squarez.clear();
  activechop = 0;
  magnets.timetolive = 0;

  if (bonuslevelsdue)
    {
      bonuslevel();
      return;
    }

  isbonuslevel = 0;

  char temp[100];
  absolutepath(filename, temp);
  ifstream fin(temp);
  if (!fin.good())
    {
      cout << "could not open level file\n";
      exit(0);
    }


  int currentlev = 0;
  while (currentlev < level)
    {
      for (char cur = ' '; fin.good(); cur = fin.get())
	if (cur == '#')
	  break;
      if (fin.good())
	fin >> currentlev;
      else
	{
	  fin.close();
	  dead = 1;
	  return;
	}
    }

  fin >> temp;
  float speed = atof(temp);
  fin >> temp;
  clearnessneeded = atoi(temp);
  char bgfile[20];
  fin >> bgfile;
  fin >> temp;

  //change the background
  set_bg(bgfile);
  set_backimage(temp);

  //make the open space cover all the screen
  squarez.push_back(square());
  squarez[0].topy = 0;
  squarez[0].bottomy = 600;
  squarez[0].leftx = 0;
  squarez[0].rightx = 700;

  ball tempball;

  fin >> temp;

  //each following character represents a new ball of a cirtain type
  for (int i = 0; temp[i] != '\0'; i++)
    {
      tempball = ball(temp[i]);
      floatpoint temppoint;
      float direction = float(rand() % 624) / 100.0;
      temppoint.x = cos(direction) * speed * (1.0 + float(hardness) * 0.25);
      temppoint.y = sin(direction) * speed * (1.0 + float(hardness) * 0.25);
      tempball.setvelocity(temppoint);
      random_place(&tempball);
      ballz.push_back(tempball);
    }

  fin.close();

  currentlevel = level;
  clearness = 0;

  if (zappy == 2) // everyone will want to make a vertical cut first
    you.setdirection('h');
  else	
    you.setdirection('v');

}

void addball(ball newball)
{
    ballzdue.push_back(newball);
}

void eraseball(unsigned int condemned)
{
  ballzcondemned.push_back(condemned);
}

void fixlist()
{
  vector<unsigned int>::iterator j;
  vector<ball>::iterator i;

  for (i = ballzdue.begin(); i < ballzdue.end(); i++)
    ballz.push_back(*i);

  for (j = ballzcondemned.begin(); j < ballzcondemned.end(); j++)
    {
      for (i = ballz.begin(); i < ballz.end(); i++)
	if (i->getserial() == *j)
	  break;
      if (i < ballz.end())
	{
	  ballz.erase(i);
	}
    }

  if (ballzcondemned.size() > 0)
    {
      clearempty();
      ballzcondemned.clear();
    }
  ballzdue.clear();
}

//this starts the game from scratch
void restartgame()
{
  dead = 0;
  lazershots = 0;
  magnetshots = 0;
  score = 0;
  sincelastprize = 0;
  loadlevel("data/level.txt", 1);
  lives = startlives;
}

//detirmines the percentage
int coverage()
{
  int total = 0;
  vector<square>::iterator i;
  for (i = squarez.begin(); i < squarez.end(); i++)
    {
      total += (i->bottomy - i->topy) * (i->rightx - i->leftx);
    }
  return int (100 - float(total) / (700.0 * 600.0) * 100.0);
}

//detirmines how many isolated balls
int isolation()
{
  fixlist();
  reassignboundarys();
  clearempty();
  int total = 0;
  vector<square>::iterator i;
  for (i = squarez.begin(); i < squarez.end(); i++)
    {
      int inside = 0;
      vector<ball>::iterator j; 
      for (j = ballz.begin(); j < ballz.end(); j++)
	{
	  if (j->getboundary() == &(*i))
	    inside++;
	}
      if (inside == 1)
	total++;
    }
  return total;
}

//this checks to see if you have won
void victorydetect()
{
  clearness = coverage();
  if (clearness >= clearnessneeded)
    {
      display_scorescreen();
      loadlevel("data/level.txt", currentlevel + 1);
    }
}

//puts a gash though the screen, and devides the square it goes through
//in a vertical direction

void splitx(point splitat)
{
  vector<square>::iterator i;
  //first it iterates through the squares to find the one to be chopped
  for (i = squarez.begin(); i < squarez.end(); i++)
    if (i->isinside(splitat))
      {
	point tl;
	point br;
	tl.x = splitat.x - gashwidth;
	br.x = splitat.x + gashwidth;
	tl.y = i->topy;
	br.y = i->bottomy;
	chop_bg(tl, br);

	vector<square> temp;
	temp = i->chopx(splitat.x - gashwidth, splitat.x + gashwidth);
	squarez.erase(i);
	squarez.push_back(temp[0]);
	squarez.push_back(temp[1]);
	reassignboundarys();
	clearempty();
	return;
      }
}

//puts a gash though the screen, and devides the square it goes through
//in a horizontal direction

void splity(point splitat)
{
  vector<square>::iterator i;
  for (i = squarez.begin(); i < squarez.end(); i++)
    if (i->isinside(splitat))
      {
	point tl;
	point br;
	tl.y = splitat.y - gashwidth;
	br.y = splitat.y + gashwidth;
	tl.x = i->leftx;
	br.x = i->rightx;
	chop_bg(tl, br);

	vector<square> temp;
	temp = i->chopy(splitat.y - gashwidth, splitat.y + gashwidth);
	squarez.erase(i);
	squarez.push_back(temp[0]);
	squarez.push_back(temp[1]);
	reassignboundarys();
	clearempty();
	return;
      }
}

//makes all the balls in the square explode
void blow_up(square * dasquare)
{
  vector<ball>::iterator j; 
  for (j = ballz.begin(); j < ballz.end(); j++)
    {
      if (j->getboundary() == dasquare)
	{
	  j->exploded();
	  ballzcondemned.push_back(j->getserial());
	  boomz.push_back(explosion(j->getcenter()));
	  score += 400;
	}
    }


  //if there are balls that are going to be created in a square that's about
  //to blow up, we don't want them to be created so we take them from the inpipe
  vector<unsigned int> choplist;
  for (j = ballzdue.begin(); j < ballzdue.end(); j++)
    {
      if (j->getboundary() == dasquare)
	{
	  j->exploded();
	  ballzcondemned.push_back(j->getserial());
	  boomz.push_back(explosion(j->getcenter()));
	  score += 400;
	}
    }
}

//if a goody has been isolated, make sure it is collected
void collect_goodys(square * dasquare)
{
  vector<ball>::iterator j; 
  for (j = ballz.begin(); j < ballz.end(); j++)
    {
      if (j->getboundary() == dasquare)
	{
	  j->exploded();
	  ballzcondemned.push_back(j->getserial());
	}
    }
}

void random_place(ball * tomove)
{
  while (1)
    {
      int square = int(rand() % squarez.size());
      int xpos = int(rand() % (squarez[square].rightx -
			       squarez[square].leftx - 2 *
			       tomove->getradius()) +
		     squarez[square].leftx+ tomove->getradius());
      int ypos = int(rand() % (squarez[square].bottomy - 
			       squarez[square].topy - 2 * 
			       tomove->getradius()) + 
		     squarez[square].topy + tomove->getradius());
      if (!iscloseto(point(xpos, ypos), tomove->getradius(), tomove))
	{
	  tomove->setboundary(&squarez[square]);
	  tomove->setcenter(point(xpos, ypos));
	  return;
	}
    }
}


//put a random treat somewhere on the screen
void add_goody()
{
  ball toadd;
  switch (rand() % 11)
    {
    case 0:
    case 1:    
    case 2:    
      toadd = ball('d');
      break;
    case 3:
    case 4:
    case 5:
      toadd = ball('z');
      break;
    case 6:
    case 7:
    case 8:
      toadd = ball('m');
      break;
    case 9:
      toadd = ball('l');
      break;
    case 10:
      if (isbonuslevel)
	toadd = ball('d');
      else
	toadd = ball('r');
      break;
    }
  random_place(&toadd);

  floatpoint temppoint;
  float direction = float(rand() % 624) / 100.0;
  temppoint.x = cos(direction) * 1.0;
  temppoint.y = sin(direction) * 1.0;
  toadd.setvelocity(temppoint);

  ballzdue.push_back(toadd);
  sincelastprize = 0;
}

//finds out if any ball is within a cirtain distance from a point, used for
//collision detection, the skip ball is so it doesn't just detect itself

ball * iscloseto(floatpoint other, float distance, ball * skip)
{
  vector<ball>::iterator i;
  
  for (i = ballz.begin(); i < ballz.end(); i++)
    {
      if ((i->getcenter().x - other.x) * 
	  (i->getcenter().x - other.x) + 
	  (i->getcenter().y - other.y) * 
	  (i->getcenter().y - other.y) < 
	  (i->getradius() + distance) * 
	  (i->getradius() + distance))
	if (&(*i) != skip)
	  return &(*i);
    }
  return NULL;  
}

//this will iterate through the balls and put each one into the boundry it now
//is in

void reassignboundarys()
{
  vector<ball>::iterator i;
  vector<square>::iterator j;

  for (i = ballz.begin(); i < ballz.end(); i++)
    for (j = squarez.begin(); j < squarez.end(); j++)
      if (i->isinside(*j))
	{
	  i->setboundary(&(*j));
	  break;
	}
}

//this gets rid of any squares devoid of balls and fills in the uncovered area

void clearempty()
{
  if (isbonuslevel) //don't worry about clearing when the level is 
    return;     //a bonus level

  vector<ball>::iterator j;
  vector<square>::iterator i;
  
  vector<vector<square>::iterator> condemned;
  vector<vector<square>::iterator>::iterator k;

  for (i = squarez.begin(); i < squarez.end(); i++)
    {  
      for (j = ballz.begin(); j < ballz.end(); j++)
	if (*(j->getboundary()) == *i && j->ishostile())
	  break;
      if (j == ballz.end())
	{
	  condemned.push_back(i);
	  collect_goodys(&(*i)); //for collecting goodies
	}
    }

  for (k = condemned.begin(); k < condemned.end(); k++)
    {
      square current;
      current = **k;
      point tl;
      point br;
      tl.y = current.topy;
      br.y = current.bottomy;
      tl.x = current.leftx;
      br.x = current.rightx;
      chop_bg(tl, br);
      squarez.erase(*k);
      sincelastprize += (br.x - tl.x) * (br.y - tl.y);
    }
}

//this puts in a new dividing thing

void addchopper(point center, char dir, int lazerish)
{
  vector<square>::iterator i;
  
  //when lazerish is set to 2, add magnets instead
  if (lazerish == 2)
    {
      for (i = squarez.begin(); i < squarez.end(); i++)
	if (i->isinside(center))
	  {
	    magnets = magnetpair(&(*i), center, dir, 500);
	    return;
	  }
    }
  if (activechop == 1)
    return;
  chopper = slicer(center, 1, dir, lazerish);
  activechop = 1;
  
  for (i = squarez.begin(); i < squarez.end(); i++)
    if (i->isinside(center))
      {
	chopper.boundary = &(*i);
	return;
      }
  activechop = 0;
}

void ouch()
{
  if (zappy == 2)
    {
      if (you.getdirection() == 'v') //if hit, the player will probably want
	you.setdirection('h'); //to cut on the same axis again
      else	
	you.setdirection('v');	
    }
  
  if (cheat != 1 )
    {
      if (lives-- == 0)
	dead = 1;
    }

  activechop = 0;
}

//this will make the chopper expand and interact
void updatechopper(float amount)
{
  if (!activechop)
    return;
  chopper.update(amount);

  vector<ball>::iterator i;
  for (i = ballz.begin(); i < ballz.end(); i++)
    {
      if (chopper.direction == 'h')
	if (chopper.center.y + chopper.width + i->getradius() > 
	    i->getcenter().y &&
	    chopper.center.y - chopper.width - i->getradius() < 
	    i->getcenter().y &&
	    chopper.center.x + chopper.size2 + i->getradius() > 
	    i->getcenter().x &&
	    chopper.center.x - chopper.size1 - i->getradius() < 
	    i->getcenter().x)
	  {
	    if (i->hitchopper())
	      {
		ouch();
		return;
	      }
	  }
      if (chopper.direction == 'v')
	  if (chopper.center.x + chopper.width + i->getradius() > 
	      i->getcenter().x &&
	      chopper.center.x - chopper.width - i->getradius() < 
	      i->getcenter().x &&
	      chopper.center.y + chopper.size2 + i->getradius() > 
	      i->getcenter().y &&
	      chopper.center.y - chopper.size1 - i->getradius() < 
	      i->getcenter().y)
	    {
	      if (i->hitchopper())
		{
		  ouch();
		  return;
		}
	    }
    }

  if (chopper.done)
    {
      activechop = 0;
      if (isbonuslevel)
	{
	  loadlevel("data/level.txt", currentlevel + 1);
	  return;
	}
      if (chopper.direction == 'h')
	splity(chopper.center);
      if (chopper.direction == 'v')
	splitx(chopper.center);
    }
}

//update explosion
void update_explosions(float amount)
{
  unsigned int i;
  
  for (i = 0; i < boomz.size(); i++)
    {
      boomz[i].update(amount);
      if (boomz[i].phase > 10)
	{
	  boomz.clear();
	  return;
	}
    }
}

//update explosion
void display_explosions()
{
  unsigned int i;
  
  for (i = 0; i < boomz.size(); i++)
    boomz[i].display();
}


//this updates the world according to how much the time has passed

void update(float amount)
{
  
  vector<ball>::iterator i;
  for (i = ballz.begin(); i < ballz.end(); i++)
    {
      i->update(amount);
      if (ballzcondemned.size() > 0)
	  break;
    }
  update_explosions(amount);
  fixlist();
  updatechopper(amount);
  magnets.update(amount);
  fixlist();
  if (sincelastprize > betweengoodys)
    add_goody();
  fixlist();
  clearempty();
  victorydetect();
  display();
}

//this throughs the world onto the screen

void display()
{
  display_bg();
  vector<ball>::iterator i;
  for (i = ballz.begin(); i < ballz.end(); i++)
    i->display();
  if (activechop)
    chopper.display();
  display_explosions();
  display_sidebar();
  you.display();
  magnets.display();
  refresh_screen();
}


void leftbutton(point temp)
{
 if (zappy == 0)
   addchopper(temp, you.getdirection(), 0);    
 else if (zappy == 1)
   {
     you.setdirection('v');
     addchopper(temp, you.getdirection(), 0);  
   }
 else if (activechop == 0)
   {
     if (you.getdirection() == 'v')
       you.setdirection('h');
     else	
       you.setdirection('v');		    
     addchopper(temp, you.getdirection(), 0);
   }      
 
}
	     
void rightbutton(point temp)
{
  if (zappy == 0)
    {
      if (you.getdirection() == 'v')
	you.setdirection('h');
      else	
	you.setdirection('v'); 
    }      
  else if (zappy == 1)
    {
      you.setdirection('h');
      addchopper(temp, you.getdirection(), 0); 
    }
  else 
    addchopper(temp, you.getdirection(), 0);
}      


//this basically is for event parseing but also triggers the update regually

void mainloop()
{
  unsigned int ticks;
  unsigned int newticks;

  restartgame();

  srand(time(0));
  ticks = SDL_GetTicks();  

  while (1)
    {
      if (dead)
	{
	  highscoreentry toadd;
	  toadd.name = username();
	  toadd.level = currentlevel;
	  toadd.score = score;
	  add_highscore(toadd);
	  return;
	}
      newticks = SDL_GetTicks();
      update (float(newticks - ticks) / 5.0);
      ticks = newticks;
      SDL_Event event;
      while(SDL_PollEvent(&event))
	{
	  if (event.type == SDL_MOUSEBUTTONDOWN) 
	    {
	      if (event.button.button == SDL_BUTTON_LEFT)
		{
		  point temp;
		  temp.x = event.button.x;
		  temp.y = event.button.y;
		  leftbutton(temp);
		}
	      if (event.button.button == SDL_BUTTON_RIGHT)
		{
		  point temp;
		  temp.x = event.button.x;
		  temp.y = event.button.y;
		  rightbutton(temp);
		}
	    }      
	  
	  
	  if (event.type == SDL_MOUSEMOTION) 
	    {
	      point temp;
	      temp.x = event.motion.x;
	      temp.y = event.motion.y;
	      you.setcenter(temp);
	    }

	  if (event.type == SDL_KEYDOWN) 
	    {
	      if (event.key.keysym.sym == SDLK_ESCAPE)
		{
		  
		  menu exitmenu;
		  exitmenu.set_title("Do you want to end the game?");
		  exitmenu.add_option("No",0);
		  exitmenu.add_option("Yes",1);
		  if (exitmenu.getchoice() == 1)
		    return;
		  ticks = SDL_GetTicks();  //pause the game
		}
	      if (event.key.keysym.sym == SDLK_l && (lazershots > 0 || cheat))
		{
		  if (!cheat)
		    lazershots--;
		  addchopper(you.getcenter(), you.getdirection(), 1); 
		} 
	      if (event.key.keysym.sym == SDLK_m && (magnetshots > 0 || cheat))
		{
		  if (!cheat)
		    magnetshots--;
		  addchopper(you.getcenter(), you.getdirection(), 2); 
		} 
	    }
	  if (event.type == SDL_QUIT) 
	    {
	      menu exitmenu;
	      exitmenu.set_title("Quit?");
	      exitmenu.add_option("No",0);
	      exitmenu.add_option("Yes",1);
	      if (exitmenu.getchoice() == 1)
		exit(0);
	      ticks = SDL_GetTicks();  //pause the game
	    }
	}
    }
}
