/**********************************************************************
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 "app/app.hh"
#include "main/main.hh"
#include "window/window.hh"
#include "image/image.hh"
#include "palette/pal.hh"
#include "math/transform.hh"
#include "window/wmanager.hh"
#include "memory/malloc.hh"
#include "device/event.hh"
#include "gui/li_pull_menu.hh"
#include "menu/pull.hh"
#include "r1_api.hh"
#include "r1_win.hh"
#include "threads/threads.hh"
#include "device/keys.hh"
#include "window/win_evt.hh"
#include "r1_clip.hh"
#include "loaders/jpg_write.hh"
#include "gui/button.hh"
#include "gui/text.hh"

r1_render_api_class *api=0;

inline float frand() { return ((rand()%5000)-2500.0); }

enum {
  SAVE
};


struct camera_info
{
  i4_3d_vector position;
  i4_3d_vector rotation;

  void calc_transform(i4_transform_class &t)
  {
    t.identity();
    t.mult_rotate_x(rotation.x);
    t.mult_rotate_y(rotation.y);
    t.mult_rotate_z(rotation.z);
    t.mult_translate(position.x, position.y, position.z);

  }


  camera_info()
  {
    position=i4_3d_vector(0,0,1.5);
    rotation=i4_3d_vector(0, 0, 0);
  }
    
};

  
  


void render_thread_start(void *context);

#define S 2.2
enum {T_C=1 };

i4_3d_vector circles[T_C]={ i4_3d_vector(0,0,0)
                            //                            i4_3d_vector(-S,0,0)
//                             i4_3d_vector(S,0,0)


};
                            



float ray_sphere_intersect(i4_3d_vector ray0,   
                           i4_3d_vector ray_dir,
                           i4_3d_vector center,
                           float r)
{
  float i=ray_dir.x, j=ray_dir.y, k=ray_dir.z;
  float l=center.x, m=center.y, n=center.z;

  float a=i*i + j*j + k*k;

  float b=2*(i*(ray0.x - l) + j*(ray0.y -  m) + k*(ray0.z-n));

//   float c=l*l + m*m + n*n + ray0.x*ray0.x + ray0.y*ray0.y + ray0.z*ray0.z -
//           2*(l*ray0.x + m*ray0.y + n*ray0.z) - r*r;

  float c=(ray0.x-l)*(ray0.x-l) + (ray0.y-m)*(ray0.y-m) + (ray0.z-n)*(ray0.z-n) - r*r;



  float descriminant = b*b - 4*a*c;
  if (descriminant<0)
    return -1;
  
  if (descriminant==0)
    return -b/(2*a);
  else
  {
    float sd=sqrt(descriminant);
    float oo_2a=1.0/(2*a);
    float t1=(-b + sd ) * oo_2a;
    float t2=(-b - sd ) * oo_2a;
    if (t1>0)
    {
      if (t11) x=1;
  x*=nspans;

 int span=(int)x;
 if (span>=nknots-3)
   span=nknots-3;

 x-=span;
 knot+=span;

 float c3 = cr[0]*knot[0]  + cr[1]*knot[1]  + cr[2]*knot[2]  + cr[3]*knot[3];
 float c2 = cr[4]*knot[0]  + cr[5]*knot[1]  + cr[6]*knot[2]  + cr[7]*knot[3];
 float c1 = cr[8]*knot[0]  + cr[9]*knot[1]  + cr[10]*knot[2] + cr[11]*knot[3];
 float c0 = cr[12]*knot[0] + cr[13]*knot[1] + cr[14]*knot[2] + cr[15]*knot[3];

 return ((c3*x + c2)*x + c1)*x + c0;        
}


struct path_info
{
  i4_3d_vector start,
    pos,  // location in space where vector hit object 
    normal,          // normal at surface
    bump_normal,
    a,b,             // tangent vectors
    color;
};

class trace_view;
trace_view *trace=0;

class trace_view : public i4_window_class
{
public:
  i4_transform_class t;
  camera_info v;

  int iw,ih;
  i4_3d_vector light;
  int mouse_x, mouse_y;
  float theta;
  r1_render_window_class *rwin_parent;
  int rendering, abort_render, reset_render;
  i4_critical_section_class reset_lock;
  i4_bool mouse_look_grab;
  path_info path[10];
  int path_len;

  r1_render_window_class *rwin() { return rwin_parent; }

  ~trace_view()
  {
    if (rendering)
    {
      abort_render=1;
      while (rendering)
        i4_thread_yield();
    }
  }

  struct noise_struct
  {
    enum {TSIZE=256};
    float data[TSIZE];
    int init;
    noise_struct() { init=0; }

    float gnoise(int x, int y, int z)
    {
      return data[(x+(y*12345+((z*92831243)&(TSIZE-1))&(TSIZE-1))&(TSIZE-1))];
    }

    float bilinear_interp(float p1, float p2, float p3, float p4, float xr, float yr)
    {
      float t1=(p2-p1)*xr+p1;
      float t2=(p4-p3)*xr+p3;
      return (t2-t1)*yr+t1;
    }

    void init_noise()
    {
      if (!init)
      {
        init=1;
        for (int i=0; i1) r=1;

    return r;
  }

  float turbulance(i4_3d_vector v, int octaves=4)
  {
    float r=0, f=1.0;
    for (int t=0; t1) r=1;

    return r;
  }

  void planet_color(float x, i4_3d_vector &c)
  {
    float b[5]={0.1, 0,    0.06,    0.0 };
    float g[5]={0.3, 0.2,   0.2,    0.6 };
    float r[5]={0.5, 0.8,   0.4,    0.7 };

    c.x=spline(x, 4,r);      c.y=spline(x, 4,g);    c.z=spline(x, 4,b);
  }




  void marble_color(float x, i4_3d_vector &c)
  {
    float b[5]={0.2,   0.12, 0.06,    0.10 };
    float g[5]={0.3,   0.20, 0.05,    0.31 };
    float r[5]={0.5,   0.29, 0.12,    0.11 };


//     x=1.0-x*x;  kiwi color
//     float b[5]={0.5,   0.3,  0.15,   0.1 };
//     float g[5]={0.5,   0.4,  0.2,   0.1 };
//     float r[5]={0.5,   0.3,  0.2,   0.5 };


    c.x=spline(x, 4,r)*2;  
    c.y=spline(x, 4,g)*2;
    c.z=spline(x, 4,b)*2;

  }


                   
  void shade_point(i4_3d_vector eye, path_info &p)
  {
    i4_3d_vector bump_pos=p.pos, bpa, bpb;
    bump_pos.x+=100;     bump_pos.y+=100;     bump_pos.z+=100;
    //    bump_pos*=4;
    

    bpa.x=bump_pos.x+p.a.x*0.0001;
    bpa.y=bump_pos.y+p.a.y*0.0001;
    bpa.z=bump_pos.z+p.a.z*0.0001;

    bpb.x=bump_pos.x+p.b.x*0.0001;
    bpb.y=bump_pos.y+p.b.y*0.0001;
    bpb.z=bump_pos.z+p.b.z*0.0001;

    float bump_h=turbulance(bump_pos,4); 
    float bump_dx=(turbulance(bpa,4)-bump_h)*2.0;
    float bump_dy=(turbulance(bpb,4)-bump_h)*2.0;

    p.bump_normal.x=p.normal.x + bpa.x*bump_dx + bpb.x*bump_dy;
    p.bump_normal.y=p.normal.y + bpa.y*bump_dx + bpb.y*bump_dy;
    p.bump_normal.z=p.normal.z + bpa.z*bump_dx + bpb.z*bump_dy;

    p.bump_normal.normalize();
    
       
    
    



    float c1=-p.bump_normal.dot(light);
    i4_3d_vector reflect=i4_3d_vector(light.x + 2*p.bump_normal.x*c1,
                                      light.y + 2*p.bump_normal.y*c1,
                                      light.z + 2*p.bump_normal.z*c1);
                                          


    float light_v=-light.dot(p.bump_normal)*0.4;

    i4_3d_vector view_dir=p.pos;
    view_dir-=eye;
    view_dir.normalize();
    float view_dot_r=-view_dir.dot(reflect);
    if (view_dot_r>=0)
    {
      light_v += pow(view_dot_r, 4)*0.5;
    }
    
    light_v+=0.5;  // ambient

    i4_3d_vector c=p.pos;
    c.x+=100; c.y+=100; c.z+=100;
    c*=4.0;
    float v=turbulance(c);
    //    v=((sin(c.x)+sin(c.y)+sin(c.z))*v/3.0);


    //    p.color=i4_3d_vector(v,v,v);

    /*
    i4_3d_vector c=p.pos;
    c.x+=10;
    c.y+=10;
    c.z+=10;

    c*=20.0;
    float v;


    float dist=c.length();
    float q1=cos(c.x+c.y+c.z)*2.0;
    float q2=sin(c.x-c.y-c.z)*2.0;
    float q3=sin(dist*4);


    v=turbulance(i4_3d_vector(c.x,c.y,c.z));

    v=((sin(c.x)*0.1 + q3*0.9)*0.5+0.5)*v;

      
    
    if (v>1 || v<0)
      i4_error("bad v");

    marble_color(v, p.color);
*/

    planet_color(v, p.color);

    p.color*=light_v;


    if (p.color.x<0) p.color.x=0;   
    if (p.color.x>1) p.color.x=1;
    if (p.color.y<0) p.color.y=0;   
    if (p.color.y>1) p.color.y=1;
    if (p.color.z<0) p.color.z=0;   
    if (p.color.z>1) p.color.z=1;
  }


  void calc_transform()
  {
    v.calc_transform(t);
    //    t.inverse_transform(i4_3d_vector(0,0,0), eye);
    request_redraw();
    render();
    

  }

  void move_cam(float x, float y, float z)
  {
    i4_3d_vector i,j,k;
    i4_3d_vector c;
    t.inverse_transform(i4_3d_vector(0,0,0), c);
    t.inverse_transform(i4_3d_vector(1,0,0), i); i-=c; i*=x;
    t.inverse_transform(i4_3d_vector(0,1,0), j); j-=c; j*=y;
    t.inverse_transform(i4_3d_vector(0,0,1), k); k-=c; k*=z;
   
    v.position-=i;
    v.position-=j;
    v.position-=k;

    calc_transform();
  }

  void rotate_cam(float x, float y, float z)
  {
    v.rotation.x+=x;
    v.rotation.y+=y;
    v.rotation.z+=z;
    calc_transform();
  }


  
  void trace_ray(i4_3d_vector eye, i4_3d_vector r0, i4_3d_vector ray_dir, 
                 int depth, 
                 float &z, 
                 path_info &cpath,
                 i4_bool store_path=i4_F)
  {
    if (depth>5)      
      cpath.color=i4_3d_vector(0,0,0);

    float closest_dist=10000;
    int closest=-1;
    
    for (int i=0; i0.001 && time1) cpath.color.x=1;
      if (cpath.color.y<0) cpath.color.y=0;   
      if (cpath.color.y>1) cpath.color.y=1;
      if (cpath.color.z<0) cpath.color.z=0;   
      if (cpath.color.z>1) cpath.color.z=1;
    }
    else
      cpath.color=i4_3d_vector(0,0,0);
  }

  void thread_render()
  {    
    reset_lock.lock();
    i4_set_thread_priority(i4_get_thread_id(), I4_THREAD_PRIORITY_LOW);

    do
    {
      reset_render=0;
      reset_lock.unlock();

      api->clear_area(0,0, width()-1, height()-1, 0x4f4f4f, 1000);
   
      srand(0);
    

      i4_3d_vector center;
      i4_3d_vector xi, yi;
      i4_3d_vector ray, eye;
      

      t.inverse_transform(i4_3d_vector(0,0,0), eye);
      t.inverse_transform(i4_3d_vector(0,0,1), center);
      t.inverse_transform(i4_3d_vector(1,0,1), xi);  xi-=center;
      t.inverse_transform(i4_3d_vector(0,1,1), yi);  yi-=center;
      
      float xm=width()/2.0;
      float ym=height()/2.0;
        


      for (int y=0; yrender_pixel(&v);

        }

        if (abort_render || reset_render)
          break;
      }


       if (!reset_render && !abort_render)
       {
         for (int i=0; iend_render();
    rendering=0;
    request_redraw();
    reset_lock.unlock();
  }

  void render()
  {
    reset_lock.lock();
    
    if (rendering)
      reset_render=1;
    else
    {
      rwin()->begin_render();      
      rendering=1;       
      i4_add_thread(render_thread_start, 200*1024, this);    
    }

    reset_lock.unlock();
  }

  void draw_vector(i4_3d_vector pos, i4_3d_vector dir, w32 color, i4_draw_context_class *context=0)
  {
    i4_3d_vector end=pos;
    end+=dir;
    draw_3d_line(pos, end, color, context);
    
    i4_3d_vector xs, ys;
    if (dir.x>dir.y && dir.x>dir.z)
    {
      xs.cross(dir, i4_3d_vector(0,1,0)); 
      ys.cross(dir, i4_3d_vector(0,0,1));  
    }
    else if (dir.y>dir.z)
    {
      xs.cross(dir, i4_3d_vector(1,0,0)); 
      ys.cross(dir, i4_3d_vector(0,0,1));  
    }
    else
    {
      xs.cross(dir, i4_3d_vector(1,0,0)); 
      ys.cross(dir, i4_3d_vector(0,1,0));  
    }

    i4_3d_vector p5=dir;
    p5*=0.8; p5+=pos;    

    xs.normalize();
    ys.normalize();

    xs*=0.1;
    ys*=0.1;
    i4_3d_vector p1=p5,p2=p5,p3=p5,p4=p5;
    p1+=xs; p1+=ys;
    p2-=xs; p2+=ys;
    p3-=xs; p3-=ys;
    p4+=xs; p4-=ys;


    draw_3d_line(p1,p2, color, context);
    draw_3d_line(p2,p3, color, context);
    draw_3d_line(p3,p4, color, context);
    draw_3d_line(p4,p1, color, context);

    draw_3d_line(p1,end, color, context);
    draw_3d_line(p2,end, color, context);
    draw_3d_line(p3,end, color, context);
    draw_3d_line(p4,end, color, context);
  }

  void draw_wire_3d_sphere(i4_3d_vector pos, float r, i4_draw_context_class *context)
  {
    draw_3d_line(i4_3d_vector(pos.x-r, pos.y, pos.z),
                 i4_3d_vector(pos.x+r, pos.y, pos.z), 0xffffff, context);
    draw_3d_line(i4_3d_vector(pos.x, pos.y-r, pos.z),
                 i4_3d_vector(pos.x, pos.y+r, pos.z), 0xffffff, context);
    draw_3d_line(i4_3d_vector(pos.x, pos.y, pos.z-r),
                 i4_3d_vector(pos.x, pos.y, pos.z+r), 0xffffff, context);
                 
  }

  void draw_3d_line(i4_3d_vector p1, i4_3d_vector p2, w32 color, i4_draw_context_class *context)
  {
    if (!context)
    {
      r1_vert v[2];
      v[0].a=1;
      v[0].r=((color&0xff0000)>>16)/255.0;
      v[0].g=((color&0xff00)>>8)/255.0;
      v[0].b=((color&0xff)>>0)/255.0;

      v[1]=v[0];

      if (project_point(p1, v[0]) &&
          project_point(p2, v[1]))
        r1_clip_render_lines(1, v, width()/2, height()/2, api);
    }
    else
    {
      r1_vert tp1, tp2;
      if (project_point(p1,tp1) && project_point(p2,tp2))   
        local_image->line((int)tp1.px, (int)tp1.py,
                          (int)tp2.px, (int)tp2.py, color, *context);
    }
                        
  }


  void draw(i4_draw_context_class &context)
  {
    if (rendering)
    {
      local_image->clear(0,context);
      int i;
      for (i=0; i0)
    {
      float ooz=1.0/q.v.z;
      q.px=q.v.x * ooz * width()/2.0 + width()/2.0;
      q.py=q.v.y * ooz * height()/2.0 + height()/2.0;
      return i4_T;
    }
    return i4_F;

  }


  void save()
  {
    i4_draw_context_class context(0,0, width()-1, height()-1);

    i4_image_class *im=i4_create_image(width(), height(), local_image->pal);
    local_image->put_part(im, 0,0, x(), y(), x()+width()-1, y()+height()-1, context);
    

    i4_file_class *fp=i4_open("im.jpg", I4_WRITE);
    i4_write_jpeg(im, fp, 100);
    delete fp;    
    delete im;
  }

  void receive_event(i4_event *ev)
  {

    switch (ev->type())
    {
      case i4_event::USER_MESSAGE :
      {
        CAST_PTR(uev, i4_user_message_event_class, ev);
        switch (uev->sub_type)
        {
          case SAVE:
            save();
            break;
        }

      } break;

      case i4_event::MOUSE_MOVE :
      {
        CAST_PTR(mev, i4_mouse_move_event_class, ev);
        if (mouse_look_grab)
        {
          rotate_cam((mev->ly-mev->y)/200.0,
                     (mev->x-mev->lx)/200.0,
                     0);
        }
      } break;
 
      case i4_event::MOUSE_BUTTON_DOWN :
      {
        CAST_PTR(bev, i4_mouse_button_event_class, ev);
        if (bev->right())
        {
          mouse_look_grab=i4_T;
          
          i4_window_request_mouse_grab_class grab(this);
          i4_kernel.send_event(parent, &grab);
          //          i4_current_app->get_display()->set_mouse_raw_mode(i4_T);
        }
        else if (bev->left())
        {
          i4_3d_vector ray_dir;
          get_mouse_ray(bev->x, bev->y, ray_dir);

          path_len=0;
          float z;

          path_info c;
          i4_3d_vector eye;
          t.inverse_transform(i4_3d_vector(0,0,0), eye);
          trace_ray(eye, eye, ray_dir, 1, z, c, i4_T);
          calc_transform();
        }


      } break;

      case i4_event::MOUSE_BUTTON_UP :
      {
        CAST_PTR(bev, i4_mouse_button_event_class, ev);
        if (bev->right() && mouse_look_grab)
        {
          mouse_look_grab=i4_F;
          //          i4_current_app->get_display()->set_mouse_raw_mode(i4_F);
          i4_window_request_mouse_ungrab_class ungrab(this);
          i4_kernel.send_event(parent, &ungrab);
        }

      } break;

    

      case i4_event::KEY_PRESS :
      {
        CAST_PTR(kev, i4_key_press_event_class, ev);
        switch (kev->key)
        {
          case I4_UP : move_cam(0,0,0.5); break;
          case I4_DOWN : move_cam(0,0,-0.5); break;
          case I4_LEFT : move_cam(-0.5,0,0); break;
          case I4_RIGHT : move_cam(0.5,0,0); break;
          case 'z' : rotate_cam(-0.1, 0,0); break;
          case 'x' : rotate_cam(0.1, 0,0); break;
          case 'c' : rotate_cam(0, -0.1,0); break;
          case 'v' : rotate_cam(0, 0.1,0); break;
          case 'w' : 
          case ' ' : 
          {
            light.x+=0.1; light.normalize(); calc_transform();
          } break;
        }

      }
    
      default:
        i4_window_class::receive_event(ev);
    }
  }
};


void render_thread_start(void *context)
{
  ((trace_view *)context)->thread_render();
}




i4_window_class *create_view(int w, int h)
{
  r1_render_window_class *win=api->create_render_window(w,h);
  trace=new trace_view(w,h, win);
  win->add_child(0,0, trace);
  return win;
}


class ray_tracer_app : public i4_application_class
{
public:
  i4_bool get_display_name(char *name, int max_len)
  {
    strcpy(name, "Windowed GDI");
    return i4_T;
  }

  void init()
  {
    i4_application_class::init();

    i4_pull_menu_class *menu=li_create_pull_menu("menu.scm");
    menu->show(wm, 0,0);

    api=r1_create_api(get_display(), "Software Z Buffer");
    if (!api)
      i4_error("no render api");

    i4_graphical_style_class *style=get_style();
    i4_window_class *w=create_view(400, 400);

    i4_parent_window_class *p;
    p=style->create_mp_window(0, menu->height(), w->width(), w->height(), "Render");
    p->add_child(0,0, w);

    
    i4_button_class *b=new i4_button_class(0, new i4_text_window_class("Save", style), style, 
                                           new i4_event_reaction_class(trace, SAVE));
    wm->add_child(wm->width()-b->width(), wm->height()-b->height(), b);
  }

  void calc_model()
  {
    if (trace->path_len)
    {
      float z=10000;
      path_info c;
      i4_3d_vector dir=trace->path[0].pos;
      dir-=trace->path[0].start;
      i4_3d_vector eye;
      trace->t.inverse_transform(i4_3d_vector(0,0,0), eye);
      trace->trace_ray(eye, trace->path[0].start, dir, 1, z, c); 
    }

  }

  void uninit()
  {
    trace->abort_render=1;
    i4_application_class::uninit();
  }


  char *name() { return "ray tracer"; }
};


void i4_main(w32 argc, i4_const_str *argv)
{
  ray_tracer_app test;
  test.run();
}