#include <math.h>
#include <malloc.h>
#include <string.h>

#include "outfit.h"
#include "physics.h"
#include "lephisto.h"
#include "log.h"
#include "rng.h"
#include "pilot.h"
#include "collision.h"
#include "player.h"
#include "spfx.h"
#include "weapon.h"

#define weapon_isSmart(w) (w->think != NULL)

#define VOICE_PRIORITY_BOLT   10  /* Default. */
#define VOICE_PRIORITY_AMMO   8   /* Higher. */
#define VOICE_PRIORITY_BEAM   6   /* Even higher. */

#define WEAPON_CHUNK 32

/* Some stuff from pilot. */
extern Pilot** pilot_stack;
extern int pilot_nstack;

/* Player stuff. */
extern unsigned int player_target;

/* Ai stuff. */
extern void ai_attacked(Pilot* attacked, const unsigned int attacker);

typedef struct Weapon_ {
  Solid* solid; /* Actually has its own solid. :D */

  unsigned int faction; /* Faction of pilot that shot the weapon. */
  unsigned int parent;  /* The pilot that just shot at you! */
  unsigned int target;  /* Target to hit. Only used by seeking stuff. */
  const Outfit* outfit; /* Related outfit that fired. */

  double lockon;        /* Some weapons have a lockon delay. */
  double timer;         /* Mainly used to see when the weapon was fired. */

  alVoice* voice; /* Virtual voise. */

  /* Update position and render. */
  void(*update)(struct Weapon_*, const double, WeaponLayer); /* Position update and render. */
  void(*think)(struct Weapon_*, const double); /* Some missiles need to be inteligent.a */
} Weapon;

/* Behind Pilot layer. */
static Weapon** wbackLayer = NULL; /* Behind pilots. */
static int nwbackLayer     = 0;    /* Number of elements. */
static int mwbackLayer     = 0;    /* Allocated memory size. */
/* Behind player layer. */
static Weapon** wfrontLayer = NULL;  /* Behind pilots. */
static int nwfrontLayer    = 0;    /* Number of elements. */
static int mwfrontLayer    = 0;    /* Allocated memory size. */

/* Static. */
static Weapon* weapon_create(const Outfit* outfit, const double dir,
                             const Vec2* pos, const Vec2* vel,
                             const unsigned int parent,
                             const unsigned int target);

static void weapon_render(const Weapon* w);
static void weapons_updateLayer(const double dt, const WeaponLayer layer);
static void weapon_update(Weapon* w, const double dt, WeaponLayer layer);
static void weapon_hit(Weapon* w, Pilot* p, WeaponLayer layer);
static void weapon_destroy(Weapon* w, WeaponLayer layer);
static void weapon_free(Weapon* w);
/* Think. */
static void think_seeker(Weapon* w, const double dt);
static void think_smart(Weapon* w, const double dt);
/* Extern. */
void weapon_minimap(const double res, const double w,
    const double h, const RadarShape shape);

/* Draw the minimap weapons (player.c). */
#define PIXEL(x,y) if((shape == RADAR_RECT && ABS(x) < w/2. && ABS(y)<h/2.) || \
  (shape == RADAR_CIRCLE && (((x)*(x)+(y)*(y))<rc))) glVertex2i((x),(y))
void weapon_minimap(const double res, const double w,
    const double h, const RadarShape shape) {
  int i, rc;
  double x, y;

  if(shape == RADAR_CIRCLE) rc = (int)(w*w);

  for(i = 0; i < nwbackLayer; i++) {
    x = (wbackLayer[i]->solid->pos.x - player->solid->pos.x) / res;
    y = (wbackLayer[i]->solid->pos.y - player->solid->pos.y) / res;
    PIXEL(x,y);
  }

  for(i = 0; i < nwfrontLayer; i++) {
    x = (wfrontLayer[i]->solid->pos.x - player->solid->pos.x) / res;
    y = (wfrontLayer[i]->solid->pos.y - player->solid->pos.y) / res;
    PIXEL(x,y);
  }
}
#undef PIXEL

/* Seeker brain, You get what you pay for. :) */
static void think_seeker(Weapon* w, const double dt) {
  double diff;
  if(w->target == w->parent) return; /* HEY! Self harm is not allowed. */

  Pilot* p = pilot_get(w->target);
  if(p == NULL) {
    limit_speed(&w->solid->vel, w->outfit->u.amm.speed, dt);
    return;
  }

  /* Ammo isn't locked on yet.. */
  if(SDL_GetTicks() > (w->outfit->u.amm.lockon)) {
    diff = angle_diff(w->solid->dir, vect_angle(&w->solid->pos, &p->solid->pos));
    w->solid->dir_vel = 10 * diff * w->outfit->u.amm.turn;
    /* Face the target. */
    if(w->solid->dir_vel > w->outfit->u.amm.turn)
      w->solid->dir_vel = w->outfit->u.amm.turn;
    else if(w->solid->dir_vel < -w->outfit->u.amm.turn)
      w->solid->dir_vel = -w->outfit->u.amm.turn;
  }

  vect_pset(&w->solid->force, w->outfit->u.amm.thrust, w->solid->dir);

  limit_speed(&w->solid->vel, w->outfit->u.amm.speed, dt);
}

/* ========================================================
 *  Smart seeker brain. Much better at homing.
 * ========================================================
 */
static void think_smart(Weapon* w, const double dt) {
  Vec2 sv, tv;
  double t;

  if(w->target == w->parent) return; /* No self shooting here. */

  Pilot* p = pilot_get(w->target); /* No null pilots.. */

  if(p == NULL) {
    limit_speed(&w->solid->vel, w->outfit->u.amm.speed, dt);
    return;
  }

  /* Ammo isn't locked on yet. */
  if(w->lockon < 0.) {
    vect_cset(&tv, VX(p->solid->pos) + dt*VX(p->solid->vel),
              VY(p->solid->pos) + dt*VY(p->solid->vel));

    vect_cset(&sv, VX(w->solid->pos) + dt*VX(w->solid->vel),
              VY(w->solid->pos) + dt*VY(w->solid->vel));

    t = -angle_diff(w->solid->dir, vect_angle(&tv, &sv));

    w->solid->dir_vel = t * w->outfit->u.amm.turn; /* Face the target. */

    if(w->solid->dir_vel > w->outfit->u.amm.turn)
      w->solid->dir_vel = w->outfit->u.amm.turn;
    else if(w->solid->dir_vel < -w->outfit->u.amm.turn)
      w->solid->dir_vel = -w->outfit->u.amm.turn;
  }

  vect_pset(&w->solid->force, w->outfit->u.amm.thrust, w->solid->dir);

  limit_speed(&w->solid->vel, w->outfit->u.amm.speed, dt);

}

/* Update all the weapon layers. */
void weapons_update(const double dt) {
  weapons_updateLayer(dt, WEAPON_LAYER_BG);
  weapons_updateLayer(dt, WEAPON_LAYER_FG);
}

/* Update all weapons in the layer. */
static void weapons_updateLayer(const double dt, const WeaponLayer layer) {
  Weapon** wlayer;
  int* nlayer;

  switch(layer) {
  case WEAPON_LAYER_BG:
    wlayer = wbackLayer;
    nlayer = &nwbackLayer;
    break;
  case WEAPON_LAYER_FG:
    wlayer = wfrontLayer;
    nlayer = &nwfrontLayer;
    break;
  }
  int i;
  Weapon* w;
  for(i = 0; i < (*nlayer); i++) {
    w = wlayer[i];
    switch(wlayer[i]->outfit->type) {
    /* Most missiles behave the same. */
    case OUTFIT_TYPE_MISSILE_SEEK_AMMO:
    case OUTFIT_TYPE_MISSILE_SEEK_SMART_AMMO:
    case OUTFIT_TYPE_MISSILE_SWARM_AMMO:
    case OUTFIT_TYPE_MISSILE_SWARM_SMART_AMMO:
      if(wlayer[i]->lockon > 0.) /* Decrement lockon. */
        wlayer[i]->lockon -= dt;
      /* Purpose fallthrough. */

    /* Bolts too. */
    case OUTFIT_TYPE_BOLT:
    case OUTFIT_TYPE_TURRET_BOLT:
      wlayer[i]->timer -= dt;
      if(wlayer[i]->timer < 0.) {
        weapon_destroy(wlayer[i],layer);
        continue;
      }
      break;
    default:
      break;
    }
    weapon_update(wlayer[i], dt, layer);
    /* If the weapon has been deleted we are going to have to hold back one. */
    if(w != wlayer[i]) i--;
  }
}

/* Render all the weapons. */
void weapons_render(const WeaponLayer layer) {
  Weapon** wlayer;
  int* nlayer;
  int i;

  switch(layer) {
  case WEAPON_LAYER_BG:
    wlayer = wbackLayer;
    nlayer = &nwbackLayer;
    break;
  case WEAPON_LAYER_FG:
    wlayer = wfrontLayer;
    nlayer = &nwfrontLayer;
    break;
  }
  for(i = 0; i < (*nlayer); i++)
    weapon_render(wlayer[i]);
}

/* Render the weapons. */
static void weapon_render(const Weapon* w) {
  int sx, sy;
  glTexture* gfx;

  gfx = outfit_gfx(w->outfit);

  /* Get the sprite corresponding to the direction facing. */
  gl_getSpriteFromDir(&sx, &sy, gfx, w->solid->dir);

  gl_blitSprite(gfx, w->solid->pos.x, w->solid->pos.y, sx, sy, NULL);
}

/* Update the weapon. */
static void weapon_update(Weapon* w, const double dt, WeaponLayer layer) {
  int i, wsx, wsy, psx, psy;
  glTexture* gfx;

  gfx = outfit_gfx(w->outfit);

  gl_getSpriteFromDir(&wsx, &wsy, gfx, w->solid->dir);

  for(i = 0; i < pilot_nstack; i++) {
    /* Check for player to exist. */
    if((i == 0) && (player == NULL)) continue;
    psx = pilot_stack[i]->tsx;
    psy = pilot_stack[i]->tsy;

    if(w->parent == pilot_stack[i]->id) continue; /* Hey! That's you. */

    /* Smart weapons only collide with  their target. */
    if((weapon_isSmart(w)) && (pilot_stack[i]->id == w->target) &&
       CollideSprite(gfx, wsx, wsy, &w->solid->pos,
                     pilot_stack[i]->ship->gfx_space, psx, psy, &pilot_stack[i]->solid->pos)) {
      weapon_hit(w, pilot_stack[i], layer);
      return;
    }
    /* Dumb weapons hit anything not of the same faction. */
    if(!weapon_isSmart(w) &&
        !areAllies(w->faction, pilot_stack[i]->faction) &&
            CollideSprite(gfx, wsx, wsy, &w->solid->pos,
                          pilot_stack[i]->ship->gfx_space, psx, psy, &pilot_stack[i]->solid->pos)) {
      weapon_hit(w, pilot_stack[i], layer);
      return;
    }
  }

  /* Smart weapons also get to think their next move. */
  if(weapon_isSmart(w)) (*w->think)(w,dt);

  (*w->solid->update)(w->solid, dt);

  /* Update the sound. */
  if(w->voice)
    voice_update(w->voice, w->solid->pos.x, w->solid->pos.y,
                 w->solid->vel.x, w->solid->vel.y);
}

/* Good shot. */
static void weapon_hit(Weapon* w, Pilot* p, WeaponLayer layer) {
  /* Someone should let the ai know it's been attacked. */
  if(!pilot_isPlayer(p)) {
    if((player_target == p->id) || (RNG(0,2) == 0)) {
      if((w->parent == PLAYER_ID) &&
          (!pilot_isFlag(p, PILOT_HOSTILE) || (RNG(0, 2) == 0))) {
        faction_modPlayer(p->faction, -1); /* Slowly lower faction. */
        pilot_setFlag(p, PILOT_HOSTILE);
      }
      ai_attacked(p, w->parent);
    }
    spfx_add(outfit_spfx(w->outfit),
             VX(w->solid->pos), VY(w->solid->pos),
             VX(p->solid->vel), VY(p->solid->vel), SPFX_LAYER_BACK);
  } else
    spfx_add(outfit_spfx(w->outfit),
             VX(w->solid->pos), VY(w->solid->pos),
             VX(p->solid->vel), VY(p->solid->vel), SPFX_LAYER_FRONT);

  /* Let the ship know that is should take some kind of damage. */
  pilot_hit(p, w->solid, w->parent,
      outfit_damageType(w->outfit), outfit_damage(w->outfit));
  /* We don't need the weapon particle any longer. */
  weapon_destroy(w, layer);
}

static Weapon* weapon_create(const Outfit* outfit, const double dir, const Vec2* pos,
                             const Vec2* vel, unsigned int parent, const unsigned int target) {
  Vec2 v;
  double mass, rdir;
  Pilot* pilot_target;
  Weapon* w;

  /* Create basic features. */
  w = MALLOC_L(Weapon);
  w->faction = pilot_get(parent)->faction; /*Non-Changeable. */
  w->parent = parent; /* Non-Changeable. */
  w->target = target; /* Non-Changeable. */
  w->outfit = outfit; /* Non-Changeable. */
  w->update = weapon_update;
  w->think = NULL;

  switch(outfit->type) {
    /* Bolts treated together. */
    case OUTFIT_TYPE_BOLT:
    case OUTFIT_TYPE_TURRET_BOLT:
      /* Only difference is the direction of fire. */
      if((outfit->type == OUTFIT_TYPE_TURRET_BOLT) && (w->parent != w->target)) {
        rdir = vect_angle(pos, &pilot_get(w->target)->solid->pos);
        rdir += RNG(-outfit->u.blt.accuracy/2.,
            outfit->u.blt.accuracy/2.)/180.*M_PI;
      } else /* Fire straight. */
        rdir = dir + RNG(-outfit->u.blt.accuracy/2.,
            outfit->u.blt.accuracy/2.)/180.*M_PI;

    if((rdir > 2.*M_PI) || (rdir < 0.)) rdir = fmod(rdir, 2.*M_PI);

    mass = 1; /* Lasers are presumed to have unitory mass. */
    vectcpy(&v, vel);
    vect_cadd(&v, outfit->u.blt.speed*cos(rdir), outfit->u.blt.speed*sin(rdir));
    w->timer += outfit->u.blt.range/outfit->u.blt.speed;
    w->solid = solid_create(mass, rdir, pos, &v);
    w->voice = sound_addVoice(VOICE_PRIORITY_BOLT,
                              w->solid->pos.x, w->solid->pos.y,
                              w->solid->vel.x, w->solid->vel.y, w->outfit->u.blt.sound, 0);
    break;
  
  /* Treat seekers togther. */
  case OUTFIT_TYPE_MISSILE_SEEK_AMMO:
  case OUTFIT_TYPE_MISSILE_SEEK_SMART_AMMO:
    mass = w->outfit->mass;
    w->lockon = outfit->u.amm.lockon;
    w->timer  = outfit->u.amm.duration;
    w->solid  = solid_create(mass, dir, pos, vel);
    w->voice  = sound_addVoice(VOICE_PRIORITY_AMMO,
                              w->solid->pos.x, w->solid->pos.y,
                              w->solid->vel.x, w->solid->vel.y, w->outfit->u.amm.sound, 0);

    /* If they are seeking a pilot, increment lockon counter. */
    pilot_target = pilot_get(target);
    if(pilot_target != NULL)
      pilot_target->lockons++;

    /* Only diff is AI. */
    if(outfit->type == OUTFIT_TYPE_MISSILE_SEEK_AMMO)
      w->think = think_seeker;
    else if(outfit->type == OUTFIT_TYPE_MISSILE_SEEK_SMART_AMMO)
      w->think = think_smart;
    break;
 
  /* Just dump it where the player is. */
  default:
    w->voice = NULL;
    w->solid = solid_create(mass, dir, pos, vel);
    break;
  }
  return w;
}

/* Add a new weapon. */
void weapon_add(const Outfit* outfit, const double dir, const Vec2* pos,
                const Vec2* vel, unsigned int parent, unsigned int target) {

  if(!outfit_isWeapon(outfit) &&
     !outfit_isAmmo(outfit) && !outfit_isTurret(outfit)) {
    ERR("Trying to create a weapon from a non-Weapon type Outfit");
    return;
  }

  WeaponLayer layer = (parent == PLAYER_ID) ? WEAPON_LAYER_FG : WEAPON_LAYER_BG;
  Weapon* w = weapon_create(outfit, dir, pos, vel, parent, target);

  /* Set the propper layer. */
  Weapon** curLayer = NULL;
  int* mLayer = NULL;
  int* nLayer = NULL;
  switch(layer) {
  case WEAPON_LAYER_BG:
    curLayer = wbackLayer;
    nLayer = &nwbackLayer;
    mLayer = &mwbackLayer;
    break;
  case WEAPON_LAYER_FG:
    curLayer = wfrontLayer;
    nLayer = &nwfrontLayer;
    mLayer = &mwfrontLayer;
    break;
  default:
    ERR("Invalid WEAPON_LAYER specified.");
    return;
  }
  if(*mLayer > *nLayer) /* More memory allocated than what we need. */
    curLayer[(*nLayer)++] = w;
  else { /* Need to allocate more memory. */
    switch(layer) {
    case WEAPON_LAYER_BG:
      (*mLayer) += WEAPON_CHUNK;
      curLayer = wbackLayer = realloc(curLayer, (*mLayer)*sizeof(Weapon*));
      break;
    case WEAPON_LAYER_FG:
      (*mLayer) += WEAPON_CHUNK;
      curLayer = wfrontLayer = realloc(curLayer, (*mLayer)*sizeof(Weapon*));
      break;
    }
    curLayer[(*nLayer)++] = w;
  }
}

/* Destroy the weapon. */
static void weapon_destroy(Weapon* w, WeaponLayer layer) {
  int i;
  Weapon** wlayer;
  int* nlayer;
  Pilot* pilot_target;

  /* Decrement target lockons if needed. */
  if(outfit_isSeeker(w->outfit)) {
    pilot_target = pilot_get(w->target);
    if(pilot_target != NULL)
      pilot_target->lockons--;
  }

  switch(layer) {
  case WEAPON_LAYER_BG:
    wlayer = wbackLayer;
    nlayer = &nwbackLayer;
    break;
  case WEAPON_LAYER_FG:
    wlayer = wfrontLayer;
    nlayer = &nwfrontLayer;
    break;
  }
  for(i = 0; wlayer[i] != w; i++); /* Get us to the current posision. */
  weapon_free(wlayer[i]);
  wlayer[i] = NULL;
  (*nlayer)--;

  for(; i < (*nlayer); i++)
    wlayer[i] = wlayer[i+1];
}

/* Clear the weapon. */
static void weapon_free(Weapon* w) {
  sound_delVoice(w->voice);
  solid_free(w->solid);
  free(w);
}

/* Clear all the weapons, do not free the layers. */
void weapon_clear(void) {
  int i;
  for(i = 0; i < nwbackLayer; i++)
    weapon_free(wbackLayer[i]);
  nwbackLayer = 0;
  for(i = 0; i < nwfrontLayer; i++)
    weapon_free(wfrontLayer[i]);
  nwfrontLayer = 0;
}

void weapon_exit(void) {
  weapon_clear();
  free(wbackLayer);
  free(wfrontLayer);
}

