/**********************************************************************
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 "player.hh"
#include "error/error.hh"
#include "g1_limits.hh"
#include "image/color.hh"
#include "map.hh"
#include "window/win_evt.hh"
#include "device/kernel.hh"
#include "objs/model_id.hh"
#include "image/image.hh"
#include "loaders/load.hh"
#include "init/init.hh"
#include "resources.hh"
#include "g1_object.hh"
#include "objs/map_piece.hh"
#include "sound_man.hh"
#include "tile.hh"
#include "resources.hh"
#include "remove_man.hh"
#include "math/pi.hh"
#include "g1_speed.hh"
#include "light.hh"
#include "m_flow.hh"
#include "statistics.hh"
#include "input.hh"
#include "time/profile.hh"
#include "g1_render.hh"
#include "height_info.hh"
#include "checksum/checksum.hh"
#include "solvemap_astar.hh"
#include "cwin_man.hh"
#include "map_light.hh"
#include "map_man.hh"
#include "lisp/li_class.hh"
#include "objs/path_object.hh"
#include "objs/vehic_sounds.hh"
#include "map_cell.hh"
#include "map_vert.hh"
#include "tick_count.hh"
#include "map_view.hh"
#include "map_data.hh"

g1_height_info::g1_height_info()
{ 
  floor_height   = 0.f;
  ceiling_height = 1.f;
  flags = BELOW_GROUND;  
}


i4_profile_class pf_map_think("map.cc think objects");
i4_profile_class pf_map_post_think("map.cc post_think objects");
i4_profile_class pf_notify_target_position_change("notify_target_pos_change");

g1_statistics_counter_class g1_stat_counter;

w32 g1_map_class::get_tick()
{
  return g1_tick_counter;
}
    
g1_map_cell_class *g1_map_class::cell(w16 x, w16 y) const { return cells + y*w + x; }
g1_map_cell_class *g1_map_class::cell(w32 offset) const  { return cells + offset; }
g1_map_vertex_class *g1_map_class::vertex(w16 x, w16 y) const { return verts + y*(w+1) + x; }


void g1_map_class::add_object(g1_object_chain_class &c, w32 x, w32 y)
{
  cells[c.offset = y*w+x].add_object(c);
}

void g1_map_class::remove_object(g1_object_chain_class &c)
{ 
  cells[c.offset].remove_object(c);
}

i4_float g1_map_class::min_terrain_height(w16 x, w16 y)
{
  g1_map_vertex_class *v1,*v2,*v3,*v4;
    
  v1 = verts+ x + y * (w+1);
  v2 = v1+1;
  v3 = v1+w+1;
  v4 = v3+1;
    
  return g1_vertex_min(v1, g1_vertex_min(v2, g1_vertex_min(v3, v4)))->get_height();
}

// only use this if you know what you are doing
void g1_map_class::change_map(int _w, int _h,
                              g1_map_cell_class *_cells, g1_map_vertex_class *_vertex)
{
  if (cells)
    i4_free(cells);
  
  if (verts)
    i4_free(verts);

  cells=_cells;
  verts=_vertex;
  w=_w;
  h=_h;
}


void g1_map_class::remove_object_type(g1_object_type type)
{
  int i,j,k;
  g1_map_cell_class *c=cell(0,0);
  g1_object_chain_class *cell, *l;
  g1_object_class *obj;

  for (j=0; jget_obj_list(); cell;)
      {
        l=cell;
        cell=cell->next;
        obj=l->object;
        if (obj->id==type)
        {          
          obj->unoccupy_location();
          obj->request_remove();
        }
      }
    }
}


g1_map_class::g1_map_class(const i4_const_str &fname)
{
  recalc=0xffffffff;     // recalculate everything until further notice

  sky_name=0;

//   lod=0;
  //  lod=(g1_map_lod_cell *) i4_malloc(sizeof(g1_map_lod_cell) * MAX_MAP_LOD, "lod");

  filename=0;
  set_filename(fname);

  post_cell_draw=0;

  w=0;
  h=0;


  think_head=think_tail=0;

  current_movie=0;
  
  cells=0;
  verts=0;

  //  map=(g1_map_cell_class *)i4_malloc(w*h*sizeof(g1_map_cell_class),"map");
  //  vert=(g1_map_vertex_class *)i4_malloc((w+1)*(h+1)*sizeof(g1_map_vertex_class),"map vert");

  w32 x,count=w*h,y;
  
  solver = new g1_astar_map_solver_class;
//   critical_graph=0;
  movie_in_progress=i4_F;
}

g1_map_class::~g1_map_class()
{
  g1_stop_sound_averages();

  g1_map_class *old_current=g1_current_map_PRIVATE;
  g1_set_map(this);


  
  for (g1_map_data_class *md=g1_map_data_class::first; md; md=md->next)
    md->free();


  if (filename)
    delete filename;

  if (sky_name)
    delete sky_name;

  if (cells)
  {
    g1_object_class *olist[G1_MAX_OBJECTS];
    w32 ids[G1_MAX_OBJECTS];

    sw32 t=make_object_list(olist, G1_MAX_OBJECTS), i;

    // can't use object pointers directly because one
    // object might delete another
    for (i=0; iglobal_id;
      

    for (i=0; iunoccupy_location();
        o->request_remove();
        g1_remove_man.process_requests();        
      }
    }
    i4_free(cells);
  }
    

  if (verts)
    i4_free(verts);

  if (current_movie)
    delete current_movie;
  
  if (solver)
    delete solver;
  


  if (old_current==this)
    g1_set_map(0);
  else
    g1_set_map(old_current);

}

void check_olist()
{
  g1_object_class *olist[G1_MAX_OBJECTS];
  sw32 t=g1_get_map()->make_object_list(olist, G1_MAX_OBJECTS);
}

// returns total added
sw32 g1_map_class::make_object_list(g1_object_class **buffer, sw32 buf_size)  
{
  int x=width()*height(), i;
  sw32 t=0;
  g1_map_cell_class *c=cells;
  if (buf_size==0) return 0;


  for (i=0; iobject_list)
    {
      for (g1_object_chain_class *obj=c->get_obj_list(); obj; obj=obj->next)
      {
        g1_object_class *o=obj->object;
        if (!o->get_flag(g1_object_class::SCRATCH_BIT))   /// make sure object only added once
        {
          o->set_flag(g1_object_class::SCRATCH_BIT, 1);
          buffer[t++]=o;
          if (t==buf_size) 
          {
            i=x;
            obj=0;
          }
        }
      }
    }

  for (i=0; iset_flag(g1_object_class::SCRATCH_BIT, 0);

  return t;
}

sw32 g1_map_class::make_selected_objects_list(w32 *buffer, sw32 buf_size)
{
  int x=width()*height(), i;
  sw32 t=0;
  g1_map_cell_class *c=cells;
  if (buf_size==0) return 0;


  for (i=0; iobject_list)
    {
      for (g1_object_chain_class *obj=c->get_obj_list(); obj; obj=obj->next)
      {
        g1_object_class *o=obj->object;

        // make sure object only added once
        if (o->selected() && !o->get_flag(g1_object_class::SCRATCH_BIT))   
        {
          o->set_flag(g1_object_class::SCRATCH_BIT, 1);
          buffer[t++]=o->global_id;
          if (t==buf_size) 
          {
            i=x;
            obj=0;
          }
        }
      }
    }

  for (i=0; iset_flag(g1_object_class::SCRATCH_BIT, 0);

  return t;
}

void g1_map_class::think_objects()
{
  li_class *old_this=li_this;

  recalc_static_stuff();


  pf_map_think.start();
  g1_input.que_keys(tick_time);


  w32 i,h;
  
  w32 start_tail = think_tail;
  w32 start_head = think_head;
  
  //do the think()'s  
  i = start_tail;
  h = start_head;

  g1_reset_sound_averages();

  pf_map_think.start();

  for (; i!=h; )
  {
    g1_object_class *o=think_que[i];

    //always check to make sure the pointer
    //is good. objects might have removed themselves
    //from the map while still in the que, and left
    //a null pointer in their place
    if (o)
    {
      li_this=o->vars;
      o->grab_old();
    }

    i++;
    if (i>=THINK_QUE_SIZE)
      i=0;
  }

  i = start_tail;
  h = start_head;
  for (; i!=h; )
  {
    g1_object_class *o=think_que[i];

    //always check to make sure the pointer
    //is good. objects might have removed themselves
    //from the map while still in the que, and left
    //a null pointer in their place
    if (o)
    {
      li_this=o->vars;
      o->set_flag(g1_object_class::THINKING, 0);
      o->think();
    }

    i++;
    if (i>=THINK_QUE_SIZE)
      i=0;
  }

  pf_map_think.stop();


  pf_map_post_think.start();
  
  //do the post_think()'s
  i = start_tail;
  h = start_head;

  for (; i!=h; )
  {
    g1_object_class *o=think_que[i];    

    think_tail++;
    if (think_tail>=THINK_QUE_SIZE)
      think_tail=0;

    //always check to make sure the pointer
    //is good. objects might have removed themselves
    //from the map while still in the que, and left
    //a null pointer in their place
    if (o)
    {
      li_this=o->vars;
      o->post_think();
    }

    i++;
    if (i>=THINK_QUE_SIZE)
      i=0;
  }
  pf_map_post_think.stop();

  g1_recalc_sound_averages();


  g1_tick_counter++;
  tick_time.add_milli((1000/G1_HZ));


  li_this=old_this;
  pf_map_think.stop();
  
}


void g1_map_class::damage_range(g1_object_class *obj,
                                i4_float x, i4_float y, i4_float z, 
                                i4_float range, w16 damage, i4_float falloff)
// damage to vehicles centers at x,y,z and falls off by falloff*damage at range distance
//   from the center of the damage range
{
  sw32 ix,iy, 
    sx = sw32(x-range),
    ex = sw32(x+range)+1,
    sy = sw32(y-range),
    ey = sw32(y+range)+1;

  // clip region
  if (sx<0) sx=0;
  if (ex>width()) ex=width();
  if (sy<0) sy=0;
  if (ey>height()) ey=height();

  // get first one
  g1_map_cell_class *c;
  g1_object_chain_class *objlist;
  i4_float dist,dx,dy,dz;

  range *= range;
  falloff *= i4_float(damage)/range;

  for (iy=sy; iyget_obj_list(); objlist; objlist=objlist->next)
      {
        g1_object_class *hurt_obj=objlist->object;

        if (objlist==&hurt_obj->occupied_squares[0]) // make sure object is only hurt once
        {
          if (hurt_obj->get_flag(g1_object_class::TARGETABLE))
// && hurt_obj->player_num!=obj->player_num)
          {
            dx=x-hurt_obj->x;
            dy=y-hurt_obj->y;
            dz=z-hurt_obj->h;
            dist = dx*dx+dy*dy+dz*dz;
            if (distdamage(obj, damage - (int)(falloff*range), i4_3d_vector (0,0,g1_resources.gravity));
          }
        }
      }
    }
  }
}



g1_object_class *g1_map_class::find_object_by_id(w32 object_id,
                                                 g1_player_type prefered_team)
{
  sw32 i,j=width()*height();
  g1_map_cell_class *c=cells;
  g1_object_chain_class *o;
  g1_object_class *best=0;

  for (i=0; iget_obj_list(); o; o=o->next)
    {
      if (o->object->id==object_id)
      {
        if (o->object->player_num==prefered_team)              
          return o->object;
        else
          best=o->object;
      }
    }
  }
  return best;
}


void g1_map_class::remove_from_think_list(g1_object_class *obj)
{
  w32 i = think_tail,
      h = think_head;
  
  for (; i!=h;)
  {
    if (think_que[i]==obj) think_que[i]=0;
      
    i++;
    if (i>=THINK_QUE_SIZE) 
      i=0;
  }
}

void g1_map_class::request_remove(g1_object_class *obj)
{
  obj->flags |= g1_object_class::DELETED;

  if (obj->flags & g1_object_class::THINKING)
  {
    obj->stop_thinking();
  }
  
  g1_remove_man.request_remove(obj);
}


i4_const_str g1_map_class::get_filename()
{
  return *filename;
}

void g1_map_class::set_filename(const i4_const_str &fname)
{
  if (filename)
    delete filename;
  filename=new i4_str(fname);
}




void g1_map_class::recalc_static_stuff()
{
  i4_bool reset_time=i4_F;

//   if (recalc & (G1_RECALC_BLOCK_MAPS | G1_RECALC_CRITICAL_DATA))
//     li_call("add_undo", li_make_list(new li_int(G1_MAP_CRITICAL_DATA), 0));
    
  if (recalc & G1_RECALC_STATIC_LIGHT)
    g1_calc_static_lighting();               // defined in light.cc


//   if (recalc & G1_RECALC_BLOCK_MAPS)    
//   {
//     make_block_maps();
//     reset_time=i4_T;
//   }

  if (recalc & G1_RECALC_RADAR_VIEW)
  {
//     init_lod();   //(OLI) need to put this in the proper place

    g1_radar_recalculate_backgrounds();
    reset_time=i4_T;
  }

  if (recalc & (G1_RECALC_WATER_VERTS))
  {
    g1_map_vertex_class *v=verts+(w+1)+1;
    g1_map_cell_class *c=cells+(w)+1;

    for (int y=1; yflags & g1_tile_class::WAVE) &&
            (g1_tile_man.get(c[-1].type)->flags & g1_tile_class::WAVE) &&
            (g1_tile_man.get(c[-w].type)->flags & g1_tile_class::WAVE) &&
            (g1_tile_man.get(c[-w-1].type)->flags & g1_tile_class::WAVE))
          v->flags |= g1_map_vertex_class::APPLY_WAVE_FUNCTION;
      }
      v+=2;
      c+=2;
      v++;      
    }

    reset_time=i4_T;
  }
  
  recalc=0;

  if (reset_time)
    tick_time.get();     // don't simulate ticks for the calculation stuff
}


void g1_map_class::range_iterator::begin(float x, float y, float range)
//{{{
{
  g1_map_class *map = g1_get_map();
  int wx = map->width(), wy = map->height();

  left   = i4_f_to_i(x - range); if (left<0)      left=0;
  right  = i4_f_to_i(x + range); if (right>wx-1)  right=wx-1;
  top    = i4_f_to_i(y - range); if (top<0)       top=0;
  bottom = i4_f_to_i(y + range); if (bottom>wy-1) bottom=wy-1;

  ix = right;
  iy = top-1;
  cell = 0;
  chain = 0;

  object_mask_flags=0xffffffff;
  type_mask_flags=0xffffffff;
}
//}}}

void g1_map_class::range_iterator::safe_restart()
//{{{
{
  chain=0;
}
//}}}

i4_bool g1_map_class::range_iterator::end()
//{{{
{
  return (iy>=bottom);
}
//}}}

void g1_map_class::range_iterator::next()
//{{{
{
  return;
  /*
  g1_object_class *o;

  if (chain) chain = chain->next;

  while (iyget_obj_list();
      else
      {
        ix = left;
        iy++;
        cell = &g1_get_map()->cell(left, iy);
      }
    }
    else
    {
      o = chain->object;
      if (&o->occupied_squares[0]!=chain ||
          (o->flags & object_mask_flags)==0 ||
          (g1_object_type_array[o->id]->flags & type_mask_flags)==0)
        chain = chain->next;
      else
        return;
    }
  }
  */
}
//}}}

sw32 g1_map_class::get_objects_in_range(float x, float y, float range,
                                        g1_object_class *dest_array[], w32 array_size,
                                        w32 object_mask_flags, w32 type_mask_flags)
{
  sw32 x_left,x_right,y_top,y_bottom;
  
  x_left   = i4_f_to_i(x - range); if (x_left<0)     x_left=0;
  x_right  = i4_f_to_i(x + range); if (x_right>w-1)  x_right=w-1;
  y_top    = i4_f_to_i(y - range); if (y_top<0)      y_top=0;
  y_bottom = i4_f_to_i(y + range); if (y_bottom>h-1) y_bottom=h-1;
  
  sw32 ix,iy;  
  sw32 num_found=0;
  
  for (iy=y_top;  iy<=y_bottom; iy++)
  {
    g1_map_cell_class *cell = g1_get_map()->cell(x_left, iy);

    for (ix=x_left; ix<=x_right;  ix++, cell++)
    {      
      g1_object_chain_class *p     = cell->get_solid_list();

      while (p && num_foundobject;

        if (o && (&o->occupied_squares[0]==p) && (o->flags & object_mask_flags))
        {
          if (g1_object_type_array[o->id]->flags & type_mask_flags)
          {
            dest_array[num_found] = p->object;
            num_found++;
          }
        }
      
        p = p->next_solid();
      }
    
      if (num_found >= array_size)
        break;
    }
    
    if (num_found >= array_size)
        break;
  }
  
  return num_found;
}