#include "allegro.h"

#include "Battle.h"
#include "Loader.h"
#include "gameConstants.h"
#include "UnitList.h"
#include "InputObj.h"
#include "Campaign.h"
#include "Unit.h"
#include "PlayerUnit.h"

#include "Round.h"
#include <vector>
#include <string>
using namespace std;

//have the arena shake when things leave?

volatile int fpsCount = 0;
volatile int fpsMax = 0;
volatile int gotoNextFrame = 0;

/* timer interrupt handler */
void incFrameTick()
{
  gotoNextFrame++;
}
END_OF_FUNCTION(incFrameTick);

void fpsReset ()
{
  if (fpsCount > fpsMax)
    fpsMax = fpsCount;
  fpsCount = 0;
}
END_OF_FUNCTION(fpsReset);

void resetTimers ()
{
  fpsCount = 0;
  fpsMax = 0;
  gotoNextFrame = 0;
}

Battle :: Battle ()
{
  numRound = 0;
  totalRounds = 0;
  units = new UnitList ();
  screenBuffer = NULL;
  noiseInstrument = NULL;
  iO = NULL;
  gs = NULL;
  rnd = new Round;
  arenaDrawPosX = 10;
  arenaDrawPosY = 10;
  arenaDraw = NULL;
  arenaDrawScale = ftofix (1.0);
  arenaDrawAngle = itofix (0);
  statusDraw = NULL;
  gamePaused = false;
  battleEndStatus = 0;
  playerScore = 0;

  //timer locks and such and such.
  LOCK_VARIABLE(fpsCount);
  LOCK_VARIABLE(gotoNextFrame);
  LOCK_VARIABLE(fpsMax);
  LOCK_FUNCTION(incFrameTick);
  LOCK_FUNCTION(fpsReset);
  install_int_ex(incFrameTick, MSEC_TO_TIMER(30));
  install_int_ex(fpsReset, SECS_TO_TIMER(1));
  resetTimers ();
}

int Battle :: battleLoop (string cName, string p, Loader * lder)
{
  //clear input
  iO -> clear ();

  //tell the loader to load the next campaign
  camp.setName (new string (cName));
  pcName.assign(p);

  if (lder -> loadGeneralData () != 0)
    { allegro_message ("Load General failed\n"); return mainMenuStopMode; }

  if (lder -> loadCampaign (camp) != 0)
    { allegro_message ("Load Campaign failed\n"); return mainMenuStopMode; }

  units -> clear ();
  if (lder -> loadAllUnitsFromCampaign (camp) != 0)
    { allegro_message ("Loading units from campaign failed\n"); return mainMenuStopMode; }

  noiseInstrument -> playMusic (camp.getMusic ());

  //get the player character
  lder -> addPC (pcName);

  screenBuffer = create_bitmap (SCREEN_W, SCREEN_H);
  clear_bitmap (screenBuffer);
  show_mouse (NULL);

  //show campaign intro
  showCampaignIntro (camp.getBeginPic ());

  battleEndStatus = 0;
  numRound = 0;

  //get the next round
  getNextRound ();

  playerScore = 0;

  //while the campaign isn't over...
  while (battleEndStatus != battleModeQuit && battleEndStatus != battleModeWinCampaign) {

    battleEndStatus = 0;

    //tell loader to load the next round with win/lose conditions
    lder -> loadRound (camp, numRound, rnd, pcName, noiseInstrument);

    noiseInstrument -> updateMusic ();
    
    //tell unitList to load units
    units -> setupRound (rnd);

    noiseInstrument -> updateMusic ();

    //set up the display and positioning of the arena
    setUpArena ();

    noiseInstrument -> updateMusic ();

    //show main screen w/round intro
    showRoundIntro (30, 0, rnd -> getTitlePic (), lder -> getMouseCursor (), false);

    //battle loop:
    while (!isRoundOver (lder)) //update the round's status with unitlist
      {
	
	//get input from the user and pause game
	iO -> processInput (false);
	
	//pause button!
	if (iO -> getKeyClicked () -> at (keyPause))
	  {
	  gamePaused = !gamePaused;
	  }

	//lock the game timer, too.
	while (gotoNextFrame == 0) {}

	while (gotoNextFrame > 0)
	  {
	    gotoNextFrame--;
	
	    if (!gamePaused)
	      {
		//tell unitlist to tell all units to move
		units -> makeUnits (lder, rnd, iO);
		units -> allUnitsMove (iO, noiseInstrument, lder);
	      }
	  }

	//draw arena and all units
	drawArena (arenaDraw);
	units -> drawUnits (arenaDraw, noiseInstrument, gamePaused);
	//tell all units to make sfx here.

	//draw the arena to the screen
	pivot_scaled_sprite (screenBuffer, arenaDraw, screenBuffer -> w/2, screenBuffer -> h/2, arenaDraw -> w/2, arenaDraw -> h/2, arenaDrawAngle, arenaDrawScale);
	
	//draw relevant stats [health, cursor, etc] depending on round status
	drawHUD (lder);
		
	//record per-round statistics and save it
	recordStats ();

	drawToScreen (NULL, false);
	//play music
	noiseInstrument -> updateMusic ();
      }

    //if the campaign isn't over, loop here

    //set the score
    if (battleEndStatus == battleModeWinRound || battleEndStatus == battleModeWinCampaign)
      playerScore += units -> getPlayerScore ();

    units -> clear ();

    //get the next round
    getNextRound ();
  }

  noiseInstrument -> fadeMusic ();

  destroy_bitmap (screenBuffer);
  show_mouse (screen);
  return mainMenuStopMode;
}

void Battle :: showCampaignIntro (BITMAP * campSplash)
{
  //shows the campaign splash screen.
  if (campSplash != NULL)
    {
      for (int i = 0; i < 30; i ++)
	{
	  masked_blit (campSplash, screenBuffer, 0, 0, (screenBuffer -> w/2) - (campSplash -> w / 2), (screenBuffer -> h/2) - (campSplash -> h / 2), campSplash -> w, campSplash -> h);
	  drawToScreen (NULL, true);
	  noiseInstrument -> updateMusic ();

	  iO -> processInput (false);
	  if (iO -> anyClicked ())
	    i = 30;
	}
    }

  resetTimers ();
  return;
}

void Battle :: showRoundIntro (int delay, int keyDelay, BITMAP * roundPic, BITMAP * mousePic, bool showUnits)
{
  //shows the roundPic screen
  if (roundPic != NULL)
    {
      for (int i = 0; i < delay; i ++)
	{
	  drawArena (arenaDraw);

	  if (showUnits)
	    units -> drawUnits (arenaDraw, noiseInstrument, true);

	  pivot_scaled_sprite (screenBuffer, arenaDraw, screenBuffer -> w/2, screenBuffer -> h/2, arenaDraw -> w/2, arenaDraw -> h/2, arenaDrawAngle, arenaDrawScale);

	  masked_blit (roundPic, screenBuffer, 0, 0, (screenBuffer -> w/2) - (roundPic -> w / 2), (screenBuffer -> h/2) - (roundPic -> h / 2), roundPic -> w, roundPic -> h);

	  //drawToScreen (mousePic, true);
	  drawToScreen (NULL, true);
	  noiseInstrument -> updateMusic ();

	  iO -> processInput (false);
	  if (i > keyDelay && iO -> anyClicked ())
	    i = delay;
	}
    }

  resetTimers ();
  return;
}

void Battle :: getNextRound ()
{
  //returns the next round, based on battle performance.
  //If there are no more rounds, returns -1.

  if (battleEndStatus == battleModeWinRound)
      numRound ++;
  
  if (numRound >= camp.getTotalRounds ())
    {
      showCampaignIntro (camp.getEndPic ());
      battleEndStatus = battleModeWinCampaign;
    }

  return;
}

void Battle :: drawToScreen (BITMAP * mc, bool careAboutTimer)
{
  //this waits for timers and then draws to screen
  //this also draws the mouse cursor (mc.) Send mc as NULL to disable this.

  //draw the mouse, too...
  if (mc != NULL)
    pivot_sprite (screenBuffer, mc, mouse_x, mouse_y, mc -> w/2, mc -> h /2, itofix (0));

  //wait for vsync, then draw

  if (careAboutTimer)
    {
      while (gotoNextFrame == 0) {}
      gotoNextFrame = 0;
    }

  blit(screenBuffer, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
  fpsCount ++;

  //clear the back buffer
  clear_bitmap (screenBuffer);
}

bool Battle :: isRoundOver (Loader * lder)
{
  //determines if the round is over.
  //if true, battleEndStatus is updated, and it returns true.

  //did user abort process?
  if (iO -> getKeyClicked () -> at (keyMenu))
    {
      battleEndStatus = battleModeQuit;
      return true;
    }

  //does the user want to restart?
  if (iO -> getKeyClicked () -> at (keyRestart))
    {
      battleEndStatus = battleModeRestartRound;
      return true;
    }

  //did the user win or lose?
  if (!(units -> anyRemainingAffiliation (affiliationPlayer, lder) || units -> anyRemainingAffiliation (affiliationPlayer, lder)))
    {
      showRoundIntro (90, 15, lder -> getYouLosePic (), lder -> getMouseCursor (), true);
      battleEndStatus = battleModeLoseRound;
      return true;
    }

  if (!(units -> anyRemainingAffiliation (affiliationEnemy, lder) || units -> anyRemainingAffiliation (affiliationEnemy, lder)))
    {
      showRoundIntro (90, 15, lder -> getYouWinPic (), lder -> getMouseCursor (), true);
      battleEndStatus = battleModeWinRound;
      return true;
    }

  return false;
}

void Battle :: drawHUD (Loader * lder)
{
  //draws the Heads-Up-Display (health, status ailments, round name, etc)
  //get the main player

  vector <Unit*> * players = units -> search (searchForPCs, 0);
  PlayerUnit * p1 = NULL;
  int hpBarLeft, hpBarTop, hpBarRight, hpBarBottom, hpBorderCol;
  int roundNameLeft, roundNameBottom, roundNameCol;
  int statusTop, statusSize, statusBrightness, statusSpacing;
  int goodStatusLeft, badStatusLeft;
  int hpCurrentLength = 0, hp2ndLength = 0, hpHyperLength = 0;
  int hpBarCol, hpBar2Col, hpBar3Col;
  char fpsC [80];

  roundNameLeft = SCREEN_W / 2;
  roundNameBottom = SCREEN_H * 9 / 10;
  roundNameCol = makecol (160, 160, 160);

  //round name
  textout_centre_ex (screenBuffer, font, rnd -> getName () -> c_str (), roundNameLeft, roundNameBottom, roundNameCol, -1);

  sprintf (fpsC, "fps: %d", fpsMax);
  textout_centre_ex (screenBuffer, font, fpsC, roundNameLeft, roundNameBottom + 20, roundNameCol, -1);

  if (players -> size () <= 0) return;

  p1 = static_cast <PlayerUnit *> (players -> at (0));

  sprintf (fpsC, "score: %d", p1 -> getScore () + playerScore);
  textout_centre_ex (screenBuffer, font, fpsC, roundNameLeft, roundNameBottom + 10, roundNameCol, -1);

  //get dimensions and positions.
  hpBarTop = SCREEN_H * 3 / 100;
  hpBarLeft = SCREEN_W / 5;
  hpBarRight = SCREEN_W * 4 / 5;
  hpBarBottom = SCREEN_H * 2 / 25;
  hpBarCol = makecol (0, 0, 0);

  statusTop = SCREEN_H * 15 / 100;
  statusSize = SCREEN_H * 70 / 100 / 6;
  statusSpacing = SCREEN_H * 70 / 100 / 4;

  goodStatusLeft = arenaDrawPosX - statusSize;
  badStatusLeft = arenaDrawPosX + fixtoi (arenaDrawScale * arenaDraw -> w);

  statusDraw = create_bitmap (statusSize, statusSize);

  //draw the hp bar.
  rectfill (screenBuffer, hpBarLeft, hpBarTop, hpBarRight, hpBarBottom, makecol (0, 0, 0));

  //choose colors
  hpBarCol = makecol (64, 32, 32);
  hpBar2Col = makecol (64, 0, 0);
  hpBar3Col = makecol (96, 0, 0);
  hpBorderCol = makecol (160, 160, 160);

  if (p1 -> getHP () > 0)
    {
      hpCurrentLength = (int) ((double) p1 -> getHP () / (double) p1 -> getMaxHP () * (hpBarRight - hpBarLeft));
      rectfill (screenBuffer, hpBarLeft, hpBarTop, hpBarLeft + hpCurrentLength, hpBarBottom, hpBarCol);
    }

  if (p1 -> getHP () > p1 -> getHPCostOfAttack (2))
    {
      hp2ndLength = (int) ((double) (p1 -> getHP () - p1 -> getHPCostOfAttack (2)) / (double) p1 -> getMaxHP () * (hpBarRight - hpBarLeft));
      rectfill (screenBuffer, hpBarLeft, hpBarTop, hpBarLeft + hp2ndLength, hpBarBottom, hpBar2Col);
    }

  if (p1 -> getHP () > p1 -> getHPCostOfAttack (3))
    {
      hpHyperLength = (int) ((double) (p1 -> getHP () - p1 -> getHPCostOfAttack (3)) / (double) p1 -> getMaxHP () * (hpBarRight - hpBarLeft));
      rectfill (screenBuffer, hpBarLeft, hpBarTop, hpBarLeft + hpHyperLength, hpBarBottom, hpBar3Col);
    }

  rect (screenBuffer, hpBarLeft, hpBarTop, hpBarRight, hpBarBottom, hpBorderCol);

  //status ailments
  set_trans_blender(255, 255, 255, 255);  

  for (int j = 0; j < numGoodStatus; j++) {
  if (p1 -> getGoodStatus () -> at (j) > 0)
    {
      statusBrightness = int (255.0 * float(p1 -> getGoodStatus () -> at (j)) / float(p1 -> getGoodStatusMax () -> at (j)));

      clear_bitmap (statusDraw);

      stretch_blit (lder -> getGoodStatusPics () -> at (j), statusDraw, 
		    0, 0, lder -> getGoodStatusPics () -> at (j) -> w, lder -> getGoodStatusPics () -> at (j) -> h, 
		    0, 0, statusDraw -> w, statusDraw -> h);

      draw_lit_sprite(screenBuffer, statusDraw, goodStatusLeft, statusTop + (j * statusSpacing), statusBrightness);
    }}

  for (int j = 0; j < numBadStatus; j++) {
  if (p1 -> getBadStatus () -> at (j) > 0)
    {
      statusBrightness = int (255.0 * float(p1 -> getBadStatus () -> at (j)) / float(p1 -> getBadStatusMax () -> at (j)));

      clear_bitmap (statusDraw);

      stretch_blit (lder -> getBadStatusPics () -> at (j), statusDraw, 
		    0, 0, lder -> getBadStatusPics () -> at (j) -> w, lder -> getBadStatusPics () -> at (j) -> h, 
		    0, 0, statusDraw -> w, statusDraw -> h);

      draw_lit_sprite(screenBuffer, statusDraw, badStatusLeft, statusTop + (j * statusSpacing), statusBrightness);
    }}
}

void Battle :: drawArena (BITMAP * bkg)
{
  //draws the Arena.
  if (rnd -> getArenaPic () != NULL)
    stretch_blit (rnd -> getArenaPic (), bkg, 0, 0, rnd -> getArenaPic () -> w, rnd -> getArenaPic () -> h, 0, 0, bkg -> w, bkg -> h);
}

void Battle :: setGameSettings (GameSettings * g)
{
  gs = g;
}

void Battle :: setMusicSound (MusicSound * m)
{
  noiseInstrument = m;
}

void Battle :: setInputObj (InputObj * i)
{
  iO = i;
}

void Battle :: recordStats ()
{
  //updates per-round stats
  return;
}

void Battle :: setUpArena ()
{
  int adw = 100, adh = 100; //be smarter later and calculate according to the arena's w and h

  adw = fixtoi (rnd -> getArenaSizeX ());
  adh = fixtoi (rnd -> getArenaSizeY ());

  fixed adsx;
  fixed adsy;

  //set up the arena dimensions and stuff.
  destroy_bitmap (arenaDraw);
  arenaDraw = create_bitmap (adw, adh);
  clear_bitmap (arenaDraw);

  //70% of the field is dedicated to it.  

  adsx = ftofix (float (.7 * SCREEN_W) / float (adw));
  adsy = ftofix (float (.7 * SCREEN_H) / float (adh));

  if (adsx <= adsy)
    arenaDrawScale = adsx;
  else
    arenaDrawScale = adsy;

  arenaDrawPosX = (SCREEN_W - fixtoi (adw * arenaDrawScale))/2;
  arenaDrawPosY = (SCREEN_H - fixtoi (adh * arenaDrawScale))/2;
}

Battle :: ~Battle ()
{destroy_bitmap (statusDraw);}
