// Watcom graphics support


#include "globals.hpp"
#include "system.h"
#include "video.hpp"
#include "dos.h"
#include "macs.hpp"
#include "bitmap.h"
#include "image.hpp"
#include "palette.hpp"
#include "jmalloc.hpp"
#include "doscall.hpp"
#include <conio.h>


#define vga_getgraphmem() ((unsigned char *)VESA_video_mem)
#define vga_setpage(x) (VESA_set_bank(x))
#define WinAAttributes() vesa_mode_info[2]
#define WinBAttributes() vesa_mode_info[3]
#define WinGranularity() (*((unsigned short *)(vesa_mode_info+4)))
#define WinSize() (*((unsigned short *)(vesa_mode_info+6)))
#define WinASegment() (*((unsigned short *)(vesa_mode_info+8)))
#define WinBSegment() (*((unsigned short *)(vesa_mode_info+10)))
#define BankSize() vesa_mode_info[0x1c]
#define WINDOW_A 0
#define WINDOW_B 1
#define WINDOW_DEFINED 1
#define WINDOW_WRITABLE 4

unsigned char current_background;
extern unsigned int xres,yres;
extern palette *lastl;
int vmode,VESA_write_window=0;
image *screen;
unsigned char *VESA_video_mem;
char *dos_pal_memory=NULL;
unsigned char *vesa_mode_info=NULL;
int write_bank;
int gran_shift=0;

#include <i86.h>

#define MKPTR(x)  ((   (((unsigned long)x)&0xffff0000)>>12)+  (((unsigned long)x)&0xffff))

#pragma pack( 1 ) // pack to bytes boundardy


/* VESA video card capabilities block  */
struct  ModeInfoBlock {
	short  ModeAttributes;
	char WinAAttributes;
	char WinBAttributes;
	unsigned short WinGranularity;
	unsigned short  WinSize;
	unsigned short  WinASegment;
	unsigned short  WinBSegment;
	unsigned long *WinFuncPtr;
	short  BytesPerScanLine;
	short  XResolution;
	short  YResolution;
	char XCharSize;
	char YCharSize;
	char NumberOfPlanes;
	char BitsPerPixel;
	char NumberOfBanks;
	char MemoryModel;
	char BankSize;
	char NumberOfImagePages;
	char SizeOfBank;
	char RedMaskSize;
	char RedFieldPosition;
	char GreenMaskSize;
	char GreenFieldPosition;
	char BlueMaskSize;
	char BlueFieldPosition;
	char RsvdMaskSize;
	char RsvdFieldPosition;
	char DirectColorModeInfo;
	char Reserved[216];
	} vesa_info;

struct VgaInfoBlock
{
  unsigned char       VESASignature[4];
  unsigned short      ver;
  unsigned long       OEMStringPtr;
  unsigned char       Capabilities[4];
  unsigned long       VideoModePtr;
  unsigned short      TotalMemory;
  unsigned char       Reserved[236];
} ;

#pragma pack( ) // pack back to normal


void get_VESA_mode_info(ModeInfoBlock *s, unsigned short mode)
{
  int size=sizeof(ModeInfoBlock)<256 ? 256 : sizeof(ModeInfoBlock);
  ModeInfoBlock *k=(ModeInfoBlock *)alloc_low_memory(size);
  if (!k)
  {
    printf("Memory error : could not alloc low memory for VESA mode info structure\n");
    exit(0);
  }  

  rminfo rm;
  memset(&rm,0,sizeof(rm));
  rm.eax=0x4f01;
  rm.ecx=mode;
  rm.es=((long)(k))>>4;
  rm.edi=0;        
  RM_intr(0x10,&rm);

  memcpy(s,k,sizeof(ModeInfoBlock));
  
  free_low_memory(k);
}

int usable_mode(ModeInfoBlock *m)
{

  if ((m->ModeAttributes&1)==1 &&        // mode available?
      (m->ModeAttributes&8)==8 &&        // color mode?
      (m->ModeAttributes&16)==16 &&        // graphics mode?
      (m->NumberOfPlanes==1)   &&        // packed pixel form
      (m->BitsPerPixel==8)     &&        // 256 color mode
      (m->MemoryModel==4)                // VESA packed pixel
      )
    return 1;
  else return 0;
}

int get_VESA_mode(char **s)
{
  int get_xres,get_yres,show_menu=1,found=0;

  if (sscanf(s[0],"%d",&get_xres)!=1 || sscanf(s[1],"%d",&get_yres)!=1)
  { 
    show_menu=1;
    found=1;
  }

  VgaInfoBlock *b=(VgaInfoBlock *)alloc_low_memory(sizeof(VgaInfoBlock));

  if (!b)
  {
    printf("Memory error : could not alloc low memory for VESA mode info structure\n");
    exit(0);
  }  
  
  rminfo rm;
  memset(&rm,0,sizeof(rm));
  rm.eax=0x4f00;
  rm.es=((long)(b))>>4;
  rm.edi=0;        
  RM_intr(0x10,&rm);

  if (memcmp(b->VESASignature,"VESA",4))
  {
    printf("No VESA driver detected.  You need to install a VESA TSR.\n");
    free_low_memory(b);
    exit(0);
  }
  if ((b->ver>>8)==0 || (b->ver&&0xff)>2)
  {
    printf("Your VESA driver is out dated, please upgrade.\n");
    free_low_memory(b);
    exit(0);
  }

  VESA_video_mem=(unsigned char *)MKPTR(b->VideoModePtr);
  
  printf("Video card info : %s\n",(char *)MKPTR(b->OEMStringPtr));
  short *modes=(short *)MKPTR(b->VideoModePtr);   // type cast low mem pointer

  while (*modes!=-1 && !found)
  {
    struct ModeInfoBlock m;
    get_VESA_mode_info(&m,*modes);
    if (usable_mode(&m))
    {                     // this is a usable mode
      if (m.XResolution==get_xres && m.YResolution==get_yres)
      {
	free_low_memory(b);
	return *modes;
      }
    }	
    modes++;
  }

  if (show_menu)
  {
    int i=1;
    printf("Bad resoultion size or size not supported by VESA driver\n"
	   "Please choice one of the below :\n");
    modes=(short *)MKPTR(b->VideoModePtr);
    while (*modes!=-1)            // list all chosable modes
    {
      struct ModeInfoBlock m;
      get_VESA_mode_info(&m,*modes);

      if (usable_mode(&m))
      {
	printf("%d) %d X %d\n",i,m.XResolution,m.YResolution);
	i++;
      }
      modes++;
    }

    int d=0;
    do
    {
      fprintf(stderr,"Enter mode # (1-%d)>",i-1);
      char ln[100];
      fgets(ln,100,stdin);
      if (!sscanf(ln,"%d",&d))
        d=0;
    } while (!d);
    d--;

    modes=(short *)MKPTR(b->VideoModePtr);
    while (*modes!=-1)
    {
      struct ModeInfoBlock m;
      get_VESA_mode_info(&m,*modes);
      if (usable_mode(&m))
      {
	if (d) d--;
	else return *modes;
      }
      modes++;
    }

    printf("Your VESA driver is reporting incorrect information, try getting\n"
	   "univbe or univesa from any simtel mirror ftp site such as\n"
	   "wuarchive.wustl.edu.\n");
    free_low_memory(b);
    exit(0);
  }

  free_low_memory(b);
  return found;
}

void VESA_set_bank(int x)
{
  if (vmode!=0x13)
  {
    rminfo rm;
    memset(&rm,0,sizeof(rm));
    rm.eax=0x4f05;
    rm.ebx=VESA_write_window;
    rm.edx=x<<gran_shift;
    RM_intr(0x10,&rm);
  }
}

int VESA_get_mode()
{
  rminfo rm;
  memset(&rm,0,sizeof(rm));
  rm.eax=0x4f03;
  RM_intr(0x10,&rm);
  return rm.ebx&0xffff;
}

int VESA_set_mode(int mode)
{
  rminfo rm;
  memset(&rm,0,sizeof(rm));
  if (mode!=19 && mode!=3)
  {
    rm.eax=0x4f02;
    rm.ebx=mode;
    RM_intr(0x10,&rm);
    vmode=mode;

    if (VESA_get_mode()==mode)
    {
      get_VESA_mode_info(&vesa_info,mode);

      if ((vesa_info.WinAAttributes & WINDOW_DEFINED) &&
	  (vesa_info.WinAAttributes & WINDOW_WRITABLE))
      {
        VESA_write_window=WINDOW_A;
	VESA_video_mem=(unsigned char *)((long)vesa_info.WinASegment<<4);
      }
      else if ((vesa_info.WinBAttributes & WINDOW_DEFINED) &&	       
	       (vesa_info.WinBAttributes & WINDOW_WRITABLE))
      {
        VESA_write_window=WINDOW_B;
	VESA_video_mem=(unsigned char *)((long)vesa_info.WinBSegment<<4);
      }
      else VESA_video_mem=(unsigned char *)0xa0000;

      xres=vesa_info.XResolution-1;
      yres=vesa_info.YResolution-1;

      if (vesa_info.WinGranularity==1)  // 1K pages
        gran_shift=6;
      else if (vesa_info.WinGranularity==2)  // 2K pages
        gran_shift=5;
      else if (vesa_info.WinGranularity==4)  // 4K pages
        gran_shift=4;
      else if (vesa_info.WinGranularity==8)  // 8K pages
        gran_shift=3;
      else if (vesa_info.WinGranularity==16)  // 16K pages
        gran_shift=2;
      else if (vesa_info.WinGranularity==32)  // 16K pages
        gran_shift=1;
      else if (vesa_info.WinGranularity==64)  // 16K pages
        gran_shift=0;
      else
      {
	printf("VESA : window granularity not supported (%d)\n",vesa_info.WinGranularity);
	exit(0);
      }

      

//      exit(0);
      return 1;
    } else return 0;
  }
  else
  {
    vmode=mode;
    rm.eax=mode;
    RM_intr(0x10,&rm);
    VESA_video_mem=(unsigned char *)0xa0000;
    VESA_write_window=WINDOW_A;
    xres=320-1;
    yres=200-1;
    return 1;
  }
}

void set_mode(int mode, int argc, char **argv)
{
  int i,j;
  
  xres=320; yres=200;

  for (i=1;i<argc;i++)
    if (!strcmp(argv[i],"-size"))
      mode=get_VESA_mode(argv+i+1);


  if (!VESA_set_mode(mode))
  {
    printf("Unable to set video mode.  The correct VESA driver is not loaded\n"
           "or it does not support this video mode.\n");
    exit(1);
  }

  screen=new image(xres+1,yres+1,NULL,2);
  screen->clear();
  update_dirty(screen);

}

int get_vmode()
{ return vmode; }

void close_graphics()
{
  if (lastl)
    delete lastl;
  lastl=NULL;
  VESA_set_mode(3);  // switch to text mode
  delete screen;
}

void put_part(image *im, int x, int y, int x1, int y1, int x2, int y2)
{
  unsigned short screen_off;
  int i,ys,ye,xs,xe,page,last_page=-1,yy;
  long breaker;
  unsigned char *screen_addr,*line_addr;

  if (y>(int)yres || x>(int)xres) return ;
  CHECK(y1>=0 && y2>=y1 && x1>=0 && x2>=x1);


  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 ;

  int virtual_screen_width=im->width();
  line_addr=im->scan_line(ys)+xs;

  for (yy=ys;yy<=ye;yy++)
  {
    page=(long)y*(long)(xres+1)>>16;
    if (page!=last_page)
    { last_page=page;
      vga_setpage(page);
    }

    // find the memory offset for the scan line of interest
    screen_off=((long)y*(long)(xres+1))&0xffff;

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

    if (breaker>x+xe-xs)
      memcpy(vga_getgraphmem()+screen_off+x,line_addr,xe-xs+1);
    else if (breaker<=x)
    { last_page++;
      vga_setpage(last_page);
      memcpy(vga_getgraphmem()+x-breaker,line_addr,xe-xs+1);
    }
    else
    {
      memcpy(vga_getgraphmem()+screen_off+x,line_addr,breaker-x);
      last_page++;
      vga_setpage(last_page);
      memcpy(vga_getgraphmem(),line_addr+breaker-x,xe-xs-(breaker-x)+1);
    }
    y++;
    line_addr+=virtual_screen_width;
  }
}

void put_image(image *im, int x, int y)
{ put_part(im,x,y,0,0,im->width()-1,im->height()-1); }


void update_dirty(image *im, int xoff, int yoff)
{

  int count,x1,y1,x2,y2;
  dirty_rect *dr,*q;
  image *Xim;
  CHECK(im->special);  // make sure the image has the ablity to contain dirty areas
  if (im->special->keep_dirt==0)
    put_image(im,0,0);
  else
  {
    count=im->special->dirties.number_nodes();
    if (!count) return;  // if nothing to update, return
    dr=(dirty_rect *)im->special->dirties.first();
    while (count>0)
    {
/*      if (dr->dx1+xoff<0) dr->dx1=0-xoff;
      if (dr->dy1+yoff<0) dr->dy1=0-yoff;
      if (dr->dx2+xoff>xres) dr->dx2=xres-xoff;
      if (dr->dy2+yres>yres) dr->dy2=yres-yoff;
      if (dr->dx1<=dr->dx2 && dr->dy1<=dr->dy2) */
      put_part(im,dr->dx1+xoff,dr->dy1+yoff,dr->dx1,dr->dy1,dr->dx2,dr->dy2);
      q=dr;
      dr=(dirty_rect *)dr->next();
      im->special->dirties.unlink((linked_node *)q);
      delete q;
      count--;
    }
  }
}


void palette::load()
{
  int i;
  unsigned char *a=(unsigned char *)addr();
  if (lastl)
    delete lastl;
  lastl=copy();

  outp(0x3c8,0);
  for (i=0;i<768;i++,a++)
    outp(0x3c9,(*a)>>2);

}

void palette::load_nice()
{ load(); }


void image::make_page(short width, short height, unsigned char *page_buffer)
{
  if (page_buffer)
    data=page_buffer;
  else data=(unsigned char *)jmalloc(width*height,"image::data");
}

void image::delete_page()
{
  if (!special || !special->static_mem)
    jfree(data);      
}


