/* -*- c -*- ------------------------------------------------------------- *
 *   
 *   Copyright 2004 Murali Krishnan Ganapathy - All Rights Reserved
 *
 *   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, Inc., 53 Temple Place Ste 330,
 *   Boston MA 02111-1307, USA; either version 2 of the License, or
 *   (at your option) any later version; incorporated herein by reference.
 *
 * ----------------------------------------------------------------------- */

#include "string.h"
#include "biosio.h"

/*
 * Note: don't use "r" or "g" for 8-bit values.  Some versions of gcc
 * will actually try to generate x86-64 registers that way!  Use
 * "abcd" or "abcdmi", respectively.  Newer gccs have the newer "q"
 * and "Q" constraints, but older gccs don't know those.
 */

/* BIOS Assisted output routines */

void bios_int10(void)
{
  asm volatile("pushl %ebp ; int $0x10 ; popl %ebp");
}

/* Print character and attribute at cursor */
static inline void asm_cprint(char chr, char attr, int times, char disppage)
{
    asm volatile("movb $0x09,%%ah ; call bios_int10"
		 : "+a" (chr) : "b" (attr + (disppage << 8)), "c" (times));
}

void cprint(char chr,char attr,int times,char disppage)
{
    asm_cprint(chr,attr,times,disppage);
}

static inline void asm_setdisppage(char num)
{
    asm volatile("movb $0x05,%%ah ; call bios_int10"
		 : "+a" (num));
}

void setdisppage(char num) // Set the display page to specified number
{
    asm_setdisppage(num);
}

static inline char asm_getdisppage(void)
{
    char page;
    
    asm("movb $0x0f,%%ah ; "
	"call bios_int10 ; "
	"movb %%bh,%0"
	: "=abcdm" (page) : : "eax");
    return page;
}

char getdisppage() // Get current display page 
{
    return asm_getdisppage();
}

static inline void asm_getpos(char *row, char *col, char page)
{
    asm("movb %2,%%bh ; "
	"movb $0x03,%%ah ; "
	"call bios_int10 ; "
	"movb %%dh,%0 ; "
	"movb %%dl,%1"
	: "=m" (*row), "=m" (*col)
	: "abcdmi" (page)
	: "eax", "ebx", "ecx", "edx");
}

void getpos(char * row, char * col, char page)
{
    asm_getpos(row,col,page);
}

static inline void asm_gotoxy(char row,char col, char page)
{
    asm volatile("movb %1,%%bh ; "
		 "movb $0x02,%%ah ; "
		 "call bios_int10"
		 : : "d" ((row << 8) + col), "abcdmi" (page)
		 : "eax", "ebx");
}

void gotoxy(char row,char col, char page)
{
    asm_gotoxy(row,col,page);
}

static inline unsigned char asm_sleep(unsigned int milli)
{ // ah = 86, int 15, cx:dx = microseconds
  // mul op16 : dx:ax = ax * op16
  unsigned char ans;
  asm volatile ("mul  %%cx; "
		"xchg %%dx, %%ax; "
		"movw %%ax, %%cx; "
		"movb $0x86, %%ah;"
		"int $0x15;"
		"setnc %0"
		: "=r" (ans) 
		: "a" (milli), "c" (1000)
		: "edx");
  return ans;
}

unsigned char sleep(unsigned int msec)
{
 return asm_sleep(msec);
}

void asm_beep()
{
  // For a beep the page number (bh) does not matter, so set it to zero
  asm volatile("movw $0x0E07, %%ax;"
	       "xor  %%bh,%%bh;"
	       "call bios_int10"
	       : : : "eax","ebx");
}

void beep()
{
  asm_beep();
}

static inline void asm_putchar(char x, char attr,char page)
{
    asm volatile("movb %1,%%bh;"
		 "movb %2,%%bl;"
		 "movb $0x09,%%ah;"
		 "movw $0x1, %%cx;"
		 "call bios_int10"
		 : "+a" (x)
		 : "abcdmi" (page), "acdmi" (attr)
		 : "ebx", "ecx", "ebp");
}

void putch(char x, char attr, char page)
{
  asm_putchar(x,attr,page);
}

void scrollup()
{
  unsigned short dx = (getnumrows()<< 8) + getnumcols();
  
  asm volatile("movw $0x0601, %%ax;"
	       "movb $0x07, %%bh;"
	       "xor %%cx, %%cx;"
	       "call bios_int10"
	       : "+d" (dx)
	       : : "eax","ebx","ecx");
}

/* Print a C string (NUL-terminated) */
void csprint(const char *str,char attr)
{
    char page = asm_getdisppage();
    char newattr=0,cha,chb;
    char row,col;

    asm_getpos(&row,&col,page);
    while ( *str ) {
      switch (*str) 
	{
	case '\b':
	  --col;
	  break;
	case '\n':
	  ++row;
	  break;
	case '\r':
	  col=0;
	  break;
	case BELL: // Bell Char
	  asm_beep();
	  break;
	case CHRELATTR: // change attribute (relatively)
	case CHABSATTR: // change attribute (absolute)
	  cha = *(str+1);
	  chb = *(str+2);
	  if ((((cha >= '0') && (cha <= '9')) || 
	       ((cha >= 'A') && (cha <= 'F'))) &&
	      (((chb >= '0') && (chb <= '9')) || 
	       ((chb >= 'A') && (chb <= 'F')))) // Next two chars are legal
	    {
	      if ((cha >= 'A') && (cha <= 'F'))
		cha = cha - 'A'+10;
	      else cha = cha - '0';
	      if ((chb >= 'A') && (chb <= 'F'))
		chb = chb - 'A'+10;
	      else chb = chb - '0';
	      newattr = (cha << 4) + chb;
	      attr = (*str == CHABSATTR ? newattr : attr ^ newattr);
	      str += 2; // Will be incremented again later
	    }
	  break;
	default:
	  asm_putchar(*str, attr, page);
	  ++col;
	}
      if (col > getnumcols())
	{
	  ++row;
	  col=0;
	}
      if (row > getnumrows())
	{
	  scrollup();
	  row= getnumrows();
	}
      asm_gotoxy(row,col,page);
      str++;
    }
}

void clearwindow(char top, char left, char bot, char right, char page, char fillchar, char fillattr)
{
    char x;
    for (x=top; x < bot+1; x++)
    {
        gotoxy(x,left,page);
        asm_cprint(fillchar,fillattr,right-left+1,page);
    }
}

void cls(void)
{
    gotoxy(0,0,getdisppage());
    asm_cprint(' ',0x07,getnumrows()*getnumcols(),getdisppage());
}

char asm_inputc(char *scancode)
{
  unsigned short ax;

  asm volatile("movb $0x10,%%ah ; "
	       "int $0x16"
	       : "=a" (ax));
  
  if (scancode)
      *scancode = (ax >> 8);
  
  return (char)ax;
}
   
char inputc(char * scancode)
{
    return asm_inputc(scancode);
}

static inline void asm_cursorshape(char start, char end)
{
    asm volatile("movb $0x01,%%ah ; int $0x10"
		 : : "c" ((start << 8) + end) : "eax");
}

void cursoroff(void)
{
    asm_cursorshape(32,32);
}

void cursoron(void)
{
    asm_cursorshape(6,7);
}

char bkspstr[] = " \b$";
char eolstr[] = "\n$";

static inline char asm_getchar(void)
{
    char v;
    
    /* Get key without echo */
    asm("movb $0x08,%%ah ; int $0x21" : "=a" (v));
    
    return v;
}

#define GETSTRATTR 0x07

// Reads a line of input from stdin. Replace CR with NUL byte
void getstring(char *str, unsigned int size)
{
    char c;
    char *p = str;
    char page = asm_getdisppage();
    char row,col;

    while ( (c = asm_getchar()) != '\r' ) {
	switch (c) {
	case '\0':		/* Extended char prefix */
	    asm_getchar();	/* Drop */
	    break;
	case '\b':
	    if ( p > str ) {
		p--;
		csprint("\b \b",GETSTRATTR);
	    }
	    break;
	case '\x15':		/* Ctrl-U: kill input */
	    while ( p > str ) {
		p--;
		csprint("\b \b",GETSTRATTR);
	    }
	    break;
	default:
	    if ( c >= ' ' && (unsigned int)(p-str) < size-1 ) {
	      *p++ = c;
	      asm_getpos(&row,&col,page);
	      asm_putchar(c, GETSTRATTR, page);
	      asm_gotoxy(row,col+1,page);
	    }
	    break;
	}
    }
    *p = '\0';
    csprint("\r\n",GETSTRATTR);
}

static inline void asm_setvideomode(char mode)
{
    /* This BIOS function is notoriously register-dirty,
       so push/pop around it */
    asm volatile("pushal ; xorb %%ah,%%ah ; int $0x10 ; popal"
		 : : "a" (mode) );
}

void setvideomode(char mode)
{
    asm_setvideomode(mode);
}

static inline unsigned char asm_checkkbdbuf()
{
  unsigned char ans;

  asm volatile("movb $0x11, %%ah;"
	       "int $0x16 ;"
	       "setnz %0;"
	       : "=abcdm" (ans)
	       : 
	       : "eax");
  return ans;
}

unsigned char checkkbdbuf()
{
  return asm_checkkbdbuf();
}

void clearkbdbuf()
{
  while (asm_checkkbdbuf()) asm_inputc(NULL);
}
