/**********************************************************************
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/display.hh"
#include "video/svgalib/svgal_vd.hh"
#include "image/image8.hh"
#include "device/kernel.hh"

#include 
#include 
#include 

svgalib_display_class svgalib_display_instance;

svgalib_display_class::svgalib_display_class()
{
  init=NOT_INITIALIZED;
  screen=NULL;
  mouse_notify.display=this;

  mouse_event_flags=0;
  mouse_pict=mouse_save=NULL;
  context=0;
}

svgalib_display_class::~svgalib_display_class()
{
  kernel.remove_device(&devs);
}


i4_image_class *svgalib_display_class::next_frame_screen(i4_rect_list_class *area_of_change)
{
  i4_rect_list_class::area *a=area_of_change->get_list();
 
  for (;a;a=a->next)   // add all of the area of change to the dirty list
    screen->dirty.add_area(a->x1,a->y1,a->x2,a->y2);

  return screen;
}


i4_image_class *svgalib_display_class::both_screens()
{
  return screen;
}

void svgalib_display_class::flush()
{
  int mw,mh;
  i4_rect_list_class no_clip(0,0,width()-1,height()-1);

  if (mouse_pict)
  {
    screen->clip_list.swap(&no_clip);

    mw=mouse_pict->width();
    mh=mouse_pict->height();
    mouse_save->set_pal(pal);

    mouse_save->copy_part(screen,0,0,mouse_x,mouse_y,mouse_x+mw-1,mouse_y+mh-1);  // save area behind mouse
    screen->copy_from_trans(mouse_pict,mouse_x,mouse_y,0);
  }

  i4_rect_list_class::area *a=screen->dirty.get_list(),*next;
  for (;a;a=next)
  {
    next=a->next;
    copy_part_to_vram(screen,a->x1,a->y1,a->x1,a->y1,a->x2,a->y2);
  }  
  screen->dirty.delete_list();

  if (mouse_pict)
  {    
    screen->copy_part(mouse_save,mouse_x,mouse_y,0,0,mw-1,mh-1);   // restore area behind mouse
    screen->clip_list.swap(&no_clip);
  }  
}


void svgalib_display_class::copy_part_to_vram(i4_image_class *im, int x, int y, int x1, int y1, int x2, int y2)
{
  unsigned long screen_off;
  int ys,ye,         // ystart, yend
        xs,xe,      
        page,last_page=-1,yy;
  long breaker;
  unsigned char *line_addr;
  int xres=width()-1,yres=height()-1;

  if (y>(int)yres || x>(int)xres) return ;


  if (y<0)
  { y1+=-y; y=0; }
  ys=y1;
  if (y+(y2-y1)>=(int)yres)
    ye=(int)yres-y+y1-1;
  else ye=y2;

  if (x<0)
  { x1+=-x; x=0; }
  xs=x1;
  if (x+(x2-x1)>=(int)xres)
    xe=(int)xres-x+x1-1;
  else xe=x2;
  if (xs>xe || ys>ye) return ;

  // find the memory offset for the scan line of interest
  screen_off=((long)y*(long)(xres+1));
  int bpl=screen->bytes_per_line();


  for (yy=ys;yy<=ye;yy++,screen_off+=(xres+1))
  {
    page=screen_off>>16;     // which page of 64k are we on?
    if (page!=last_page)     
    { last_page=page;
      vga_setpage(page);     // switch to new bank
    }

    line_addr=im->image_data()+yy*bpl+xs;

    // breaker is the number of bytes before the page split
    breaker=(long)0xffff-(long)(screen_off&0xffff)+1;


    // see if the slam gets split by the page break
    if (breaker>x+xe-xs)
    {
      void *dest=v_addr+(screen_off&0xffff)+x;
      int size=xe-xs+1;
      memcpy(dest,line_addr,size);
    }
    else if (breaker<=x)
    { last_page++;
      vga_setpage(last_page);
      memcpy(v_addr+x-breaker,line_addr,xe-xs+1); 
    }
    else
    {
      memcpy(v_addr+(screen_off&0xffff)+x,line_addr,breaker-x);
      last_page++;
      vga_setpage(last_page);
      memcpy(v_addr,line_addr+breaker-x,xe-xs-(breaker-x)+1);
    } 
    y++;   
  }
}


static int svga_modes[]={  // a list of a the SVGALIB supported modes we support
  G320x200x256,
/*  G320x240x256,       // modex only?
  G320x400x256,
  G360x480x256, */
  G640x480x256,
  G800x600x256,
  G1024x768x256,
  G1280x1024x256,
  -1
} ;


void svgalib_display_class::fill_amode(int mode)
{
  memset(&amode,0,sizeof(amode));
  amode.mode_num=mode;
  vga_modeinfo *m=vga_getmodeinfo(svga_modes[mode]);
  amode.bits_per_pixel=8;
  amode.bits_per_color=8;
  amode.assoc=this;
  amode.xres=m->width;
  amode.yres=m->height;
}


i4_display_class::mode *svgalib_display_class::get_first_mode()
{
  if (!available()) return NULL;

  fill_amode(0);
  return &amode;
}


i4_display_class::mode *svgalib_display_class::get_next_mode(mode *last_mode)
{
  if (!last_mode || ((svga_mode *)last_mode)->assoc!=this) return NULL;   // make sure we created the last mode
  if (svga_modes[((svga_mode *)last_mode)->mode_num+1]==-1) return NULL;     // any mode modes left?
  fill_amode(  ((svga_mode *)last_mode)->mode_num+1);
  return &amode;
}


static unsigned char def_mouse[]=
    { 0,2,0,0,0,0,0,0,
      2,1,2,0,0,0,0,0,
      2,1,1,2,0,0,0,0,
      2,1,1,1,2,0,0,0,

      2,1,1,1,1,2,0,0,
      2,1,1,1,1,1,2,0,
      0,2,1,1,2,2,0,0,
      0,0,2,1,1,2,0,0,
      0,0,2,1,1,2,0,0,
      0,0,0,2,2,0,0,0 };    // 8x10


  // initialize_mode need not call close() to switch to another mode
i4_bool svgalib_display_class::initialize_mode(mode *which_one)
{
  if (!which_one || ((svga_mode *)which_one)->assoc!=this) return i4_F;

  cur_svga_mode=svga_modes[((svga_mode *)which_one)->mode_num];
  vga_setmode(cur_svga_mode);

  v_addr=vga_getgraphmem();
  if (pal.is_32bit())          // palette has not been created yet
  {
    w32 initial_pal[256];
    int x;
    for (x=0;x<256;x++)                       // initialize to a gray scale pal
      initial_pal[x]=(x<<16)|(x<<8)|x;

    pal=i4_pal_man.register_pal(i4_pal_handle_class::ID_8BIT,initial_pal);
  }

  realize_palette(pal);

  if (screen) 
    delete screen;

  screen=new i4_image8_clippable(which_one->xres,which_one->yres,pal);


  if (mouse_pal.is_32bit())
  {
    w32 mpal[256];
    memset(mpal,0,sizeof(mpal));
    mpal[0]=0;
    mpal[1]=0xffffff;
    mpal[2]=0x080808;
    mouse_pal=i4_pal_man.register_pal(i4_pal_handle_class::ID_8BIT,mpal);
  } 

  if (!mouse_event_flags)
    mouse_event_flags=local_devices()->request_device(&mouse_notify,i4_device_class::FLAG_MOUSE_MOVE);

  if (mouse_pict)    // has the mouse already been initialized?
  {
    delete mouse_pict;  mouse_pict=NULL;
    delete mouse_save;  mouse_save=NULL;
  }

  if (mouse_event_flags)
  {
    mouse_pict=new i4_image8(8,10,mouse_pal);    // the mouse pict has it's own palette to ensure it will be displayed properly
    mouse_save=new i4_image8(8,10,pal);    // the save image (for behinf the mouse) uses the screen palette
    memcpy(mouse_pict->image_data(),def_mouse,8*10);
  } 

  
}

  // should be called before a program quits
i4_bool svgalib_display_class::close()
{
  if (mouse_event_flags)
  {
    mouse_event_flags=0;
    local_devices()->release_device(&mouse_notify,i4_device_class::FLAG_MOUSE_MOVE);
  }

  if (screen) 
  {
    delete screen;
    screen=NULL;
  }

  if (mouse_pict)
  {
    delete mouse_pict;
    mouse_pict=NULL;
  }

  if (mouse_save)
  {
    delete mouse_save;
    mouse_save=NULL;
  }

  vga_setmode(TEXT);


}


i4_bool svgalib_display_class::available()
{
  if (init==NOT_INITIALIZED)
  {
    if (ioperm(0x3b4, 0x3df - 0x3b4 + 1, 1)!=0)   // make sure we have permission to vga ports
      init=INITIALIZED_AND_NOT_AVAILABLE;
    else if (vga_init()==-1)
      init=INITIALIZED_AND_NOT_AVAILABLE;
    else 
      init=INITIALIZED_AND_AVAILABLE;
  } 

  return (i4_bool)(init==INITIALIZED_AND_AVAILABLE);
}



i4_bool svgalib_display_class::realize_palette(i4_pal_handle_class pal_id)
{
  if (pal_id.is_8bit())
  {
    w32 *data=i4_pal_man.get_pal(pal_id);
    for (int i=0;i<256;i++,data++)
      vga_setpalette(i,(((*data)>>18)&0x3f),
		       (((*data)>>10)&0x3f),
		       (((*data)>>2)&0x3f));
    pal=pal_id;
    if (screen)
      screen->set_pal(pal);
    return i4_T;
  } else return i4_F;
}