/**********************************************************************
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) 
***********************************************************************/

#ifndef __MAP_HPP_
#define __MAP_HPP_


#include "arch.hh"
#include "error/error.hh"
#include "g1_limits.hh"
#include "player_type.hh"
#include "time/time.hh"
#include "g1_vert.hh"
#include "g1_object.hh"
#include "reference.hh"
#include "global_id.hh"

class g1_map_cell_class;
class g1_map_vertex_class;
class g1_draw_context_class;
class g1_obj_conscell_class;
class g1_solid_class;
class g1_bullet_class;
class g1_solid_class;
class g1_saver_class;
class g1_loader_class;
class g1_path_manager_class;
class g1_visible_projection;
class g1_quad_object_class;
class g1_movie_flow_class;
class g1_light_object_class;
class i4_str;
class i4_polygon_class;
struct g1_visible_cell;
class i4_pal_handle_class;
class g1_astar_map_solver_class;
class g1_takeover_pad_class;
//class g1_critical_graph_class;
typedef g1_typed_reference_class g1_takeover_pad_ref;

// these bit flags are passed into save & load for a map
// this can be used to merge in sections of another map or reduce the amount
// of undo info need to be saved and loaded
enum {
  G1_MAP_CELLS=(1<<0),
  G1_MAP_VERTS=(1<<1),
  G1_MAP_OBJECTS=(1<<2),
  G1_MAP_MOVIE=(1<<3),
  G1_MAP_PLAYERS=(1<<4),
  G1_MAP_TICK=(1<<5),
  G1_MAP_GRAPH=(1<<6),
  G1_MAP_LIGHTS=(1<<7),
  G1_MAP_SKY=(1<<8),
  G1_MAP_VARS=(1<<9),
  G1_MAP_VIEW_POSITIONS=(1<<11),
  G1_MAP_RES_FILENAME=(1<<12),
  G1_LAST_THING,
  G1_MAP_ALL=(G1_LAST_THING-1)*2-1,

  G1_MAP_SELECTED_VERTS=(1<<13)  // not part of G1_MAP_ALL because G1_MAP_VERTS encompases
};


enum {
  G1_RECALC_RADAR_VIEW         = (1<<0),
  G1_RECALC_PAD_LIST           = (1<<1),
//   G1_RECALC_BLOCK_MAPS         = (1<<2), 
//   G1_RECALC_CRITICAL_DATA      = (1<<3),
  G1_RECALC_WATER_VERTS        = (1<<4),  // determines which verts are drawn with wave motion
  G1_RECALC_STATIC_LIGHT       = (1<<5)  // determines how much directional light hits each cell
};


class g1_map_class
{
private:
  friend class g1_map_view_class;
  friend class g1_editor_class;

  friend i4_bool g1_load_level(const i4_const_str &filename,
                               int reload_textures_and_models,
                               w32 exclude_flags);

//  friend class g1_critical_map_maker_class;  
  friend class g1_object_controller_class;

  void save_objects(g1_saver_class *out);

  w32 recalc;       // bits telling what need recalculating set by add_undo in editor
  w32 w,h;

  g1_map_cell_class *cells;       // 2d array (w*h) of tile type, rotation, blocking status
  g1_map_vertex_class *verts;    // 2d array ((w+1)*(h+1)) of height and lighting info of corners

//   g1_block_map_class block[G1_GRADE_LEVELS];    // blockage maps for different slopes
//   g1_collision_map_class collide; // collision map for objects

//   g1_critical_graph_class *critical_graph;

  enum { THINK_QUE_SIZE=G1_MAX_OBJECTS };
  g1_object_class *think_que[THINK_QUE_SIZE];
  w32 think_head, think_tail;

  
  void delete_map_view();

  g1_object_class **load_objects(g1_loader_class *fp, w32 &tobjs);

  // returns sections actually loaded
  w32 load(g1_loader_class *fp, w32 sections);

  i4_bool load_sky(g1_loader_class *fp);
  void save_sky(g1_saver_class *fp);

//   void save_critical_map(g1_saver_class *f);
//   void load_critical_map(g1_loader_class *f);

  g1_astar_map_solver_class *solver;
  g1_path_manager_class *path_manager;
  

  // this is a hook so the level editor can draw selected verts
  typedef void (*cell_draw_function_type)(sw32 x, sw32 y, void *context);
  cell_draw_function_type post_cell_draw;
  void *post_cell_draw_context;

  i4_str *filename;

  void init_lod();
  void calc_map_lod(g1_object_controller_class *);

  i4_bool movie_in_progress;

public:
  // only use this if you know what you are doing
  void change_map(int w, int h, g1_map_cell_class *cells, g1_map_vertex_class *vertex);
  
  i4_str *sky_name;

  void recalc_static_stuff();

  void remove_from_think_list(g1_object_class *o);

  void mark_for_recalc(w32 flags) { recalc |= flags; }

//   g1_block_map_class *get_block_map(w8 grade) const { return (g1_block_map_class*)&block[grade]; }

  i4_bool playing_movie() { return movie_in_progress; }  

  i4_const_str get_filename();
  void set_filename(const i4_const_str &fname);
    

  void set_post_cell_draw_function(cell_draw_function_type fun, void *context) 
  { 
    post_cell_draw=fun; 
    post_cell_draw_context=context;
  }


  i4_bool start_movie();
  void stop_movie();
  i4_bool advance_movie_with_time();

  g1_movie_flow_class *current_movie;

  g1_movie_flow_class *get_current_movie() { return current_movie; }

  w32 get_tick();
  i4_time_class tick_time;  

  w16 width()  const { return w; }
  w16 height() const { return h; }

  class range_iterator
  {
  protected:
    sw32 left, right, top, bottom, ix, iy;
    g1_map_cell_class *cell;
    g1_object_chain_class *chain;
    w32 object_mask_flags, type_mask_flags;
  public:
    void begin(float x, float y, float range);
    void mask(w32 _object_mask_flags, w32 _type_mask_flags=0xffffffff)
    {
      object_mask_flags = _object_mask_flags;
      type_mask_flags = _type_mask_flags;
    }
    void safe_restart();
    i4_bool end();
    void next();

    g1_object_class *get() const;

    g1_object_class *operator*() { return get(); }
    range_iterator& operator++() { next(); return *this;}
    range_iterator& operator++(int) { next(); return *this;}
  };

  sw32 get_objects_in_range(float x, float y, float range, 
                            g1_object_class *dest_array[], w32 array_size,
                            w32 object_mask_flags=0xffffffff, w32 type_mask_flags=0xffffffff);
                            

  g1_map_cell_class *cell(w16 x, w16 y) const;
  g1_map_cell_class *cell(w32 offset) const;
  g1_map_vertex_class *vertex(w16 x, w16 y) const;

  void request_think(g1_object_class *obj)
  {
    think_que[think_head]=obj;

    think_head++;
    if (think_head>=THINK_QUE_SIZE)
      think_head=0;
    
    if (think_head==think_tail)
      i4_error("g1_map_class::request_think - thinkers exceeded maximum");
  }

  void request_remove(g1_object_class *obj);

  void add_object(g1_object_chain_class &c, w32 x, w32 y);
  void remove_object(g1_object_chain_class &c);


  void remove_object_type(g1_object_type type);

  g1_map_class(const i4_const_str &fname);

//   void make_block_maps();

  void draw(g1_draw_context_class *context,
            i4_float player_x, 
            i4_float player_y,
            i4_float player_z,
            i4_angle player_angle);

  void draw_cells(g1_draw_context_class  *context,
                  g1_visible_cell *cell_list,
                  int t_visible_cells);


  void fast_draw_cells(g1_draw_context_class  *context);
//                        g1_visible_cell *cell_list,
//                        int t_visible_cells);


  void think_objects();


  // check so see if an object can move to the position x,y blocking is not checked
  // against solids on the same team.
  // returns 1 if hit object, -1 if hit building, and 0 if nothing
  int check_non_player_collision(g1_player_type player_num,
                                 const i4_3d_vector &point,
                                 i4_3d_vector &ray,
                                 g1_object_class*& hit) const;

  int check_terrain_location(i4_float x, i4_float y, i4_float z,
                             i4_float occupancy_radius,
                             w8 grade, w8 dir) const;

  void save(g1_saver_class *out, w32 sections);

  g1_object_class *find_object_by_id(w32 object_id, g1_player_type prefered_team);

  // this is not stank specific!
  i4_bool find_path(i4_float start_x, i4_float start_y,
                    i4_float dest_x, i4_float dest_y,
                    i4_float *points, w16 &t_nodes); 

  // all objects in this area will receive damage falling off with distance
  void damage_range(g1_object_class *obj,
                    i4_float x, i4_float y, i4_float z, 
                    i4_float range, w16 damage, i4_float falloff=0);

//   g1_critical_graph_class *get_critical_graph() { return critical_graph; }

  i4_float terrain_height(i4_float x, i4_float y) const;
  i4_float map_height(i4_float x, i4_float y, i4_float z) const;

  void calc_terrain_normal(i4_float x, i4_float y, i4_3d_vector &normal);
  void calc_pitch_and_roll(i4_float x, i4_float y, i4_float z, i4_float &pitch, i4_float &roll);

  void calc_height_pitch_roll(i4_float x, i4_float y, i4_float z,
                              i4_float &height, i4_float &pitch, i4_float &roll);
                              

  sw32 make_object_list(g1_object_class **buffer, sw32 buf_size);  // returns total added
  sw32 make_selected_objects_list(w32 *buffer, sw32 buf_size);  // return total added (saved id's)

  void change_vert_height(sw32 x, sw32 y, w8 new_height);

  // how much light illuminates an object at this position
  void get_illumination_light(i4_float x, i4_float y, i4_float &r, i4_float &g, i4_float &b);

  void reload();

  // returns the total number of cells that can be seen
  int calc_visible(i4_transform_class &t,
                   i4_polygon_class *area_poly,
                   g1_visible_cell *buffer, w32 buf_elements,
                   i4_float xscale, i4_float yscale);           // how much to scale x & y members of vertexes


  i4_float min_terrain_height(w16 x, w16 y);

  ~g1_map_class();
} ;


#endif