/**********************************************************************
This file is part of Crack dot Com's free source code release of Golgotha.
for information about compiling & licensing issues visit this URL
 If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/

#include "sound_man.hh"
#include "objs/model_id.hh"
#include "objs/model_draw.hh"
#include "input.hh"
#include "math/pi.hh"
#include "math/trig.hh"
#include "math/angle.hh"
#include "objs/bullet.hh"
#include "resources.hh"
#include "saver.hh"
#include "map_cell.hh"
#include "map.hh"
#include "map_man.hh"
#include "objs/vehic_sounds.hh"
#include "sound/sfx_id.hh"
#include "objs/helicopter.hh"
#include "objs/fire.hh"
#include "object_definer.hh"
#include "objs/path_object.hh"
#include "camera.hh"

#include "image_man.hh"
static g1_team_icon_ref radar_im("bitmaps/radar/helicopter.tga");

static g1_model_ref model_ref("heli_body"),
  shadow_ref("heli_body_shadow"),
  blades_ref("heli_blades-alpha");

static i4_3d_vector blades_attach, blades_offset;

enum {DATA_VERSION=2};
enum helicopter_mode
{
  TAKE_OFF=0, 
  TAKE_OFF2, 
  FLYING, 
  DYING
};
  
const i4_float fly_height = 1.5;
const i4_float max_bladespeed = 0.75;

static g1_object_type missile;
void g1_helicopter_init()
{
  missile = g1_get_object_type("guided_missile");

  blades_attach.set(0,0,0);
  model_ref()->get_mount_point("Blades", blades_attach);
  blades_offset.set(0,0,0);
  blades_ref()->get_mount_point("Blades", blades_offset);
}

g1_object_definer
g1_helicopter_def("helicopter",
                  g1_object_definition_class::TO_MAP_PIECE |
                  g1_object_definition_class::EDITOR_SELECTABLE |
                  g1_object_definition_class::MOVABLE,
                  g1_helicopter_init);

g1_helicopter_class::g1_helicopter_class(g1_object_type id,
                                         g1_loader_class *fp)
  : g1_map_piece_class(id,fp)
{  
  radar_image=&radar_im;
  radar_type=G1_RADAR_VEHICLE;

  set_flag(BLOCKING      |
           TARGETABLE    |
           AERIAL        | 
           HIT_AERIAL    |
           HIT_GROUND    |
           DANGEROUS,
           1);

  draw_params.setup("heli_body","heli_body_shadow");  

  //allocate 1 mini object
  allocate_mini_objects(1,"Helicopter Mini-Objects");  
  blades = &mini_objects[0];
  
  //setup blades
  blades->defmodeltype = blades_ref.id();
  blades->position(blades_attach);
  blades->offset = blades_offset;
  bladespeed = 0;

  w16 ver,data_size;
  if (fp)
    fp->get_version(ver,data_size);
  else
    ver =0;

  switch (ver)
  {
    case DATA_VERSION:
      mode = 0;
      fp->read_format("1ffffff",
                      &mode,

                      &blades->rotation.x,&blades->rotation.x,&blades->rotation.x,
                      &bladespeed,
                      &vspeed,
                      &upaccel);
      grab_old();
      break;
    case 1:
      mode = 0;
      if (fp->read_8()) mode=TAKE_OFF;  // take_off
      if (fp->read_8()) mode=TAKE_OFF;  // taking_off
      if (fp->read_8()) mode=FLYING;    // flying
      fp->read_8();
      fp->read_8();

      blades->rotation.x = fp->read_float();
      blades->rotation.y = fp->read_float();
      blades->rotation.z = fp->read_float();
      fp->read_float();
      fp->read_float();
      fp->read_float();

      bladespeed = fp->read_float();

      vspeed  = fp->read_float();
      upaccel = fp->read_float();
      break;
    default:
      if (fp) fp->seek(fp->tell() + data_size);
      mode = TAKE_OFF;
      blades->rotation.x = blades->lrotation.x = 0;
      blades->rotation.y = blades->lrotation.y = 0;
      blades->rotation.z = blades->lrotation.z = 0;
      bladespeed = 0;
      vspeed = upaccel = 0;     
      break;
  }
  
  if (fp)
    fp->end_version(I4_LF);

  fire_delay = 0;

  blades->defmodeltype = g1_model_list_man.find_handle("heli_blades-alpha");
  blades->lod_model = blades->defmodeltype;

  draw_params.setup("heli_body","heli_body_shadow", "heli_body_lod");  


  init_rumble_sound(G1_RUMBLE_HELI);

  damping_fraction = 0.02;

  bladespeed=max_bladespeed;
}
   
static char *chunk_list[3]={"chunk_chopper_blade","chunk_chopper_body", "chunk_chopper_tail"};

int g1_helicopter_class::get_chunk_names(char **&list) { list=chunk_list; return 3; }


void g1_helicopter_class::save(g1_saver_class *fp)
{
  g1_map_piece_class::save(fp);

  fp->start_version(DATA_VERSION);
  
  fp->write_format("1ffffff",
                   &mode,
                   &blades->rotation.x,&blades->rotation.x,&blades->rotation.x,
                   &bladespeed,
                   &vspeed,
                   &upaccel);

  fp->end_version();
}

void g1_helicopter_class::fire()
{
  g1_object_class *target=attack_target.get();

  if (target)
  {    
    i4_transform_class btrans,tmp1;
    i4_3d_vector pos1, pos2, dir;
  
    btrans.translate(x,y,h);

    tmp1.rotate_x(groundroll);
    btrans.multiply(tmp1);

    tmp1.rotate_y(groundpitch);
    btrans.multiply(tmp1);

    tmp1.rotate_z(theta);
    btrans.multiply(tmp1);

    tmp1.rotate_y(pitch);
    btrans.multiply(tmp1);

    tmp1.rotate_x(roll);
    btrans.multiply(tmp1);

    btrans.transform(i4_3d_vector(0.2, -0.11, -0.02), pos1);
    btrans.transform(i4_3d_vector(0.2,  0.11, -0.02), pos2);
    btrans.transform(i4_3d_vector(0.4, -0.11, -0.02), dir);
    dir-=pos1;
    

    g1_fire(defaults->fire_type,  this, attack_target.get(), pos1, dir);
    g1_fire(defaults->fire_type,  this, attack_target.get(), pos2, dir);

    fire_delay = defaults->fire_delay;
  }
}

i4_bool g1_helicopter_class::move(i4_float x_amount, i4_float y_amount)
{
  
  unoccupy_location();
  x+=x_amount;
  y+=y_amount;

  g1_add_to_sound_average(rumble_type, i4_3d_vector(x,y,h));
  if (occupy_location())
    return i4_T;

  return i4_F;
}


void g1_helicopter_class::think()
{  
  if (!check_life(i4_F))      
    mode = DYING;


  //physics
  blades->rotation.z -= bladespeed;
  h                  += vspeed;

  if (fire_delay>0) fire_delay--;


  switch (mode)
  {
    case TAKE_OFF:
    {
      g1_camera_event cev;
      cev.type=G1_WATCH_EVENT;
      cev.follow_object=this;
      g1_current_view_state()->suggest_camera_event(cev);
      mode=TAKE_OFF2;
    } break;
    
    case TAKE_OFF2:
    {
      if (next_path.valid())
      {
        dest_x = next_path->x - path_cos*path_len;
        dest_y = next_path->y - path_sin*path_len;
        dest_z = next_path->h - path_tan_phi*path_len;
      }

      //then raise the helicopter
      i4_float dist_to_go = dest_z - h;
      if (dist_to_go>0.05) 
      {
        //if he is more than halfway away, accelerate down
        //otherwise accelerate up        
        if (dist_to_go > (fly_height * 0.5)) 
          vspeed = (vspeed<0.05) ? vspeed+0.005 : vspeed;
      } 
      else 
      {
        //lock these there were any small errors
        h = dest_z;
        vspeed = 0;
        upaccel = 0;      
        
        //think one more time so the l* variables catch up
        //(otherwise he'll bob up and down)
        if (h == lh)
          mode = FLYING;
      }
    } 
    
    case FLYING:
    {
      find_target();

      if (next_path.valid())
      {
        dest_x = next_path->x;
        dest_y = next_path->y;
        dest_z = next_path->h;
      }
      
      i4_float dist, dtheta;
      i4_3d_vector d;
      suggest_air_move(dist, dtheta, d);
      
      move(d.x,d.y);
      h += d.z;

      if (distturn_speed/4);  
      else
        i4_rotate_to(roll,0,defaults->turn_speed/2);
      
      if (attack_target.valid() && !fire_delay)
        fire();
    
      groundpitch = 0; //no ground when in the air (duh)
      groundroll  = 0;
    } break;

    case DYING:
    {
      i4_float &roll_speed  = dest_x;
      i4_float &theta_speed = dest_y;
      
      theta_speed += 0.02;
      theta += theta_speed;

      roll_speed += 0.02;
      roll += roll_speed;

      vspeed -= (g1_resources.gravity * 0.15);

      if (h<=terrain_height)
        g1_map_piece_class::damage(0,health,i4_3d_vector(0,0,1));               // die somehow!!!
      
    } break;
  }

  request_think();  
}

void g1_helicopter_class::damage(g1_object_class *obj, int hp, i4_3d_vector _damage_dir)
{
  //we dont want to explode if ppl shoot us while we're dying.. we want to
  //smash into the ground and create a nice explosion
  if (mode != DYING)
  {
    g1_map_piece_class::damage(obj,hp,_damage_dir);  
    if (health<20)
    {
      i4_float &roll_speed  = dest_x;
      i4_float &theta_speed = dest_y;
      
      roll_speed  = 0;
      theta_speed = 0;
      health      = 20;
      set_flag(DANGEROUS,0);
      mode = DYING;
    }
  }
}