/*************************************************************************** 
  alienBlaster 
  Copyright (C) 2004 
  Paul Grathwohl, Arne Hormann, Daniel Kuehn, Soenke Schwardt

  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, 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 "enemy.h"
#include "SDL.h"
#include "surfaceDB.h"
#include "boundingBox.h"
#include "mixer.h"
#include "shots.h"
#include "shot.h"
#include "racers.h"
#include "racer.h"
#include "items.h"
#include "item.h"
#include "wrecks.h"
#include "wreck.h"
#include "global.h"
#include "explosions.h"
#include "explosion.h"
#include "options.h"

Enemy::Enemy( Vector2D pos, Vector2D vel, EnemyTypes whichEnemyType, 
	      bool isInFormation, bool fireByFormation ) {

  this->isInFormation = isInFormation;
  this->fireByFormation = fireByFormation;
  enemyType = whichEnemyType;
  hitpoints = ENEMY_HITPOINTS[ enemyType ];

  this->pos = pos;
  this->vel = vel;
  relTargetPos = Vector2D(0,0);
  
  switch ( enemyType ) {
  case FIGHTER: 
    {
      string fn = LVL_ENEMY_FIGHTER;
      levelConf->getStr( LVL_ENEMY_FIGHTER, fn );
      spriteEnemy = surfaceDB.loadSurface( fn );
      
      fn = LVL_ENEMY_FIGHTER_SHADOW;
      levelConf->getStr( LVL_ENEMY_FIGHTER_SHADOW, fn );
      spriteShadow = surfaceDB.loadSurface( fn, true );
      break;
    }
  case BOMBER:
    {
      string fn = LVL_ENEMY_BOMBER;
      levelConf->getStr( LVL_ENEMY_BOMBER, fn );
      spriteEnemy = surfaceDB.loadSurface( fn );

      fn = LVL_ENEMY_BOMBER_SHADOW;
      levelConf->getStr( LVL_ENEMY_BOMBER_SHADOW, fn );
      spriteShadow = surfaceDB.loadSurface( fn, true );
      break;
    }
  case TANK:
    {
      string fn = LVL_ENEMY_TANK;
      levelConf->getStr( LVL_ENEMY_TANK, fn );
      spriteEnemy = surfaceDB.loadSurface( fn );
      break;
    }

  case BOSS_1_MAIN_GUN:
    {
      string fn = LVL_ENEMY_BOSS_1_MAIN_GUN;
      levelConf->getStr( LVL_ENEMY_BOSS_1_MAIN_GUN, fn );
      spriteEnemy = surfaceDB.loadSurface( fn );
      break;
    }
  case BOSS_1_ROCKET_LAUNCHER:
    {
      string fn = LVL_ENEMY_BOSS_1_ROCKET_LAUNCHER;
      levelConf->getStr( LVL_ENEMY_BOSS_1_ROCKET_LAUNCHER, fn );
      spriteEnemy = surfaceDB.loadSurface( fn );
      break;
    }
  case BOSS_1_SHOT_BATTERY_LEFT:
    {
      string fn = LVL_ENEMY_BOSS_1_SHOT_BATTERY_LEFT;
      levelConf->getStr( LVL_ENEMY_BOSS_1_SHOT_BATTERY_LEFT, fn );
      spriteEnemy = surfaceDB.loadSurface( fn );
      break;
    }
  case BOSS_1_SHOT_BATTERY_RIGHT:
    {
      string fn = LVL_ENEMY_BOSS_1_SHOT_BATTERY_RIGHT;
      levelConf->getStr( LVL_ENEMY_BOSS_1_SHOT_BATTERY_RIGHT, fn );
      spriteEnemy = surfaceDB.loadSurface( fn );
      break;
    }
  case BOSS_2:
    {
      spriteEnemy = surfaceDB.loadSurface( FN_ENEMY_BOSS_2 );
      spriteShadow = surfaceDB.loadSurface( FN_ENEMY_BOSS_2_SHADOW, true );
      boss2PointReached = false;
      boss2TargetPos = Vector2D(SCREEN_WIDTH / 2, 100);
      break;
    }

  default: 
    {
      string fn = LVL_ENEMY_FIGHTER;
      levelConf->getStr( LVL_ENEMY_FIGHTER, fn );
      spriteEnemy = surfaceDB.loadSurface( fn );
      break;
    }
  }

  boundingBox = new BoundingBox( lroundf(pos.getX() - spriteEnemy->w * 0.45),
				 lroundf(pos.getY() - spriteEnemy->h * 0.45),
				 lroundf(spriteEnemy->w * 0.9),
				 lroundf(spriteEnemy->h * 0.9) );
  nextShotPrimary = rand() % (ENEMY_RAND_WAIT_PRIMARY[ enemyType ]+1);
  nextShotSecondary = rand() % (ENEMY_RAND_WAIT_SECONDARY[ enemyType ]+1);

  sndShotPrimary = Mixer::mixer().loadSample( FN_SOUND_SHOT_PRIMARY );
  sndShotSecondary = Mixer::mixer().loadSample( FN_SOUND_SHOT_SECONDARY );
}

Enemy::~Enemy() {
  delete boundingBox;
}


bool Enemy::isExpired() {
  return ( hitpoints <= 0 || 
	   pos.getY() < -500 ||
	   pos.getY() > SCREEN_HEIGHT + 500 ||
	   pos.getX() < -500 || 
	   pos.getX() > SCREEN_WIDTH + 500 );
}


void Enemy::expire() {
  hitpoints = -1;
}

Circle Enemy::getBoundingCircle() {
  return Circle( pos, min(spriteEnemy->w / 2, spriteEnemy->h / 2) );
}

BoundingBox *Enemy::getBoundingBox() {
  return boundingBox;
}

bool Enemy::collidesWith( const Vector2D &shotPosOld, const Vector2D &shotPosNew ) {
  return boundingBox->overlaps(shotPosOld, shotPosNew);
}

bool Enemy::collidesWith( BoundingBox *box ) {
  return boundingBox->overlaps( box );
}

bool Enemy::collidesWith( const Circle &circle ) {
  return boundingBox->overlaps( circle );
}

bool Enemy::collidesWithAsCircle( const Circle &circle ) {
  return ( circle.getRadius() + spriteEnemy->w / 2 > circle.getCenter().distanceTo( pos ) );
}

bool Enemy::collidesWithAsCircle( BoundingBox *box ) {
  return ( box->overlaps( Circle( pos, min( spriteEnemy->h / 2, spriteEnemy->w / 2 ) ) ) );
}


void Enemy::update( int dT ) {
  move( dT );
  if ( !fireByFormation ) {
    shootPrimary( dT );
    shootSecondary( dT );
  }
}

void Enemy::move( int dT ) {
  switch ( enemyType ) {
  case FIGHTER:
  case BOMBER:
    {
      if ( scrollingOn ) pos += vel * dT / 1000.0;
      else pos += (vel - SCROLL_SPEED) * dT / 1000.0;
      if ( isInFormation && relTargetPos != Vector2D(0,0) ) {
        Vector2D addMovement = 
          Vector2D( 40, relTargetPos.getDirection(), POLAR ) * dT / 1000.0;
        if ( addMovement.getLength() > relTargetPos.getLength() ) {
          addMovement.setLength( relTargetPos.getLength() );
        }
        pos += addMovement;
        relTargetPos -= addMovement;
      }
      updateBoundingBox();
      break;
    }
  case TANK: {
    if ( scrollingOn ) pos += vel * dT / 1000.0;
    updateBoundingBox();
    break;
  }
  case BOSS_1_MAIN_GUN:
  case BOSS_1_ROCKET_LAUNCHER:
  case BOSS_1_SHOT_BATTERY_LEFT:
  case BOSS_1_SHOT_BATTERY_RIGHT: {
    if ( scrollingOn ) {
      pos += Vector2D( 0, SCROLL_SPEED * dT / 1000.0 );
      updateBoundingBox();
      if ( pos.getY() >= BOSS_1_END_Y ) {
        scrollingOn = false;
        pos.setY( BOSS_1_END_Y );
      }
    }  
    break;
  }    
  case BOSS_2: {
    if ( boss2PointReached ) {
      boss2TargetPos = Vector2D( (rand() % (SCREEN_WIDTH - spriteEnemy->w)) + spriteEnemy->w / 2, rand() % 100 + spriteEnemy->h );
      boss2PointReached = false;
    } else {
      pos += (boss2TargetPos - pos) / 50;
    }
    if ( pos.distanceTo(boss2TargetPos) < 15.0 ) boss2PointReached = true;
    updateBoundingBox();
    break;
  }
  default: cout << "enemys.cc::move(): unknown enemyType" << endl; break;
  }
}

void Enemy::updateBoundingBox() {
  boundingBox->moveUpperBound( lroundf(pos.getY() - spriteEnemy->h * 0.45) );
  boundingBox->moveLeftBound( lroundf(pos.getX() - spriteEnemy->w * 0.45) );
}


void Enemy::firePrimary() {
  switch (enemyType) {
  case FIGHTER: 
    {
      Shot *shot = 
	new Shot( ENEMY_SHOT_NORMAL, 666, 
		  pos + Vector2D( 0, spriteEnemy->h / 2 ),
		  90 );
      shots->addShot( shot );
      Mixer::mixer().playSample( sndShotPrimary, 0 );
      break;
    }
  case BOMBER:
    {
      Shot *shot = 
	new Shot( ENEMY_SHOT_NORMAL, 666,
		  pos + Vector2D( -7, spriteEnemy->h / 2 ),
		  100 );
      shots->addShot( shot );
      shot = 
	new Shot( ENEMY_SHOT_NORMAL, 666,
		  pos + Vector2D( +7, spriteEnemy->h / 2 ),
		  80 );
      shots->addShot( shot );
      Mixer::mixer().playSample( sndShotPrimary, 0 );
      break;
    }
  case TANK:
    {
      Shot *shot = 
	new Shot( ENEMY_SHOT_NORMAL, 666,
		  pos,
		  (rand() % 360) - 180 );
      shots->addShot( shot );
      Mixer::mixer().playSample( sndShotPrimary, 0 );
      break;
    }
  case BOSS_1_MAIN_GUN:
    {
      Shot *shot = 
	new Shot( ENEMY_SHOT_NORMAL, 666,
		  pos,
		  (rand() % 20) + 80 );
      shots->addShot( shot );
      Mixer::mixer().playSample( sndShotPrimary, 0 );
      break;
    }
  case BOSS_1_SHOT_BATTERY_LEFT:
    {
      Shot *shot = 
	new Shot( ENEMY_SHOT_NORMAL, 666,
		  pos,
		  (rand() % 120) + 30 );
      shots->addShot( shot );
      Mixer::mixer().playSample( sndShotPrimary, 0 );
      break;
    }
  case BOSS_1_SHOT_BATTERY_RIGHT:
    {
      Shot *shot = 
	new Shot( ENEMY_SHOT_NORMAL, 666,
		  pos,
		  (rand() % 120) + 30 );
      shots->addShot( shot );
      Mixer::mixer().playSample( sndShotPrimary, 0 );
      break;
    }
  case BOSS_1_ROCKET_LAUNCHER:
    {
      unsigned int racerIdx = rand() % racers->getNrRacers();
      float angle = (racers->getRacer( racerIdx )->getPos() - pos).getDirection();
      Shot *shot = 
	new Shot( ENEMY_SHOT_TANK_ROCKET, 666, pos, angle );
      shots->addShot( shot );
      Mixer::mixer().playSample( sndShotSecondary, 0 );
      break;
    }
  case BOSS_2:
    {
      unsigned int racerIdx = rand() % racers->getNrRacers();
      float angle = (racers->getRacer( racerIdx )->getPos() - pos).getDirection();
      Shot *shot = new Shot( ENEMY_SHOT_NORMAL, 666, pos, angle );
      shots->addShot( shot );
      Mixer::mixer().playSample( sndShotPrimary, 0 );
      break;
    }
  default:
    {
      break;
    }
  }
}


void Enemy::shootPrimary( int dT ) {
  nextShotPrimary -= dT;
  if ( nextShotPrimary < 0 ) {
    firePrimary();
    nextShotPrimary = 
      (rand() % (ENEMY_RAND_WAIT_PRIMARY[ enemyType ]+1)) + ENEMY_COOLDOWN_PRIMARY[ enemyType ];
  }
}


void Enemy::fireSecondary() {
  switch (enemyType) {
  case TANK:
    {
      unsigned int racerIdx = rand() % racers->getNrRacers();
      float angle = (racers->getRacer( racerIdx )->getPos() - pos).getDirection();
      
      Shot *shot = 
	new Shot( ENEMY_SHOT_TANK_ROCKET, 666, pos, angle );
      shots->addShot( shot );
      Mixer::mixer().playSample( sndShotSecondary, 0 );
      break;
    }
  case BOSS_2:
    {
      unsigned int racerIdx = rand() % racers->getNrRacers();
      float angle = (racers->getRacer( racerIdx )->getPos() - pos).getDirection();
      Shot *shot = new Shot( ENEMY_SHOT_TANK_ROCKET, 666, pos - Vector2D(-80,0), angle );
      shots->addShot( shot );
      shot = new Shot( ENEMY_SHOT_TANK_ROCKET, 666, pos - Vector2D(+80,0), angle );
      shots->addShot( shot );
      Mixer::mixer().playSample( sndShotSecondary, 0 );
      break;
    }
  default:
    {
      break;
    }
  }
}

void Enemy::shootSecondary( int dT ) {
  nextShotSecondary -= dT;
  if ( nextShotSecondary < 0 ) {
    fireSecondary();
    nextShotSecondary = 
      (rand() % (ENEMY_RAND_WAIT_SECONDARY[ enemyType ]+1)) + 
      ENEMY_COOLDOWN_SECONDARY[ enemyType ];
  }
}


void Enemy::doDamage( ShotTypes shotType, int fromWhichPlayer ) {
  bool allreadyDead = isExpired();

  switch (shotType) {
  case SHOT_NORMAL: hitpoints -= DAMAGE_SHOT_NORMAL; break;
  case SHOT_NORMAL_HEAVY: hitpoints -= DAMAGE_SHOT_NORMAL_HEAVY; break;
  case SHOT_DOUBLE: hitpoints -= DAMAGE_SHOT_DOUBLE; break;
  case SHOT_DOUBLE_HEAVY: hitpoints -= DAMAGE_SHOT_DOUBLE_HEAVY; break;
  case SHOT_TRIPLE: hitpoints -= DAMAGE_SHOT_TRIPLE; break;
    
  case SHOT_HF_NORMAL: hitpoints -= DAMAGE_SHOT_HF_NORMAL; break;
  case SHOT_HF_DOUBLE: hitpoints -= DAMAGE_SHOT_HF_DOUBLE; break;
  case SHOT_HF_TRIPLE: hitpoints -= DAMAGE_SHOT_HF_TRIPLE; break;
  case SHOT_HF_QUATTRO: hitpoints -= DAMAGE_SHOT_HF_QUATTRO; break;
  case SHOT_HF_QUINTO: hitpoints -= DAMAGE_SHOT_HF_QUINTO; break;
    
  case SHOT_DUMBFIRE: hitpoints -= DAMAGE_SHOT_DUMBFIRE; break;
  case SHOT_DUMBFIRE_DOUBLE: hitpoints -= DAMAGE_SHOT_DUMBFIRE_DOUBLE; break;
  case SHOT_KICK_ASS_ROCKET: hitpoints -= DAMAGE_SHOT_KICK_ASS_ROCKET; break;
  case SHOT_HELLFIRE: hitpoints -= DAMAGE_SHOT_HELLFIRE; break;
  case SHOT_MACHINE_GUN: hitpoints -= DAMAGE_SHOT_MACHINE_GUN; break;
  case SHOT_ENERGY_BEAM: hitpoints -= DAMAGE_SHOT_ENERGY_BEAM; break;

  case SHOT_HF_DUMBFIRE: hitpoints -= DAMAGE_SHOT_HF_DUMBFIRE; break;
  case SHOT_HF_DUMBFIRE_DOUBLE: hitpoints -= DAMAGE_SHOT_HF_DUMBFIRE_DOUBLE; break;
  case SHOT_HF_KICK_ASS_ROCKET: hitpoints -= DAMAGE_SHOT_HF_KICK_ASS_ROCKET; break;
  case SHOT_HF_LASER: hitpoints -= DAMAGE_SHOT_HF_LASER; break;

  case SPECIAL_SHOT_HEATSEEKER: hitpoints -= DAMAGE_SPECIAL_SHOT_HEATSEEKER; break;
  case SPECIAL_SHOT_NUKE: hitpoints -= DAMAGE_SPECIAL_SHOT_NUKE; break;
  default: 
    {
      cout << "Enemy::doDamage: unexpected shotType: " << shotType << endl;
      break;
    }
  }

  if ( enemyType < NR_ENEMY_TYPES_NORMAL ) {
    // the enemy just died -> explode, generate item, 
    if ( (!allreadyDead) && isExpired() ) {
      // the player gets points, who did the final shot
      if ( 0 <= fromWhichPlayer && fromWhichPlayer < (int)racers->getNrRacers() ) {
        racers->getRacer( fromWhichPlayer )->receivePoints( ENEMY_POINTS_FOR_DEST[ enemyType ]);
      }
      // explode
      Explosion *newExplosion = 
        new Explosion( FN_EXPLOSION_ENEMY, pos, vel, EXPLOSION_NORMAL_AIR );
      explosions->addExplosion( newExplosion );
      // generate wreck
      Wreck *newWreck = new Wreck( pos, WRECK_FOR_ENEMYTYPE[ enemyType ]);
      wrecks->addWreck( newWreck );
      // generate item
      if ( rand() % ENEMY_DIES_ITEM_APPEAR_CHANCE[ enemyType ] == 0 ) {
        items->generateItemNow( pos, vel / 10.0 );
      }
    }
  } 
  // it's a boss!!!
  else {
    if ( (!allreadyDead) && isExpired() ) {
      // explode
      Explosion *newExplosion;
      for(int i = 0; i < 10; i++){
        newExplosion = new Explosion(
          FN_EXPLOSION_ENEMY,
          pos + Vector2D(rand() % spriteEnemy->w - spriteEnemy->w / 2, rand() % spriteEnemy->h - spriteEnemy->h / 2),
          vel + Vector2D(rand() % 100 - 50, rand() % 100 - 50) / 5.0,
          EXPLOSION_NORMAL_GROUND );
        explosions->addExplosion( newExplosion );
      }

      // generate wreck
      Wreck *newWreck = new Wreck( pos, WRECK_FOR_ENEMYTYPE[ enemyType ]);
      wrecks->addWreck( newWreck );      
    }
  }  
}


void Enemy::drawGroundEnemy( SdlCompat_AcceleratedSurface *screen ) {
  if ( ENEMY_FLYING[ enemyType ] ) return;

  SDL_Rect destR;

  destR.x = lroundf(pos.getX()) - spriteEnemy->w / 2;
  destR.y = lroundf(pos.getY()) - spriteEnemy->h / 2;
  destR.w = spriteEnemy->w;
  destR.h = spriteEnemy->h;

  SDL_BlitSurface( spriteEnemy, 0, screen, &destR );
}

void Enemy::drawAirEnemy( SdlCompat_AcceleratedSurface *screen ) {
  if ( !ENEMY_FLYING[ enemyType ] ) return;

  SDL_Rect destR;

  destR.x = lroundf(pos.getX()) - spriteEnemy->w / 2;
  destR.y = lroundf(pos.getY()) - spriteEnemy->h / 2;
  destR.w = spriteEnemy->w;
  destR.h = spriteEnemy->h;

  SDL_BlitSurface( spriteEnemy, 0, screen, &destR );
}

void Enemy::drawShadow( SdlCompat_AcceleratedSurface *screen ) {
  if ( !ENEMY_FLYING[ enemyType ] ) return;

  SDL_Rect destR;

  destR.x = lroundf(pos.getX()) - spriteShadow->w / 2 - ENEMY_FLYING_HEIGHT[ enemyType ];
  destR.y = lroundf(pos.getY()) - spriteShadow->h / 2 + ENEMY_FLYING_HEIGHT[ enemyType ];
  destR.w = spriteShadow->w;
  destR.h = spriteShadow->h;

  SDL_BlitSurface( spriteShadow, 0, screen, &destR );
}


void Enemy::drawStats( SdlCompat_AcceleratedSurface *screen ) {
  // draw status of the bosses
  float pixPerHP = spriteEnemy->w / (float)(ENEMY_HITPOINTS[ enemyType ]);
  SDL_Rect destR;
  // draw damage
  destR.x = lroundf(pos.getX()) - spriteEnemy->w / 2;
  destR.y = lroundf(pos.getY()) - spriteEnemy->h / 2 - 4;
  float damageToDraw = min( (float)ENEMY_HITPOINTS[ enemyType ] / 2, hitpoints );
  destR.w = lroundf(pixPerHP * damageToDraw);
  destR.h = 3;
  SDL_FillRect( screen, &destR, SDL_MapRGB(screen->format, 255, 0, 0) );
  // draw shields
  destR.x = lroundf(pos.getX());
  destR.y = lroundf(pos.getY()) - spriteEnemy->h / 2 - 4;
  float shieldToDraw = min( (float)ENEMY_HITPOINTS[ enemyType ] / 2, 
			    hitpoints - ENEMY_HITPOINTS[ enemyType ] / 2 );
  if ( shieldToDraw < 1 ) destR.w = 0;
  else destR.w = lroundf(pixPerHP * shieldToDraw);
  destR.h = 3;
  SDL_FillRect(screen, &destR, SDL_MapRGB(screen->format, 0, 255, 0) );
}
