/**********************************************************************
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 "video/glide/glide_display.hh"
#include "image/image.hh"
#include "time/profile.hh"
#include "image/context.hh"
#include "image/image16.hh"

#ifdef _WINDOWS
#include "video/glide/glidefunc.cc"
#endif

i4_profile_class pf_glide_flush_lock_write("glide_flush :: lock(WRITE)");
i4_profile_class pf_glide_flush_mouse("glide_flush :: mouse draw");
i4_profile_class pf_glide_flush_mouse_save("glide_flush :: mouse save");
i4_profile_class pf_glide_flush_copy("glide_flush :: copy_vram");
i4_profile_class pf_glide_flush_flip("glide_flush :: page_flip");

i4_glide_display_class *i4_glide_display=0;

struct gr_buf_status_type
{
  sw8            state;  // -1 = not locked, else  
                         //    I4_FRAME_BUFFER_READ,
                         // or I4_FRAME_BUFFER_WRITE
  i4_image_class *im;
  int gr_lock_type;

} gr_buf[2];    // I4_FRONT_FRAME_BUFFER=0, I4_BACK_FRAME_BUFFER=1


i4_image_class *i4_glide_display_class::lock_frame_buffer(i4_frame_buffer_type type,
                                                          i4_frame_access_type access)
{
  if (gr_buf[type].state!=access)
  {
    if (gr_buf[type].state!=-1)
      unlock_frame_buffer(type);

    int gr_access = access==I4_FRAME_BUFFER_READ ? GR_LFB_READ_ONLY : GR_LFB_WRITE_ONLY;
    int gr_buf_t = type == I4_FRONT_FRAME_BUFFER ? GR_BUFFER_FRONTBUFFER : GR_BUFFER_BACKBUFFER;

    GrLfbInfo_t info;
    info.size = sizeof(GrLfbInfo_t);

    if (!grLfbLock(gr_access,
                   gr_buf_t,
                   GR_LFBWRITEMODE_565,
                   GR_ORIGIN_UPPER_LEFT,
                   FXFALSE,
                   &info)) 
      return 0;


    gr_buf[type].im->bpl=info.strideInBytes;
    gr_buf[type].im->data=info.lfbPtr;
    gr_buf[type].gr_lock_type=gr_access;
    gr_buf[type].state=access;
  }

  return gr_buf[type].im;
}

void i4_glide_display_class::unlock_frame_buffer(i4_frame_buffer_type type)
{
  if (gr_buf[type].state==-1)  // already unlocked?
    return;    

  int gr_buf_t = type == I4_FRONT_FRAME_BUFFER ? GR_BUFFER_FRONTBUFFER : GR_BUFFER_BACKBUFFER;
 
  grLfbUnlock(gr_buf[type].gr_lock_type, gr_buf_t);
  gr_buf[type].im->data=0;
  gr_buf[type].state=-1;
}


i4_bool procs_loaded = i4_F;

#ifdef _WINDOWS
void FreeTheGlideDll();
FxBool GetProcAddresses();
#else
void FreeTheGlideDll() { ; }
FxBool GetProcAddresses() { return 1; }
#endif

void i4_glide_display_class::init()
{
  if (procs_loaded)
  {
    GrHwConfiguration config;    
    grSstQueryBoards(&config);

    if (config.num_sst>0)
    {
      me.add_to_list("3dfx Native", 0, this, i4_display_list);
    }
  }
}


i4_glide_display_class::i4_glide_display_class()
{
  i4_glide_display=this;
  mcursor=0;
  mouse_save1=0;
  mouse_save2=0;
  prev_mouse_save=0;
  procs_loaded = GetProcAddresses();
  last_mouse_x=last_mouse_y=40;
}

i4_bool i4_glide_display_class::close()
{
  next_frame_copy.delete_list();

  if (context)
    delete context;
  context=0;

  for (int i=0; i<2; i++)
    if (gr_buf[i].im)
    {
      delete gr_buf[i].im;
      gr_buf[i].im=0;
    }

  if (fake_screen)
  {
    delete fake_screen;
    fake_screen=0;
  }
  
  if (grSstControl)
    grSstControl(GR_CONTROL_DEACTIVATE);

  if (grSstWinClose)
    grSstWinClose();

  if (grGlideShutdown)
    grGlideShutdown();
  
  destroy_window();

  return i4_T;
}


static GrFog_t    fogtable[GR_FOG_TABLE_SIZE];

i4_bool i4_glide_display_class::initialize_mode()
{
  if (!create_window())
    return i4_F;  

  grGlideInit();
  GrHwConfiguration config;    
  grSstQueryHardware(&config);
  if (config.num_sst<=0)
  {
    i4_error("Couldnt find your 3dfx board");
  }
  grSstSelect( 0 );
 
  grSstWinOpen(window_handle(),
               tmp.glide_mode,
               GR_REFRESH_60Hz,
               GR_COLORFORMAT_ARGB,
               GR_ORIGIN_UPPER_LEFT,
               2, 1 );

  grDepthBufferMode( GR_DEPTHBUFFER_WBUFFER );
  grDepthBufferFunction( GR_CMP_LEQUAL);
  grDepthMask( FXTRUE );
  grCullMode(GR_CULL_DISABLE);
  //  grCullMode( GR_CULL_POSITIVE );
  grTexMipMapMode(GR_TMU0, GR_MIPMAP_DISABLE, FXFALSE);
  grHints(GR_HINT_STWHINT, GR_STWHINT_W_DIFF_TMU0 |
          GR_STWHINT_ST_DIFF_TMU0);

  guColorCombineFunction( GR_COLORCOMBINE_TEXTURE_TIMES_ITRGB );
  grTexCombineFunction(GR_TMU0, GR_TEXTURECOMBINE_DECAL);
  grAlphaCombine(GR_COMBINE_FUNCTION_BLEND_OTHER,GR_COMBINE_FACTOR_ONE,
                 GR_COMBINE_LOCAL_NONE,GR_COMBINE_OTHER_CONSTANT,0);
  grAlphaBlendFunction(GR_BLEND_ONE,GR_BLEND_ZERO,GR_BLEND_ZERO,GR_BLEND_ZERO);

  //  grTexClampMode(GR_TMU0,GR_TEXTURECLAMP_WRAP,GR_TEXTURECLAMP_WRAP);


  //grGlideShamelessPlug(1);
 // setup fog
  //grFogMode( GR_FOG_WITH_TABLE );
	//grFogColorValue( 0x00666666 );
	w32 i;
  w32 fog_density=0;
  for (i=0;i<64;i++)
  {
    fog_density += 4;
    if (fog_density>255) fog_density=255;
    fogtable[i] = (w8)fog_density;
  }
  //guFogGenerateExp(fogtable,0.001f);
	//grFogTable( fogtable );	

  i4_pixel_format fmt;
  fmt.pixel_depth=I4_16BIT;
  fmt.red_mask=tmp.red_mask;
  fmt.green_mask=tmp.green_mask;
  fmt.blue_mask=tmp.blue_mask;
  fmt.alpha_mask=0;
  fmt.calc_shift();

  pal = i4_pal_man.register_pal(&fmt);

  fake_screen=i4_create_image(tmp.xres, tmp.yres, pal);
  gr_buf[0].im=i4_create_image(tmp.xres, tmp.yres, pal, 0, 0);
  gr_buf[0].state=-1;

  gr_buf[1].im=i4_create_image(tmp.xres, tmp.yres, pal, 0, 0);
  gr_buf[1].state=-1;

    
  context=new i4_draw_context_class(0,0,tmp.xres-1,tmp.yres-1);
  context->both_dirty=new i4_rect_list_class;

  memcpy(&cur_mode, &tmp, sizeof(cur_mode));  
  
  return i4_T;
}

void i4_glide_display_class::remove_cursor(sw32 x, sw32 y, i4_image_class *mouse_save)
{
  if (mouse_save && mcursor)
  {
    i4_draw_context_class no_clip_context(0,0, width()-1, height()-1);      

    pf_glide_flush_lock_write.start();    
    i4_image_class *draw_to=lock_frame_buffer(I4_BACK_FRAME_BUFFER, I4_FRAME_BUFFER_WRITE);
    pf_glide_flush_lock_write.stop();

    int w=mouse_save->w, h=mouse_save->h;
    ((i4_image16 *)mouse_save)->put_part_one_pixel_at_a_time(draw_to,
                                                             x,y,0,0,w-1,h-1, no_clip_context);
    unlock_frame_buffer(I4_BACK_FRAME_BUFFER);
  }
}

void i4_glide_display_class::save_and_draw_cursor(sw32 x, sw32 y, i4_image_class *&mouse_save)
{
  if (mcursor)
  {
    pf_glide_flush_mouse_save.start();

    if (mouse_save && (mouse_save->width()!=mcursor->pict->width() ||
                       mouse_save->height()!=mcursor->pict->height()))
    {
      delete mouse_save;
      mouse_save=i4_create_image(mcursor->pict->width(), mcursor->pict->height(), pal);
    }
    else if (!mouse_save)
      mouse_save=i4_create_image(mcursor->pict->width(), mcursor->pict->height(), pal);

    i4_draw_context_class no_clip_context(0,0, width()-1, height()-1);
    sw32 x1=x, y1=y;
    sw32 x2=x1+mcursor->pict->width()-1, y2=y1+mcursor->pict->height()-1;


    if (x2 > width()-1)
      x2=width()-1;
    if (y2 > height()-1)
      y2=height()-1;
    if (xput_part_one_pixel_at_a_time(mouse_save, 0,0, x1,y1,x2,y2, no_clip_context);
      unlock_frame_buffer(I4_BACK_FRAME_BUFFER);
    }
           
    i4_image16 *dest=(i4_image16 *)lock_frame_buffer(I4_BACK_FRAME_BUFFER, I4_FRAME_BUFFER_WRITE);
    mcursor->pict->put_image_trans(dest,  x1,y1, mcursor->trans, no_clip_context);
    unlock_frame_buffer(I4_BACK_FRAME_BUFFER);

    pf_glide_flush_mouse_save.stop();
  }
}

void i4_glide_display_class::flush()
{
  i4_rect_list_class::area_iter a;

  sw32 mouse_x, mouse_y;
  get_mouse_pos(mouse_x, mouse_y);


  context->both_dirty->intersect_area(0,0,width()-1,height()-1);
  w16 *middle_buffer=(w16 *)fake_screen->data;

  pf_glide_flush_lock_write.start();
  i4_image_class *bbuf=lock_frame_buffer(I4_BACK_FRAME_BUFFER, I4_FRAME_BUFFER_WRITE);
  int bpl=bbuf->bpl;
  void *bptr = bbuf->data;
  pf_glide_flush_lock_write.stop();

  pf_glide_flush_copy.start();
  a=context->both_dirty->list.begin();
  for (;a!=context->both_dirty->list.end();++a)
    next_frame_copy.add_area(a->x1, a->y1, a->x2, a->y2);


  a=next_frame_copy.list.begin();
  for (;a!=context->both_dirty->list.end();++a)
  {
    for (w32 y=a->y1; y<=a->y2; y++)
    {
      w32 sx=a->x1, ex=(a->x2);

      w16 *src=(w16 *)(((w16 *)middle_buffer)+y*cur_mode.xres + sx);
      w16 *dst=(w16 *)(((w8 *)bptr)+y*bpl + sx*2);

      while (sx<=ex)
      {
        *(dst++)=*(src++);
        sx++;
      }
    }    
  }  

  next_frame_copy.swap(context->both_dirty);
  context->both_dirty->delete_list();

  pf_glide_flush_copy.stop();


  pf_glide_flush_mouse.start();

  i4_image_class **cur_save;
  // if this is the first time to save, or last save wave to mouse_save2 then use 1
  if (!prev_mouse_save || prev_mouse_save==mouse_save2)
    cur_save=&mouse_save1;
  else
    cur_save=&mouse_save2;

  save_and_draw_cursor(mouse_x - (sw32)mcursor->hot_x, 
                       mouse_y - (sw32)mcursor->hot_y, *cur_save);


  pf_glide_flush_mouse.stop();

  unlock_frame_buffer(I4_BACK_FRAME_BUFFER);
  
  pf_glide_flush_flip.start();
  //  while (grBufferNumPending());
  grBufferSwap(0);
  pf_glide_flush_flip.stop();

  pf_glide_flush_mouse.start();
  if (prev_mouse_save)
    remove_cursor(last_mouse_x, last_mouse_y, prev_mouse_save);
  pf_glide_flush_mouse.stop();

  prev_mouse_save=*cur_save;
  last_mouse_x=mouse_x - (sw32)mcursor->hot_x;
  last_mouse_y=mouse_y - (sw32)mcursor->hot_y;
}


i4_image_class *i4_glide_display_class::get_screen()
{      
  return fake_screen;
}

i4_bool i4_glide_display_class::set_mouse_shape(i4_cursor_class *cursor)
{
  if (mcursor)
    delete mcursor;
  
  mcursor=cursor->copy(get_palette());
  
  return i4_T;
}

void i4_glide_display_class::uninit()
{
  if (mcursor)
    delete mcursor;
  if (mouse_save1)
    delete mouse_save1;
  if (mouse_save2)
    delete mouse_save2;

  mouse_save1 = 0;
  mouse_save2 = 0;
  mcursor = 0;
}

i4_glide_display_class::~i4_glide_display_class()
{
  FreeTheGlideDll();
  procs_loaded = i4_F;
}

i4_display_class::mode *i4_glide_display_class::get_first_mode(int driver_id) 
{ 
  tmp.glide_mode=GR_RESOLUTION_320x200;
  tmp.xres=320;
  tmp.yres=200;
  tmp.flags=mode::PAGE_FLIPPED;
  tmp.red_mask=31<<11;
  tmp.green_mask=63<<5;
  tmp.blue_mask=31;
  strcpy(tmp.name, "320 X 200 16bit");
  return &tmp;
}

i4_display_class::mode *i4_glide_display_class::get_next_mode() 
{
  switch (tmp.glide_mode)
  {
    case GR_RESOLUTION_320x200 :
      tmp.glide_mode=GR_RESOLUTION_320x240;
      tmp.yres=240;
      break;
      
    case GR_RESOLUTION_320x240 :
      tmp.glide_mode=GR_RESOLUTION_400x256;
      tmp.xres=400; tmp.yres=256;
      break;

    case GR_RESOLUTION_400x256 :
      tmp.glide_mode=GR_RESOLUTION_512x384;
      tmp.xres=512; tmp.yres=384;
      break;


    case GR_RESOLUTION_512x384 :
      tmp.glide_mode=GR_RESOLUTION_640x200;
      tmp.xres=640; tmp.yres=200;
      break;

    case GR_RESOLUTION_640x200 :
      tmp.glide_mode=GR_RESOLUTION_640x350;
      tmp.xres=640; tmp.yres=350;
      break;

    case GR_RESOLUTION_640x350 :
      tmp.glide_mode=GR_RESOLUTION_640x400;
      tmp.xres=640; tmp.yres=400;
      break;

    case GR_RESOLUTION_640x400 :
      tmp.glide_mode=GR_RESOLUTION_640x480;
      tmp.xres=640; tmp.yres=480;
      break;

    case GR_RESOLUTION_640x480 :
      tmp.glide_mode=GR_RESOLUTION_800x600;
      tmp.xres=800; tmp.yres=600;
      break;

    case GR_RESOLUTION_800x600 :
      tmp.glide_mode=GR_RESOLUTION_960x720;
      tmp.xres=960; tmp.yres=720;
      break;

    case GR_RESOLUTION_960x720 :
      tmp.glide_mode=GR_RESOLUTION_856x480;
      tmp.xres=856; tmp.yres=480;
      break;

    case GR_RESOLUTION_856x480 :
      tmp.glide_mode=GR_RESOLUTION_512x256;
      tmp.xres=512; tmp.yres=256;
      break;


    case GR_RESOLUTION_512x256 :
      return 0;

  }

  sprintf(tmp.name, "%d X %d 16bit", tmp.xres, tmp.yres);

  return &tmp;
}