/**********************************************************************
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 "objs/light_o.hh"
#include "saver.hh"
#include "objs/model_draw.hh"
#include "map.hh"
#include "math/num_type.hh"
#include "object_definer.hh"
#include "map_man.hh"
#include "flare.hh"
#include "time/profile.hh"
#include "map_vert.hh"

static i4_profile_class pf_light_occupy("light::occupy"), pf_light_unoccupy("light::unoccupy");

g1_object_type g1_lightbulb_type;
void g1_light_object_init();

g1_object_definer
g1_light_object_def("lightbulb", 
                    g1_object_definition_class::EDITOR_SELECTABLE,
                    g1_light_object_init);

void g1_light_object_init()
{
  g1_lightbulb_type = g1_light_object_def.type;
}


void g1_light_object_class::setup(float _x, float _y, float _h, 
                                  float _r, float _g, float _b, float _white,
                                  float min_light_contribute, 
                                  float linear_contribute, 
                                  float geometric_contribute)
{
  r=_r;
  g=_g;
  b=_b;
  x=lx=_x;
  y=ly=_y;
  h=lh=_h;

  int occ=get_flag(MAP_OCCUPIED);

  if (occ)
    unoccupy_location();

  if (add_intensities)
    i4_free(add_intensities);


  c1=min_light_contribute;
  c2=linear_contribute;
  c3=geometric_contribute;

  if (c3<0.0016)  // maximum radius is 25 squares for now
    c3=0.0016;

  change_radius=i4_f_to_i(sqrt((32-c1)/c3));
  
  sw32 w=change_radius*2+1,h=change_radius*2+1;

  add_intensities=(w32 *)i4_malloc(w*h*sizeof(w32),
                                   "light map restore");  
  
  if (occ)
    occupy_location();
  
}

g1_light_object_class::~g1_light_object_class()
{
  if (add_intensities)
    i4_free(add_intensities);
}

g1_light_object_class::g1_light_object_class(g1_object_type id,
                                             g1_loader_class *fp)
  : g1_object_class(id,fp)
{
  add_intensities=0;

  if (fp && fp->check_version(DATA_VERSION))
  {
    float _r, _g, _b, _white, _c1, _c2, _c3;
    fp->read_format("fffffff", &_r, &_g, &_b, &_white, &_c1, &_c2, &_c3);
    setup(x,y,h, _r, _g, _b, _white, _c1, _c2, _c3);

    fp->end_version(I4_LF);
  }
  else
  {
    r=g=b=1; 
    white=1;    
    c1=0.1;    c2=0.25;    c3=0.5;    
    h=2;
  }

  draw_params.setup("lightbulb");
}


void g1_light_object_class::save(g1_saver_class *fp)
{
  g1_object_class::save(fp);
  fp->start_version(DATA_VERSION);
  fp->write_format("fffffff", &r,&g,&b, &white, &c1,&c2,&c3);
  fp->end_version();
}

void g1_light_object_class::draw(g1_draw_context_class *context)
{  
  g1_editor_model_draw(this, draw_params, context);
}


void g1_light_object_class::move(float nx, float ny, float nh)
{
  unoccupy_location();
  lx=x; ly=y; lh=h;
  x=nx; y=ny; h=nh;

  occupy_location();
}


void g1_light_object_class::think()
{
}

i4_bool g1_light_object_class::occupy_location()
{
  if (!add_intensities)
  {
    i4_warning("call light::setup before occupy_location");
    
    return i4_F;
  }
  
  pf_light_occupy.start();
  if (g1_object_class::occupy_location())
  {

    
    sw32 ix=i4_f_to_i(x), iy=i4_f_to_i(y);
    w32 *a=add_intensities;

    for (int ty=-change_radius+iy; ty<=change_radius+iy; ty++)
    {
      sw32 start_x=ix-change_radius;
      if (start_x<0) start_x=0;
      g1_map_vertex_class *v=g1_get_map()->vertex(start_x,ty);

      for (int tx=-change_radius+ix; tx<=change_radius+ix; tx++, a++)
      {
        if (tx>=0 && ty>=0 && tx<=g1_get_map()->width() && ty<=g1_get_map()->height())
        {
          i4_3d_vector normal;
          i4_float tz;
          w32 old_rgb=v->dynamic_light;

          v->get_normal(normal, tx, ty);
          tz=v->get_height();

          i4_3d_vector dir=i4_3d_vector(x-tx,
                                        y-ty, 
                                        h-tz);

          i4_float dist=sqrt(dir.x*dir.x + dir.y*dir.y + dir.z*dir.z);
          i4_float odist=1.0/dist;

          dir.x*=odist;  // normalize the light direction vector
          dir.y*=odist;
          dir.z*=odist;

          i4_float ndl = normal.dot(dir);
          i4_float atten =  1.0/(c1 + c3*dist*dist);

          if (atten>1) atten=1;

          i4_float intensity=ndl*atten;
          if (intensity<0) intensity=0;

          sw32 ra,ga,ba, or,og,ob;

          ra=i4_f_to_i(intensity * r * 255);    // calculate how much to add to the current light
          ga=i4_f_to_i(intensity * g * 255);
          ba=i4_f_to_i(intensity * b * 255);

          or=(old_rgb>>16)&255;              // grab the old light values
          og=(old_rgb>>8)&255;
          ob=(old_rgb)&255;
      
          if (ra+or>255) ra=255-or;           // adjust for overflow
          if (ga+og>255) ga=255-og;
          if (ba+ob>255) ba=255-ob;

          *a=(ra<<16)|(ga<<8)|ba;           // store the added amount so we can subtract out later
        
          v->dynamic_light=((ra+or)<<16) | ((ga+og)<<8) | (ba+ob);
          v->light_sum|=0x80000000;
          v++;
        }
      }
    }
    pf_light_occupy.stop();
    return i4_T;
  }
  else
  {
    pf_light_occupy.stop();
    return i4_F;
  }
}

void g1_light_object_class::unoccupy_location()
{ 
  if (!add_intensities)
    return ;

  pf_light_unoccupy.start();
  g1_object_class::unoccupy_location();

  sw32 w=change_radius*2+1,h=change_radius*2+1;
  sw32 ix=i4_f_to_i(x), iy=i4_f_to_i(y);
  w32 *a=add_intensities;

  for (int ty=-change_radius+iy; ty<=change_radius+iy; ty++)
  {
    sw32 start_x=ix-change_radius;
    if (start_x<0) start_x=0;
    g1_map_vertex_class *v=g1_get_map()->vertex(start_x,ty);
    for (int tx=-change_radius+ix; tx<=change_radius+ix; tx++, a++)
    {
      if (tx>=0 && ty>=0 && tx<=g1_get_map()->width() && ty<=g1_get_map()->height())      
      {
        v->dynamic_light-=*a;
        v++;
        v->light_sum=0x80000000;
      }
    }
  }
  
  pf_light_unoccupy.stop();
}