/* 
 *     XaoS, a fast portable realtime fractal zoomer 
 *                  Copyright (C) 1996,1997 by
 *
 *      Jan Hubicka          (hubicka@paru.cas.cz)
 *      Thomas Marsh         (tmarsh@austin.ibm.com)
 *
 *     OS/2 VIO driver by
 *      Thomas A. K. Kjaer   (takjaer@imv.aau.dk)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include "aconfig.h"
#ifdef OS2VIO_DRIVER
#define INCL_VIO
#define INCL_KBD
#define INCL_MOU
#include <os2.h>
#include <sys/hw.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "ui.h"
#include "ui_os2viofont.h"

struct ui_driver os2vio_driver;

static int currentbuff = 0;
static int width=320, height=200;

static int page_size = 64000;

static int buttons = 0, keys = 0;
static char *buffers[2] = {NULL, NULL};

static PVIOPHYSBUF phys_buf;
static VIOMODEINFO orig_mode, vio_mode;
static PCH ScrPtr;

static HMOU hMou;
static USHORT mouwait = MOU_NOWAIT;
static USHORT oldDeviceStatus;
static int mousetype=NORMALMOUSE;
static char screen[8*8];

static void vio_mousetype(int m)
{
  mousetype = m;
}

static int vio_init()
{
  USHORT mask = 0x7F;
  USHORT status;
  PTRLOC ptrLoc = {0, 0};

  if (MouOpen(NULL,&hMou) && MouOpen("POINTER$", &hMou))
    hMou = 0;

  MouFlushQue(hMou);
  MouSetEventMask(&mask, hMou);

  MouGetDevStatus(&oldDeviceStatus, hMou);

  /* Turn off mouse pointer drawing, BEFORE switching to graphics mode! */
  status = 0x100;
  MouSetDevStatus(&status, hMou);

  orig_mode.cb=sizeof(VIOMODEINFO);
  VioGetMode((PVIOMODEINFO) &orig_mode,(HVIO) 0);
  MouSetPtrPos(&ptrLoc, hMou);

  memcpy((void *) &vio_mode, (const void *) &orig_mode, sizeof(VIOMODEINFO));
  vio_mode.fbType = 3;
  vio_mode.color = 8;
  vio_mode.col = 40;
  vio_mode.row = 25;
  vio_mode.hres = width;
  vio_mode.vres = height;
  vio_mode.fmt_ID = 0;
  vio_mode.attrib = 0;

  if (VioSetMode(&vio_mode, (HVIO) 0)) {
    printf("VioSetMode error\n");
    return 0;
  }

  phys_buf = malloc(sizeof(VIOPHYSBUF));
  phys_buf->pBuf=(unsigned char *) 0xA0000;
  phys_buf->cb=page_size;
  if (VioGetPhysBuf(phys_buf, 0)) {
    printf("VioGetPhysBuf error\n");
    return 0;
  }

  ScrPtr=MAKEP(phys_buf->asel[0],0);
  memset(ScrPtr, 0, page_size);

  return 1;
}

static void vio_get_size(int *wi, int *he)
{
  *wi = width; *he = height;
}

#define ABS(a) (((a)<0) ? -(a) : (a))

static int vga_drawline(int x1, int y1, int x2, int y2, int c)
{
  int dx = x2 - x1;
  int dy = y2 - y1;
  int ax = ABS(dx) << 1;
  int ay = ABS(dy) << 1;
  int sx = (dx >= 0) ? 1 : -1;
  int sy = (dy >= 0) ? 1 : -1;
  
  int x = x1;
  int y = y1;
  
  if (ax > ay) {
    int d = ay - (ax >> 1);
    while (x != x2) {
      ScrPtr[y * width + x] = c;
      
      if (d > 0 || (d == 0 && sx == 1)) {
	y += sy;
	d -= ax;
      }
      x += sx;
      d += ay;
    }
  } else {
    int d = ax - (ay >> 1);
    while (y != y2) {
      ScrPtr[y * width + x] = c;
      
      if (d > 0 || (d == 0 && sy == 1)) {
	x += sx;
	d -= ay;
      }
      y += sy;
      d += ax;
    }
  }
  ScrPtr[y * width + x] = c;
  
  return 0;
  
}

static void draw_mouse(int x, int y, int clear)
{
  static int oldx=3, oldy=3;
  int buffstart, i;
  char status;

  if(mousetype!=REPLAYMOUSE) {
    VioScrLock(1, &status, (HVIO) 0);
    if (x < 3)
      x = 3;
    if (y < 3)
      y = 3;
    if (x > width - 5)
      x = width - 5;
    if (y > height - 4)
      y = height - 4;
    
    if ((oldx != x || oldy != y) && clear) {
      /* copy rectangle over mouse pointer */
      buffstart = (oldy - 3) * width + (oldx - 3);
      for (i = 0; i < 8; i++) {
	memcpy((PCH)(ScrPtr+buffstart),
	       (char *)(buffstart+buffers[currentbuff]), 8);
	buffstart += width;
      }
    }
    
    vga_drawline(x - 3, y - 3, x + 3, y + 3, 255);
    vga_drawline(x - 3, y + 3, x + 3, y - 3, 255);
    vga_drawline(x + 1 - 3, y - 3, x + 1 + 3, y + 3, 253);
    vga_drawline(x + 1 - 3, y + 3, x + 1 + 3, y - 3, 253);
    
    oldx = x;
    oldy = y;
    VioScrUnLock((HVIO)0);
  }
}

/* check for keys
 *
 * returns ascii-code
 */
static int check_keys(KBDKEYINFO k)
{
  if(!(k.fbStatus&0x02)) { /* ASCII code */
    if(k.chChar==27) return 'q';

    return k.chChar;
  }

  return 0;  /* not handled */
}

static int check_ckeys(KBDKEYINFO k)
{
  int c;

  if(k.fbStatus&0x02) { /* SCAN code */
    c = k.chScan;

    if (c == 75) { /* left */
      keys |= 1;
    } else {
      keys &= ~1;
    }
    if (c == 77) { /* right */
      keys |= 2;
    } else {
      keys &= ~2;
    }
    if (c == 72) { /* up */
      keys |= 4;
    } else {
      keys &= ~4;
    }
    if (c == 80) { /* down */
      keys |= 8;
    } else {
      keys &= ~8;
    }
  }
  return keys;
}


static void vio_processevents(int wait, int *x, int *y, int *b, int *k)
{
  KBDKEYINFO kbdkeyinfo;
  MOUEVENTINFO mouEvent;

  *k = 0;
  *b = buttons;
  if (wait) {
    while(1) {
      KbdCharIn(&kbdkeyinfo, IO_NOWAIT, 0);
      if (kbdkeyinfo.fbStatus!=0) {
	if(check_keys(kbdkeyinfo))
	  ui_key(kbdkeyinfo.chChar);
        *k = check_ckeys(kbdkeyinfo);
        return;
      }
      MouReadEventQue(&mouEvent, &mouwait, hMou);
      if(!((mouEvent.fs==0)&&(mouEvent.col==0)&&(mouEvent.row==0))) {
	if(mouEvent.fs&0x60)
	  buttons |= BUTTON2;
	else
	  buttons &= ~BUTTON2;
	if((mouEvent.fs&0x18)||(mouEvent.fs&0x60 && mouEvent.fs&0x06))
	  buttons |= BUTTON3;
	else
	  buttons &= ~BUTTON3;
	if(mouEvent.fs&0x06)
	  buttons |= BUTTON1;
	else
	  buttons &= ~BUTTON1;
	if(mouEvent.fs&0x01)
	  buttons = 0;
	*x=mouEvent.col;
	*y=mouEvent.row;
	*b=buttons;

        draw_mouse(*x, *y, 1);
        return;
      }
      DosSleep(0);
    }
  } else {
    KbdCharIn(&kbdkeyinfo, IO_NOWAIT, 0);
    if (kbdkeyinfo.fbStatus!=0) {
      if(check_keys(kbdkeyinfo))
  	ui_key(kbdkeyinfo.chChar);
      *k = check_ckeys(kbdkeyinfo);
    }
    os2vio_driver.getmouse(x, y, b);
  }
  return;
}

static void vio_getmouse(int *x, int *y, int *b)
{
  PTRLOC ptrLoc;
  MOUEVENTINFO mouEvent;

  MouGetPtrPos(&ptrLoc, hMou);
  MouReadEventQue(&mouEvent, &mouwait, hMou);
  *x = ptrLoc.col; *y = ptrLoc.row;
  if((mouEvent.fs==0)&&(mouEvent.col==0)&&(mouEvent.row==0)) { /* no event */
    *b = buttons;
    draw_mouse(ptrLoc.col, ptrLoc.row, 1);
    return;
  }
  if(mouEvent.fs&0x60)
    buttons |= BUTTON2;
  else
    buttons &= ~BUTTON2;
  if((mouEvent.fs&0x18)||(mouEvent.fs&0x60 && mouEvent.fs&0x06))
    buttons |= BUTTON3;
  else
    buttons &= ~BUTTON3;
  if(mouEvent.fs&0x06)
    buttons |= BUTTON1;
  else
    buttons &= ~BUTTON1;
  if(mouEvent.fs&0x01)
    buttons = 0;

  *b = buttons;
  draw_mouse(ptrLoc.col, ptrLoc.row, 1);
}

static void vio_uninitialise()
{
  MouSetDevStatus(&oldDeviceStatus, hMou);
  MouClose(hMou);
  VioSetMode(&orig_mode, (HVIO) 0);
}

static void vio_setpalette(ui_palette pal, int start, int end) {
    int i;
    _portaccess(0x03c8, 0x03c9);
    for(i=0;i<=end-start;i++) {
        _outp8(0x03c8, i);
  	_outp8(0x03c9, pal[i][0]/4);
  	_outp8(0x03c9, pal[i][1]/4);
  	_outp8(0x03c9, pal[i][2]/4);
    }
    _outp8(0x03c8, 253);
    _outp8(0x03c9, 0);
    _outp8(0x03c9, 0);
    _outp8(0x03c9, 0);
    _outp8(0x03c8, 254);
    _outp8(0x03c9, 0);
    _outp8(0x03c9, 0);
    _outp8(0x03c9, 40);
    _outp8(0x03c8, 255);
    _outp8(0x03c9, 255);
    _outp8(0x03c9, 255);
    _outp8(0x03c9, 255);
}


static void vio_print(int x, int y, char *text)
{
  char c, t, status;
  int i, idx, cx, cy;
  fprintf(stderr, "x=%d, y=%d\n", x, y);

  if (y > height - os2vio_driver.textheight)
    return;

  VioScrLock(1, &status, (HVIO) 0);
  while((c=*text++)!='\0') {
    cy = y;
    if (cy >= 200) {
	VioScrUnLock((HVIO)0);
	return;
      }
    for (i=0; i < os2vio_driver.textheight; i++) {
      cx = x;
      if (cx >= 320) {
	VioScrUnLock((HVIO)0);
	return;
      }
      t = font8[c*8+i];
      for (idx = 0; idx < 8; idx++) {
	if (t & 0x80)
	  ScrPtr[cy*width+cx++] = 255;
	else
	  ScrPtr[cy*width+cx++] = 254;
	t = t << 1;
      }
      cy++;
    }
    x+=os2vio_driver.textwidth;
  }	
  VioScrUnLock((HVIO)0);
}

static void vio_display()
{
  PTRLOC ptrLoc;
  char status;

  /* lock screen, if possible */
  VioScrLock(1, &status, (HVIO) 0);
  MouGetPtrPos(&ptrLoc, hMou);
  memcpy(ScrPtr, buffers[currentbuff], page_size);

  draw_mouse(ptrLoc.col, ptrLoc.row, 0);
  VioScrUnLock((HVIO)0);
}

static int vio_alloc(char **b1, char **b2)
{
  *b1 = buffers[0] = (char *)malloc(width*height);
  *b2 = buffers[1] = (char *)malloc(width*height);
  currentbuff = 0;

  return(width);
}

void vio_free(char *b1, char *b2) 
{
  free(buffers[0]);
  free(buffers[1]);
}

static void vio_flip_buffers() 
{
  currentbuff ^= 1;
}

static void vio_clear(void)
{
  memset(ScrPtr, 0, page_size);
}

static char *helptext[] =
{
  "OS/2 VIO DRIVER VERSION 1.1            ", 
  "=========================              ", 
  " I really like this driver, because    ",
  " I much prefer full screen zooming     ",
  " instead of small 320x200 window on    ",
  " the OS/2 Desktop.                     ",
  " This XaoS driver is for OS/2 VIO mode ",
  " and is fully featured.                ",
  " The following problems can ocour:     ",
  " o It doesn't start                    ",
  "    Thats can not be true. You can see ",
  "    this text!                         ",
  "                                       ",
  " As can be seen, I managed to get the  ",
  " MouAPI working in VIO fullscreen...   ",
  " What a fight is was..                 ",
  "                                       ", 
  "      OS/2 VIO driver was done by      ", 
  "      Thomas A. K. Kjaer  (C) 1996/97  ", 
  "      takjaer@imv.aau.dk               "
};

#define UGLYTEXTSIZE (sizeof(helptext)/sizeof(char *))
static struct params params[] =
{
  {NULL, 0, NULL, NULL}
};

struct ui_driver os2vio_driver = 
{
  "OS2VIO", 
  vio_init, 
  vio_get_size, 
  vio_processevents, 
  vio_getmouse, 
  vio_uninitialise, 
  NULL,
  vio_setpalette,
  vio_print, 
  vio_display, 
  vio_alloc, 
  vio_free, 
  vio_flip_buffers, 
  vio_clear,
  vio_mousetype,
  NULL,
  NULL,
  8,
  8,
  helptext, 
  UGLYTEXTSIZE, 
  params,
  FULLSCREEN | UPDATE_AFTER_RESIZE | PALETTE_ROTATION | ROTATE_INSIDE_CALCULATION,
  0.0, 0.0,
  0, 0,
  UI_C256,
  0, 252, 253
};

#endif
