
#if defined(__TURBOC__)
extern "C" unsigned char inportb(int port_id);
extern "C" void outportb(int portid, unsigned char value);
#define _PORT_DEFS
#endif

#include <dos.h>
#include <conio.h>

#if defined(__EMX__)
#include <sys/hw.h>
#define port_out(value,port) _outp32(port,value)
#define port_in(port)  _inp32(port)

#elif defined(__TURBOC__) || defined(__GNUG__)
#define port_out(value,port) outportb(port,value)
#define port_in(port)  inportb(port)

#else
#define port_out(value,port) outp(port,value)
#define port_in(port)  inp(port)

#endif


#if defined (__ZTC__) || defined(__TURBOC__)
typedef unsigned char far* VIDEO_PTR;
#else
typedef unsigned char* VIDEO_PTR;
#endif


/* 
 * basic graphics routines in VGA mode 640x480x16
 * (part of this code is based on "VGAlib" by Tommy Frandsen)
 */


static VIDEO_PTR VIDEO;

static int LINE_BYTES;
static int VGA_WIDTH;
static int VGA_MAX_X;
static int VGA_HEIGHT;
static int VGA_MAX_Y;
static int VGA_DEPTH;


#define FONT_SIZE  0x2000

/* VGA index register ports */
#define CRT_IC  0x3D4   /* CRT Controller Index - color emulation */
#define CRT_IM  0x3B4   /* CRT Controller Index - mono emulation */
#define ATT_IW  0x3C0   /* Attribute Controller Index & Data Write Register */
#define GRA_I   0x3CE   /* Graphics Controller Index */
#define SEQ_I   0x3C4   /* Sequencer Index */
#define PEL_IW  0x3C8   /* PEL Write Index */
#define PEL_IR  0x3C7   /* PEL Read Index */

/* VGA data register ports */
#define CRT_DC  0x3D5   /* CRT Controller Data Register - color emulation */
#define CRT_DM  0x3B5   /* CRT Controller Data Register - mono emulation */
#define ATT_R   0x3C1   /* Attribute Controller Data Read Register */
#define GRA_D   0x3CF   /* Graphics Controller Data Register */
#define SEQ_D   0x3C5   /* Sequencer Data Register */
#define MIS_R   0x3CC   /* Misc Output Read Register */
#define MIS_W   0x3C2   /* Misc Output Write Register */
#define IS1_RC  0x3DA   /* Input Status Register 1 - color emulation */
#define IS1_RM  0x3BA   /* Input Status Register 1 - mono emulation */
#define PEL_D   0x3C9   /* PEL Data Register */

/* VGA indexes max counts */
#define CRT_C   24      /* 24 CRT Controller Registers */
#define ATT_C   21      /* 21 Attribute Controller Registers */
#define GRA_C   9       /* 9  Graphics Controller Registers */
#define SEQ_C   5       /* 5  Sequencer Registers */
#define MIS_C   1       /* 1  Misc Output Register */

/* VGA registers saving indexes */
#define CRT     0               /* CRT Controller Registers start */
#define ATT     CRT+CRT_C       /* Attribute Controller Registers start */
#define GRA     ATT+ATT_C       /* Graphics Controller Registers start */
#define SEQ     GRA+GRA_C       /* Sequencer Registers */
#define MIS     SEQ+SEQ_C       /* General Registers */
#define END     MIS+MIS_C       /* last */


/* variables used to shift between monchrome and color emulation */
static int CRT_I;		/* current CRT index register address */
static int CRT_D;		/* current CRT data register address */
static int IS1_R;		/* current input status register address */
static int color_text;		/* true if color text emulation */


/* BIOS mode 12h - 640x480x16 */
static char g640x480x16_regs[60] = {
  0x5F,0x4F,0x50,0x82,0x54,0x80,0x0B,0x3E,0x00,0x40,0x00,0x00,
  0x00,0x00,0x00,0x00,0xEA,0x8C,0xDF,0x28,0x00,0xE7,0x04,0xE3,
  0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,
  0x0C,0x0D,0x0E,0x0F,0x01,0x00,0x0F,0x00,0x00,
  0x00,0x0F,0x00,0x20,0x00,0x00,0x05,0x0F,0xFF,
  0x03,0x01,0x0F,0x00,0x06,
  0xE3
};


static char text_regs[60];   /* VGA registers for saved text mode */

/* saved text mode palette values */
static char text_red[256];
static char text_green[256];
static char text_blue[256];


static int initialized = 0;

static char font_buf1[FONT_SIZE];  /* saved font data - plane 2 */
static char font_buf2[FONT_SIZE];  /* saved font data - plane 3 */


static void set_regs(char regs[])
{
    int i;

    /* disable video */
    port_in(IS1_R);	
    port_out(0x00, ATT_IW);

    /* update misc output register */
    port_out(regs[MIS], MIS_W);

    /* synchronous reset on */
    port_out(0x00,SEQ_I);
    port_out(0x01,SEQ_D);	

    /* write sequencer registers */
    for (i = 1; i < SEQ_C; i++) {
	port_out(i, SEQ_I);
	port_out(regs[SEQ+i], SEQ_D);
    }

    /* synchronous reset off */
    port_out(0x00, SEQ_I);
    port_out(0x03, SEQ_D);	

    /* deprotect CRT registers 0-7 */
    port_out(0x11, CRT_I);		
    port_out(port_in(CRT_D)&0x7F, CRT_D);

    /* write CRT registers */
    for (i = 0; i < CRT_C; i++) {
	port_out(i, CRT_I);
	port_out(regs[CRT+i], CRT_D);
    }

    /* write graphics controller registers */
    for (i = 0; i < GRA_C; i++) {
	port_out(i, GRA_I);
	port_out(regs[GRA+i], GRA_D);
    }

    /* write attribute controller registers */
    for (i = 0; i < ATT_C; i++) {
	port_in(IS1_R);   /* reset flip-flop */
	port_out(i, ATT_IW);
	port_out(regs[ATT+i],ATT_IW);
    }
}

static void vga_initialize()
{
    int  i, j;
    int mem_fd = -1;  /* /dev/mem file descriptor		     */

    if (initialized) return;

    initialized = 1;


#if defined(__EMX__)
    _portaccess(0x300,0x3FF);
    VIDEO = (VIDEO_PTR)_memaccess(0xa0000,0xaffff,1);
#elif defined(__GNUC__)
    VIDEO = (VIDEO_PTR)0xd0000000;
#elif defined(__ZTC__)
    VIDEO = (VIDEO_PTR)_x386_mk_protected_ptr(0xa0000);
#elif defined(__WATCOMC__)
    VIDEO = (VIDEO_PTR)0xa0000;
#else
    VIDEO = (VIDEO_PTR)MK_FP(0xa000,0);
#endif


    /* color or monochrome text emulation? */
    color_text = port_in(MIS_R)&0x01;

    /* chose registers for color/monochrome emulation */
    if (color_text) {
	CRT_I = CRT_IC;
	CRT_D = CRT_DC;
	IS1_R = IS1_RC;
    } else {
	CRT_I = CRT_IM;
	CRT_D = CRT_DM;
	IS1_R = IS1_RM;
    }

    /* disable video */
    port_in(IS1_R);	
    port_out(0x00, ATT_IW);

    /* save text mode palette - first select palette index 0 */
    port_out(0, PEL_IR);

    /* read RGB components - index is autoincremented */
    for(i = 0; i < 256; i++) {
	for(j = 0; j < 10; j++) ;   /* delay (minimum 240ns) */
	text_red[i] = port_in(PEL_D);
	for(j = 0; j < 10; j++) ;   /* delay (minimum 240ns) */
	text_green[i] = port_in(PEL_D);
	for(j = 0; j < 10; j++) ;   /* delay (minimum 240ns) */
	text_blue[i] = port_in(PEL_D);
    }

    /* save text mode VGA registers */
    for (i = 0; i < CRT_C; i++) {
	 port_out(i, CRT_I);
	 text_regs[CRT+i] = port_in(CRT_D);
    }
    for (i = 0; i < ATT_C; i++) {
      	 port_in(IS1_R);
         port_out(i, ATT_IW);
         text_regs[ATT+i] = port_in(ATT_R);
    }
    for (i = 0; i < GRA_C; i++) {
       	 port_out(i, GRA_I);
       	 text_regs[GRA+i] = port_in(GRA_D);
    }
    for (i = 0; i < SEQ_C; i++) {
       	 port_out(i, SEQ_I);
       	 text_regs[SEQ+i] = port_in(SEQ_D);
    }
    text_regs[MIS] = port_in(MIS_R);

    /* shift to color emulation */
    CRT_I = CRT_IC;
    CRT_D = CRT_DC;
    IS1_R = IS1_RC;
    port_out(port_in(MIS_R)|0x01, MIS_W);

    /* save font data - first select a 16 color graphics mode */
    set_regs(g640x480x16_regs);

    /* save font data in plane 2 */
    port_out(0x04, GRA_I);
    port_out(0x02, GRA_D);
    for(i = 0; i < FONT_SIZE; i++) font_buf1[i] = VIDEO[i];

    /* save font data in plane 3 */
    port_out(0x04, GRA_I);
    port_out(0x03, GRA_D);
    for(i = 0; i < FONT_SIZE; i++) font_buf2[i] = VIDEO[i];
}


void vga_setpal(int index, int red, int green, int blue)
{
    volatile int i;

    /* select palette register */
    port_out(index, PEL_IW);

    /* write RGB components */
    for(i = 0; i < 10; i++) ;   /* delay (minimum 240ns) */
    port_out(red/4, PEL_D);
    for(i = 0; i < 10; i++) ;   /* delay (minimum 240ns) */
    port_out(green/4, PEL_D);
    for(i = 0; i < 10; i++) ;   /* delay (minimum 240ns) */
    port_out(blue/4, PEL_D);
}


void vga_getpal(int index, int *red, int *green, int *blue)
{
    int i;

    /* select palette register */
    port_out(index, PEL_IR);

    /* read RGB components */
    for(i = 0; i < 10; i++) ;   /* delay (minimum 240ns) */
    *red = 4*(int) port_in(PEL_D);
    for(i = 0; i < 10; i++) ;   /* delay (minimum 240ns) */
    *green = 4*(int) port_in(PEL_D);
    for(i = 0; i < 10; i++) ;   /* delay (minimum 240ns) */
    *blue = 4*(int) port_in(PEL_D);
}

void vga_clear(int c)
{
  register VIDEO_PTR p;
  register VIDEO_PTR last;

  /* set color c */
  port_out(c, GRA_I );
  port_out(0, GRA_D );

  /* set mode 0 */
  port_out(0x03, GRA_I );
  port_out(0, GRA_D );

  /* write to all bits */
  port_out(0x08, GRA_I );
  port_out(0xFF, GRA_D );

  last  = VIDEO + VGA_HEIGHT*LINE_BYTES;

  for(p = VIDEO; p < last; p++)  *p = 0;

}


void vga_init(int mode)
{ 
  // mode = 0: Text, 1: 640x480x16 
 
    int i;

    vga_initialize();
    
    if (mode == 0)  // TEXT
      { 
        //vga_clear(0);

        /* restore font data - first select a 16 color graphics mode */
        set_regs(g640x480x16_regs);

	/* disable Set/Reset Register */
    	port_out(0x01, GRA_I );
    	port_out(0x00, GRA_D );

        /* restore font data in plane 2 - necessary for all VGA's */
    	port_out(0x02, SEQ_I );
    	port_out(0x04, SEQ_D );
        for(i = 0; i < FONT_SIZE; i++) VIDEO[i] = font_buf1[i];

        /* restore font data in plane 3 - necessary for Trident VGA's */
    	port_out(0x02, SEQ_I );
    	port_out(0x08, SEQ_D );
        for(i = 0; i < FONT_SIZE; i++) VIDEO[i] = font_buf2[i];

        /* change register adresses if monochrome text mode */
        if (!color_text) {
            CRT_I = CRT_IM;
            CRT_D = CRT_DM;
            IS1_R = IS1_RM;
            port_out(port_in(MIS_R)&0xFE, MIS_W);
        }

	/* restore text mode VGA registers */
    	set_regs(text_regs);

        /* restore saved palette */
        for(i = 0; i < 256; i++)
            vga_setpal(i, text_red[i], text_green[i], text_blue[i]);

        VGA_WIDTH = 80;
        VGA_HEIGHT= 25;

      }
    else // graphics mode
      { 
        /* shift to color emulation */
        CRT_I = CRT_IC;
        CRT_D = CRT_DC;
        IS1_R = IS1_RC;
        port_out(port_in(MIS_R)|0x01, MIS_W);
        set_regs(g640x480x16_regs);

        /* set default palette */
        for(i = 0; i < 16; i++)
          vga_setpal(i, _R_[i], _G_[i], _B_[i]);

        //vga_clear(root_col);

        LINE_BYTES = 80;
        VGA_WIDTH = 640;
        VGA_HEIGHT= 480;
        VGA_DEPTH = 4;
        VGA_MAX_X = VGA_WIDTH - 1;
        VGA_MAX_Y = VGA_HEIGHT - 1;
      }

    /* enable video */
    port_in(IS1_R);
    port_out(0x20, ATT_IW);

}


int vga_width()  { return VGA_WIDTH; }
int vga_height() { return VGA_HEIGHT; }
int vga_depth()  { return VGA_DEPTH; }


void vga_set_mode(int m)
{ port_out(0x03, GRA_I );
  port_out(m<<3, GRA_D );
 }

void vga_set_color(int col)
{  port_out(0x00, GRA_I );
   port_out(col, GRA_D );
 }


void vga_pixel(int x, int y)
{ // clip to screen
  if (x < 0 || x > VGA_MAX_X || y < 0 || y > VGA_MAX_Y) return;

  VIDEO_PTR p = VIDEO + y*LINE_BYTES + (x>>3);
  port_out(8, GRA_I);
  port_out((0x80 >> (x & 7)), GRA_D);
  *p = *p;
 }


int vga_getpix(int x, int y)
{
  register int bit = 0x80 >> (x&7);
  register VIDEO_PTR byte = VIDEO + LINE_BYTES*y+(x>>3);
  register int c;

  /* set read mode 1 */
  port_out(5, GRA_I);
  port_out(8, GRA_D);

  for(c=0; c<16; c++)
  { port_out(2, GRA_I);
    port_out(c, GRA_D);
    if (*byte & bit)  break;
   }

  return c;
}


void vga_hline(int x0, int x1, int y)
{  // clip into screen
  if (y < 0 || y > VGA_MAX_Y || x1 < 0 || x0 > VGA_MAX_X) return;
  if (x0 < 0) x0 = 0;
  if (x1 > VGA_MAX_X) x1 = VGA_MAX_X;
  
  VIDEO_PTR first = VIDEO + LINE_BYTES*y + (x0>>3);
  VIDEO_PTR last  = VIDEO + LINE_BYTES*y + (x1>>3);

  port_out(8, GRA_I);

  if (first == last)
  { char byte  = 0xFF>>(x0&7);
    byte &= 0xFF<<((~x1)&7);
    port_out(byte, GRA_D);
    *first = *first;
    return;
   }

  port_out(0xFF>>(x0&7), GRA_D);
  *first = *first;

  port_out(0xFF<<((~x1)&7), GRA_D);
  *last = *last;

  port_out(0xFF, GRA_D);
  for(VIDEO_PTR p=first+1; p<last; p++) *p = *p;

 }


void vga_vline(int x, int y0, int y1)
{// clip into screen
  if (x < 0 || x > VGA_MAX_X || y1 < 0 || y0 > VGA_MAX_Y) return;
  if (y0 < 0) y0 = 0;
  if (y1 > VGA_MAX_Y) y1 = VGA_MAX_Y;

  port_out(8, GRA_I);
  port_out(128 >> (x&7), GRA_D);
  VIDEO_PTR last  = VIDEO + LINE_BYTES*y1 + (x>>3);
  VIDEO_PTR p;
  for(p = VIDEO + LINE_BYTES*y0 + (x>>3); p <= last; p+=LINE_BYTES)  *p = *p;

}

void vga_box(int x0, int y0, int x1, int y1)
{ 
  for(int y=y0; y<=y1; y++) vga_hline(x0,x1,y);
 }



static void write_byte(VIDEO_PTR q, unsigned char byte, int D)
{
  if (D==0)
    { port_out(byte, GRA_D);
      *q = *q;
     }
  else
     { port_out(0x00, GRA_I );  // set_color(white)
       port_out(0, GRA_D );

       port_out(0x03, GRA_I ); // set_mode(src)
       port_out(0, GRA_D );

       port_out(0xFF, GRA_D);  // clear
       *q = *q;

       port_out(0x03, GRA_I ); // set_mode(or)
       port_out(16, GRA_D );
       
       for(int i = 0; i < D; i++)
       { port_out(0x00, GRA_I );  // set_color(1<<i)
         port_out(1<<i, GRA_D );
         port_out(byte, GRA_D);
         *q = *q;
        }
     }
}


static void vga_bytes(int x, int y, unsigned char* ptr, int len, int D=0)
{ 
  if (len < 1) return;

  // clip into display

  int x1 = x + 8*len - 1;

  if (y < 0 || y > VGA_MAX_Y || x1 < 0 || x > VGA_MAX_X) return;

  if (x < 0) 
  { ptr -= x/8;
    x = 0;
   }

  if (x1 > VGA_MAX_X) x1 = VGA_MAX_X;

  len = (x1 - x + 1)/8;

  // shift to byte boundary 

  int s1 = x % 8;
  int s2 = 8 - s1;

  unsigned char  last_c = 0;
  unsigned char* stop = ptr + len;

  VIDEO_PTR q = VIDEO + LINE_BYTES*y + x/8;
  port_out(8, GRA_I);

  while (ptr < stop)
  { unsigned char c = *ptr++;
    unsigned char byte = (c >> s1) | (last_c << s2);
    port_out(byte, GRA_D);
    *q = *q;
    last_c = c;
    q++;
   }
  port_out(last_c<<s2, GRA_D);
  *q = *q;
}


void vga_bitmap(int x, int y, unsigned char* pm, int width, int height)
{ while (height--)
  { vga_bytes(x,y++,pm,width);
    pm += width;
   }
 }



//------------------------------------------------------------------------------
// pixrects and images
//------------------------------------------------------------------------------

struct pixrect
{ unsigned char** plane;
  int width;
  int bwidth;
  int height;
  int depth;
  int offset;
  pixrect(int, int, int, int);
 ~pixrect();
};

pixrect::pixrect(int left, int top, int right, int bottom)
{ offset = left % 8;
  width  = right - left + 1;
  height = bottom - top + 1;
  depth  = VGA_DEPTH;
  bwidth = width/8;
  if (width % 8) bwidth++;
  if (offset) bwidth++;
  plane = new unsigned char*[depth];
  for(int i=0; i<depth; i++) 
      plane[i] =  new unsigned char[height*bwidth];
 }


pixrect::~pixrect()
{ for(int i=0; i<depth; i++) delete[] plane[i];
  delete[] plane;
 }


void vga_delimage(char* image) { delete (pixrect*)image; }


char* vga_getimage(int left, int top, int right, int bottom)
{ 
  if (left < 0) left = 0;
  if (top  < 0) top = 0;
  if (right  > VGA_MAX_X) right = VGA_MAX_X;
  if (bottom > VGA_MAX_Y) bottom = VGA_MAX_Y;

  pixrect* pr = new pixrect(left,top,right,bottom);

  unsigned char mask1 = 0xFF >> pr->offset;
  unsigned char mask2 = 0xFF << ((8-pr->offset) % 8);

  /* set read mode 0 */
  port_out(5, GRA_I);
  port_out(0, GRA_D);

  /* read planes 0 to 3 */

  for(int i = 0; i < pr->depth; i++)
  { unsigned char* p = pr->plane[i];
    VIDEO_PTR first = VIDEO + LINE_BYTES*top + left/8;
    VIDEO_PTR last  = first + pr->bwidth - 1;

    port_out(4, GRA_I);
    port_out(i, GRA_D);
  
    for(int y=top; y<=bottom; y++)
    { VIDEO_PTR q = first; 
      *p++ = (*q++) & mask1;
      while (q < last) *p++ = *q++;
      *p++ = (*q++) & mask2;
      first += LINE_BYTES;
      last  += LINE_BYTES;
     }
   }

  return (char*)pr;
  
}


void vga_putimage(int left, int top, char* image)
{ 
  pixrect* pr = (pixrect*)image;

  int right  = left + pr->width - 1; 
  int bottom = top  + pr->height - 1; 
  int left0  = left - pr->offset;

  int bw = pr->bwidth;

  unsigned char** pfirst = new unsigned char*[pr->depth];

  for(int i = 0; i < pr->depth; i++) pfirst[i] = pr->plane[i];

  unsigned char* p0 = pr->plane[0];
  unsigned char* p1 = pr->plane[1];
  unsigned char* p2 = pr->plane[2];
  unsigned char* p3 = pr->plane[3];

  for(int y=top; y <= bottom; y++) 
  { vga_set_color(0);
    vga_set_mode(0);
    vga_hline(left,right,y); // clear line
    vga_set_mode(2); // or
    for(int i = 0; i < pr->depth; i++)
    { vga_set_color(1<<i);
      vga_bytes(left0,y,pfirst[i],bw);
      pfirst[i] += bw;
     }
   }
  
}


void vga_copyimage(int left,int top,int right,int bottom,int x,int y)
{  
  int width  = right-left+1;
  int height = bottom-top+1;

  if(x < 0) 
  { left += x;
    width -= x;
    x = 0;
   }

  if(y < 0) 
  { top += y;
    height -= y;
    y = 0;
   }

  if(x+width >= vga_width()) width = vga_width() - x - 1;
  if(y+height >= vga_height()) height = vga_height() - y -1;

  int  d1  = LINE_BYTES;
  int  d2  = d1 * (height-1);

  VIDEO_PTR first1 = VIDEO + LINE_BYTES*y + x/8;
  VIDEO_PTR last1  = VIDEO + LINE_BYTES*y + (x+width-1)/8;
  VIDEO_PTR first  = VIDEO + LINE_BYTES*top + left/8;
  VIDEO_PTR last   = VIDEO + LINE_BYTES*top + (left+width-1)/8;
  VIDEO_PTR p;
  VIDEO_PTR q;

  if (y > top)
  { first1 +=  d2;
    last1  +=  d2;
    first  +=  d2;
    last   +=  d2;
    d1  = -d1;
   }

  // set write mode 1 
  port_out(5, GRA_I);
  port_out(1, GRA_D);

  for(int i=0; i<height; i++)
  { 
    if (x <= left)
      for(q=first,p=first1; q <= last;  q++,p++) *p = *q;
    else
      for(q=last,p=last1;   q >= first; q--,p--) *p = *q;

    first1 += d1;
    first  += d1;
    last1  += d1;
    last   += d1;
   }

  /* set write mode 0 */
  port_out(5, GRA_I);
  port_out(0, GRA_D);

 }

