#include <stdarg.h>
#include <rfb/keysym.h>
#include "VNConsole.h"

#define DEBUG(x)

unsigned char colourMap16[16*3]={
  /* 0 black       #000000 */ 0x00,0x00,0x00,
  /* 1 maroon      #800000 */ 0x80,0x00,0x00,
  /* 2 green       #008000 */ 0x00,0x80,0x00,
  /* 3 khaki       #808000 */ 0x80,0x80,0x00,
  /* 4 navy        #000080 */ 0x00,0x00,0x80,
  /* 5 purple      #800080 */ 0x80,0x00,0x80,
  /* 6 aqua-green  #008080 */ 0x00,0x80,0x80,
  /* 7 light grey  #c0c0c0 */ 0xc0,0xc0,0xc0,
  /* 8 dark grey   #808080 */ 0x80,0x80,0x80,
  /* 9 red         #ff0000 */ 0xff,0x00,0x00,
  /* a light green #00ff00 */ 0x00,0xff,0x00,
  /* b yellow      #ffff00 */ 0xff,0xff,0x00,
  /* c blue        #0000ff */ 0x00,0x00,0xff,
  /* d pink        #ff00ff */ 0xff,0x00,0xff,
  /* e light blue  #00ffff */ 0x00,0xff,0xff,
  /* f white       #ffffff */ 0xff,0xff,0xff
};

void MakeColourMap16(vncConsolePtr c)
{
  rfbColourMap* colourMap=&(c->screen->colourMap);
  if(colourMap->count)
    free(colourMap->data.bytes);
  colourMap->data.bytes=malloc(16*3);
  memcpy(colourMap->data.bytes,colourMap16,16*3);
  colourMap->count=16;
  colourMap->is16=FALSE;
  c->screen->serverFormat.trueColour=FALSE;
}

void vcDrawOrHideCursor(vncConsolePtr c)
{
  int i,j,w=c->screen->paddedWidthInBytes;
  char *b=c->screen->frameBuffer+c->y*c->cHeight*w+c->x*c->cWidth;
  for(j=c->cy1;j<c->cy2;j++)
    for(i=c->cx1;i<c->cx2;i++)
      b[j*w+i]^=0x0f;
  rfbMarkRectAsModified(c->screen,
			c->x*c->cWidth+c->cx1,c->y*c->cHeight+c->cy1,
			c->x*c->cWidth+c->cx2,c->y*c->cHeight+c->cy2);
  c->cursorIsDrawn=c->cursorIsDrawn?FALSE:TRUE;
}

void vcDrawCursor(vncConsolePtr c)
{
  if(c->cursorActive && c->y<c->height && c->x<c->width) {
    /* rfbLog("DrawCursor: %d,%d\n",c->x,c->y); */
    vcDrawOrHideCursor(c);
  }
}

void vcHideCursor(vncConsolePtr c)
{
  if(c->currentlyMarking)
    vcUnmark(c);
  vcDrawOrHideCursor(c);
}

void vcMakeSureCursorIsDrawn(rfbClientPtr cl)
{
  vncConsolePtr c=(vncConsolePtr)cl->screen->screenData;
  if(!c->dontDrawCursor)
    vcDrawCursor(c);
}

vncConsolePtr vcGetConsole(int *argc,char **argv,
			   int width,int height,rfbFontDataPtr font
#ifdef USE_ATTRIBUTE_BUFFER
			   ,rfbBool withAttributes
#endif
			   )
{
  vncConsolePtr c=(vncConsolePtr)malloc(sizeof(vncConsole));

  c->font=font;
  c->width=width;
  c->height=height;
  c->screenBuffer=(char*)malloc(width*height);
  memset(c->screenBuffer,' ',width*height);
#ifdef USE_ATTRIBUTE_BUFFER
  if(withAttributes) {
    c->attributeBuffer=(char*)malloc(width*height);
    memset(c->attributeBuffer,0x07,width*height);
  } else
    c->attributeBuffer=NULL;
#endif
  c->x=0;
  c->y=0;
  c->wrapBottomToTop=FALSE;
  c->cursorActive=TRUE;
  c->cursorIsDrawn=FALSE;
  c->dontDrawCursor=FALSE;
  c->inputBuffer=(char*)malloc(1024);
  c->inputSize=1024;
  c->inputCount=0;
  c->selection=0;
  c->selectTimeOut=40000; /* 40 ms */
  c->doEcho=TRUE;

  c->wasRightButtonDown=FALSE;
  c->currentlyMarking=FALSE;

  rfbWholeFontBBox(font,&c->xhot,&c->cHeight,&c->cWidth,&c->yhot);
  c->cWidth-=c->xhot;
  c->cHeight=-c->cHeight-c->yhot;

  /* text cursor */
  c->cx1=c->cWidth/8;
  c->cx2=c->cWidth*7/8;
  c->cy2=c->cHeight-1-c->yhot+c->cHeight/16;
  if(c->cy2>=c->cHeight)
    c->cy2=c->cHeight-1;
  c->cy1=c->cy2-c->cHeight/8;
  if(c->cy1<0)
    c->cy2=0;

  c->screen=
    rfbGetScreen(argc,argv,c->cWidth*c->width,c->cHeight*c->height,8,1,1);
  c->screen->screenData=(void*)c;
  c->screen->displayHook=vcMakeSureCursorIsDrawn;
  c->screen->frameBuffer=
    (char*)malloc(c->screen->width*c->screen->height);
  memset(c->screen->frameBuffer,c->backColour,
	 c->screen->width*c->screen->height);
  c->screen->kbdAddEvent=vcKbdAddEventProc;
  c->screen->ptrAddEvent=vcPtrAddEventProc;
  c->screen->setXCutText=vcSetXCutTextProc;

  MakeColourMap16(c);
  c->foreColour=0x7;
  c->backColour=0;

  rfbInitServer(c->screen);

  return(c);
}

#include <rfb/rfbregion.h>

/* before using this function, hide the cursor */
void vcScroll(vncConsolePtr c,int lineCount)
{
  int y1,y2;
  rfbScreenInfoPtr s=c->screen;

  if(lineCount==0)
    return;

  /* rfbLog("begin scroll\n"); */
  vcHideCursor(c);
  c->dontDrawCursor=TRUE;

  if(lineCount>=c->height || lineCount<=-c->height) {
    y1=0; y2=s->height;
  } else if(lineCount>0) {
    y1=s->height-lineCount*c->cHeight; y2=s->height;
    rfbDoCopyRect(s,0,0,s->width,y1,0,-lineCount*c->cHeight);
    memmove(c->screenBuffer,
	    c->screenBuffer+(c->height-lineCount)*c->width,
	    (c->height-lineCount)*c->width);
#ifdef USE_ATTRIBUTE_BUFFER
    if(c->attributeBuffer)
	    memmove(c->attributeBuffer,
		    c->attributeBuffer+(c->height-lineCount)*c->width,
		    (c->height-lineCount)*c->width);
#endif
  } else {
    y1=0; y2=-lineCount*c->cHeight;
    rfbDoCopyRect(s,0,y2,s->width,s->height,0,-lineCount*c->cHeight);
    memmove(c->screenBuffer-lineCount*c->width,
	    c->screenBuffer,
	    (c->height+lineCount)*c->width);
#ifdef USE_ATTRIBUTE_BUFFER
    if(c->attributeBuffer)
	    memmove(c->attributeBuffer-lineCount*c->width,
		    c->attributeBuffer,
		    (c->height+lineCount)*c->width);
#endif
  }

  c->dontDrawCursor=FALSE;
  memset(s->frameBuffer+y1*s->width,c->backColour,(y2-y1)*s->width);
  rfbMarkRectAsModified(s,0,y1-c->cHeight,s->width,y2);
  memset(c->screenBuffer+y1/c->cHeight*c->width,' ',
	 (y2-y1)/c->cHeight*c->width);
#ifdef USE_ATTRIBUTE_BUFFER
  if(c->attributeBuffer)
	  memset(c->attributeBuffer+y1/c->cHeight*c->width,0x07,
		 (y2-y1)/c->cHeight*c->width);
#endif
  /* rfbLog("end scroll\n"); */
}  

void vcCheckCoordinates(vncConsolePtr c)
{
  if(c->x>=c->width) {
    c->x=0;
    c->y++;
  }
  if(c->y>=c->height) {
    if(c->wrapBottomToTop)
      c->y=0;
    else {
      vcScroll(c,c->y+1-c->height);
      c->y=c->height-1;
    }
  }
}

void vcPutChar(vncConsolePtr c,unsigned char ch)
{
#ifdef USE_ATTRIBUTE_BUFFER
  if(c->attributeBuffer) {
    unsigned char colour=c->attributeBuffer[c->x+c->y*c->width];
    vcPutCharColour(c,ch,colour&0x7,colour>>4);
  } else
#endif
    vcPutCharColour(c,ch,c->foreColour,c->backColour);
}

void vcPutCharColour(vncConsolePtr c,unsigned char ch,unsigned char foreColour,unsigned char backColour)
{
  rfbScreenInfoPtr s=c->screen;
  int j,x,y;

  vcHideCursor(c);
  if(ch<' ') {
    switch(ch) {
    case 7:
    case 13:
      break;
    case 8: /* BackSpace */
      if(c->x>0) {
	c->x--;
	vcPutChar(c,' ');
	c->x--;
      }
      break;
    case 10: /* return */
      c->x=0;
      c->y++;
      vcCheckCoordinates(c);
      break;
    case 9: /* tabulator */
      do {
	vcPutChar(c,' ');
      } while(c->x%8);
      break;
    default:
       rfbLog("putchar of unknown character: %c(%d).\n",ch,ch);
      vcPutChar(c,' ');
    }
  } else {
#ifdef USE_ATTRIBUTE_BUFFER
    if(c->attributeBuffer)
      c->attributeBuffer[c->x+c->y*c->width]=foreColour|(backColour<<4);
#endif
    x=c->x*c->cWidth;
    y=c->y*c->cHeight;
    for(j=y+c->cHeight-1;j>=y;j--)
      memset(s->frameBuffer+j*s->width+x,backColour,c->cWidth);
    rfbDrawChar(s,c->font,
		x-c->xhot+(c->cWidth-rfbWidthOfChar(c->font,ch))/2,
		y+c->cHeight-c->yhot-1,
		ch,foreColour);
    c->screenBuffer[c->y*c->width+c->x]=ch;
    c->x++;
    rfbMarkRectAsModified(s,x,y-c->cHeight+1,x+c->cWidth,y+c->cHeight+1);
    vcCheckCoordinates(c);
  }
}

void vcPrint(vncConsolePtr c,unsigned char* str)
{
  while(*str) {
    vcPutChar(c,*str);
    str++;
  }
}

void vcPrintColour(vncConsolePtr c,unsigned char* str,unsigned char foreColour,unsigned char backColour)
{
  while(*str) {
    vcPutCharColour(c,*str,foreColour,backColour);
    str++;
  }
}

void vcPrintF(vncConsolePtr c,char* format,...)
{
  va_list args;
  char buf[4096];
  va_start(args, format);
  vsprintf(buf, format, args);
  vcPrint(c,buf);
  va_end(args);
}

void vcPrintFColour(vncConsolePtr c,unsigned char foreColour,unsigned char backColour,char* format,...)
{
  va_list args;
  char buf[4096];
  va_start(args, format);
  vsprintf(buf, format, args);
  vcPrintColour(c,buf,foreColour,backColour);
  va_end(args);
}

char vcGetCh(vncConsolePtr c)
{
  if(c->inputCount>0) {
    char ch;
    ch=c->inputBuffer[0];
    c->inputCount--;
    if(c->inputCount>0)
      memmove(c->inputBuffer,c->inputBuffer+1,c->inputCount);
    return(ch);
  } else
    return(0);
}

char vcGetChar(vncConsolePtr c)
{
  while(rfbIsActive(c->screen) && c->inputCount==0)
    vcProcessEvents(c);
  return(vcGetCh(c));
}

char *vcGetString(vncConsolePtr c,char *buffer,int bufferSize)
{
  char *bufferBackup=c->inputBuffer;
  int i,count=bufferSize-1;

  if(count>c->inputCount)
    count=c->inputCount;
  for(i=1;i<count && bufferBackup[i-1]!='\n';i++);
  if(i<count || i==bufferSize-1) {
    memcpy(buffer,bufferBackup,i);
    buffer[i+1]=0;
    c->inputCount-=i;
    memmove(bufferBackup,bufferBackup+i+2,c->inputCount);
    return(buffer);
  }
  memcpy(buffer,bufferBackup,c->inputCount);
  count=c->inputSize;
  c->inputSize=bufferSize;
  c->inputBuffer=buffer;
  while(rfbIsActive(c->screen)
      && c->inputCount<bufferSize-1 && buffer[c->inputCount-1]!='\n')
    vcProcessEvents(c);
  buffer[c->inputCount]=0;
  c->inputBuffer=bufferBackup;
  c->inputSize=count;
  c->inputCount=0;
  return(buffer);
}

void vcKbdAddEventProc(rfbBool down,rfbKeySym keySym,rfbClientPtr cl)
{
  vncConsolePtr c=(vncConsolePtr)cl->screen->screenData;
  if(down) {
    if(c->inputCount<c->inputSize) {
      if(keySym<0 || keySym>0xff) {
	if(keySym==XK_Return) keySym='\n';
	else if(keySym==XK_BackSpace) keySym=8;
        else if(keySym==XK_Tab) keySym=9;
	else keySym=0;
      }
      if(keySym>0) {
	if(keySym==8) {
	  if(c->inputCount>0)
	    c->inputCount--;
	} else
	  c->inputBuffer[c->inputCount++]=(char)keySym;
	if(c->doEcho)
	  vcPutChar(c,(unsigned char)keySym);
      }
    }
  }
}

void vcPtrAddEventProc(int buttonMask,int x,int y,rfbClientPtr cl)
{
  vncConsolePtr c=(vncConsolePtr)cl->screen->screenData;

  if(c->wasRightButtonDown) {
    if((buttonMask&4)==0) {
      if(c->selection) {
	char* s;
	for(s=c->selection;*s;s++) {
	  c->screen->kbdAddEvent(1,*s,cl);
	  c->screen->kbdAddEvent(0,*s,cl);
	}
      }
      c->wasRightButtonDown=0;
    }
  } else if(buttonMask&4)
    c->wasRightButtonDown=1;
      
  if(buttonMask&1) {
    int cx=x/c->cWidth,cy=y/c->cHeight,pos;
    if(cx<0) cx=0; else if(cx>=c->width) cx=c->width-1;
    if(cy<0) cy=0; else if(cy>=c->height) cy=c->height-1;
    pos=cy*c->width+cx;

    /* mark */
    if(!c->currentlyMarking) {
      c->currentlyMarking=TRUE;
      c->markStart=pos;
      c->markEnd=pos;
      vcToggleMarkCell(c,pos);
    } else {
      DEBUG(rfbLog("markStart: %d, markEnd: %d, pos: %d\n",
	      c->markStart,c->markEnd,pos));
      if(c->markEnd!=pos) {
	if(c->markEnd<pos) {
	  cx=c->markEnd; cy=pos;
	} else {
	  cx=pos; cy=c->markEnd;
	}
	if(cx<c->markStart) {
	  if(cy<c->markStart)
	    cy--;
	} else
	  cx++;
	while(cx<=cy) {
	  vcToggleMarkCell(c,cx);
	  cx++;
	}
	c->markEnd=pos;
      }
    }
  } else if(c->currentlyMarking) {
    int i,j;
    if(c->markStart<c->markEnd) {
      i=c->markStart; j=c->markEnd+1;
    } else {
      i=c->markEnd; j=c->markStart;
    }
    if(c->selection) free(c->selection);
    c->selection=(char*)malloc(j-i+1);
    memcpy(c->selection,c->screenBuffer+i,j-i);
    c->selection[j-i]=0;
    vcUnmark(c);
    rfbGotXCutText(c->screen,c->selection,j-i);
  }
  rfbDefaultPtrAddEvent(buttonMask,x,y,cl);
}

void vcSetXCutTextProc(char* str,int len, struct _rfbClientRec* cl)
{
  vncConsolePtr c=(vncConsolePtr)cl->screen->screenData;

  if(c->selection) free(c->selection);
  c->selection=(char*)malloc(len+1);
  memcpy(c->selection,str,len);
  c->selection[len]=0;
}

void vcToggleMarkCell(vncConsolePtr c,int pos)
{
  int x=(pos%c->width)*c->cWidth,
    y=(pos/c->width)*c->cHeight;
  int i,j;
  rfbScreenInfoPtr s=c->screen;
  char *b=s->frameBuffer+y*s->width+x;
  for(j=0;j<c->cHeight;j++)
    for(i=0;i<c->cWidth;i++)
      b[j*s->width+i]^=0x0f;
  rfbMarkRectAsModified(c->screen,x,y,x+c->cWidth,y+c->cHeight);
}

void vcUnmark(vncConsolePtr c)
{
  int i,j;
  c->currentlyMarking=FALSE;
  if(c->markStart<c->markEnd) {
    i=c->markStart; j=c->markEnd+1;
  } else {
    i=c->markEnd; j=c->markStart;
  }
  for(;i<j;i++)
    vcToggleMarkCell(c,i);
}

void vcProcessEvents(vncConsolePtr c)
{
  rfbProcessEvents(c->screen,c->selectTimeOut);
}

