/*
 * Copyright (c) 2005, Swedish Institute of Computer Science
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * This file is part of the Contiki operating system.
 *
 * @(#)$Id: ctk-term-out.c,v 1.1 2006/06/17 22:41:16 adamdunkels Exp $
 */
#include "libconio.h"
#include "ctk-term-int.h"
#include <string.h>
#include <stdio.h> // sprintf

#define PRINTF(x)

/*-----------------------------------------------------------------------------------*/
/* 
 * #defines and enums
 */
/*-----------------------------------------------------------------------------------*/

#define CHARS_WIDTH    LIBCONIO_CONF_SCREEN_WIDTH
#define CHARS_HEIGHT   LIBCONIO_CONF_SCREEN_HEIGHT

/*-----------------------------------------------------------------------------------*/
/* 
 * Local variables
 */
/*-----------------------------------------------------------------------------------*/

/* ANSI/VT100 colors
  0 - None
  1 - Bold (inc. inten)
  4 - Underscore
  7 - Reverse
  x0 - black
  x1 - red
  x2 - green
  x3 - yellow
  x4 - blue
  x5 - magenta
  x6 - cyan
  x7 - white
  x = 3 fg x = 4 bg
*/
#if 0 /* Colorfull theme */
static const char backgroundcolor[] = "\033[0;37;40m";

static const char wincol[] = "\033[0;37;40m";
static const char wincol_f[] = "\033[0;1;37;40m";
static const char wincol_d[] = "\033[0;30;47m";

static const char sepcol[] = "\033[0;37;40m";
static const char sepcol_f[] = "\033[0;1;37;40m";
static const char sepcol_d[] = "\033[0;30;47m";

static const char labcol[] = "\033[0;37;40m";
static const char labcol_f[] = "\033[1;37;40m";
static const char labcol_d[] = "\033[0;30;47m";

static const char butcol[] = "\033[0;37;40m";
static const char butcol_w[] = "\033[0;30;47m";
static const char butcol_f[] = "\033[0;1;37;40m";
static const char butcol_fw[] = "\033[0;1;37;46m";
static const char butcol_d[] = "\033[0;30;47m";
static const char butcol_dw[] = "\033[0;37;46m";

static const char hlcol[] = "\033[0;4;36;40m";
static const char hlcol_w[] = "\033[0;4;30;47m";
static const char hlcol_f[] = "\033[0;1;4;36;40m";
static const char hlcol_fw[] = "\033[0;1;4;37;46m";
static const char hlcol_d[] = "\033[0;4;34;47m";
static const char hlcol_dw[] = "\033[0;4;37;46m";

static const char iconcol[] = "\033[0;32;40m";
static const char iconcol_w[] = "\033[0;30;42m";

static const char menucolor[] = "\033[0;37;43m";
static const char activemenucolor[] = "\033[0;1;37;43m";
#endif

#if 1 /* B/W theme */
static const char backgroundcolor[] = "\033[0m";

static const char wincol[] = "\033[0m";
static const char wincol_f[] = "\033[0;1m";
static const char wincol_d[] = "\033[0;7m";

static const char sepcol[] = "\033[0m";
static const char sepcol_f[] = "\033[0;1m";
static const char sepcol_d[] = "\033[0;7m";

static const char labcol[] = "\033[0m";
static const char labcol_f[] = "\033[0;1m";
static const char labcol_d[] = "\033[0;7m";

static const char butcol[] = "\033[0m";
static const char butcol_w[] = "\033[0m";
static const char butcol_f[] = "\033[0;1m";
static const char butcol_fw[] = "\033[0;1;7m";
static const char butcol_d[] = "\033[0;7m";
static const char butcol_dw[] = "\033[0m";

static const char hlcol[] = "\033[0;4m";
static const char hlcol_w[] = "\033[0;4;7m";
static const char hlcol_f[] = "\033[0;1;4m";
static const char hlcol_fw[] = "\033[0;1;4;7m";
static const char hlcol_d[] = "\033[0;4;7m";
static const char hlcol_dw[] = "\033[0;4m";

static const char iconcol[] = "\033[0m";
static const char iconcol_w[] = "\033[0;7m";

static const char menucolor[] = "\033[0;7m";
static const char activemenucolor[] = "\033[0m";

#endif

static const char* const colortheme[] =
{
    backgroundcolor,
    
    /* Window colors */
    wincol, wincol, wincol_f, wincol_f, wincol_d, wincol_d,

    /* Separator colors. */
    sepcol, sepcol, sepcol_f, sepcol_f, sepcol_d, sepcol_d,    

    /* Label colors. */
    labcol, labcol, labcol_f, labcol_f, labcol_d, labcol_d,    

    /* Button colors. */
    butcol, butcol_w, butcol_f, butcol_fw, butcol_d, butcol_dw,    

    /* Hyperlink colors. */
    hlcol, hlcol_w, hlcol_f, hlcol_fw, hlcol_d, hlcol_dw,

    /* Textentry colors. */
    butcol, butcol_w, butcol_f, butcol_fw, butcol_d, butcol_dw,

    /* Icon colors */
    iconcol, iconcol_w, iconcol, iconcol_w, iconcol, iconcol_w,
    
    /* Menu colors. */
    menucolor, activemenucolor, activemenucolor
  };

static unsigned char  
  screen[CHARS_WIDTH * CHARS_HEIGHT],
  colorscreen[CHARS_WIDTH * CHARS_HEIGHT];

/*-----------------------------------------------------------------------------------*/
/* 
 * Add a character to the screen buffer 
 */
/*-----------------------------------------------------------------------------------*/
void
ctk_term_out_update_screen(unsigned char  xpos, 
			   unsigned char  ypos, 
			   unsigned char  c, 
			   unsigned char  color)
{
  if (c < 0x20) c = 0x20;
  screen[xpos + ypos * CHARS_WIDTH] = c;
  colorscreen[xpos + ypos * CHARS_WIDTH] = color;
}

/*-----------------------------------------------------------------------------------*/
/* 
 * Check if there are any updated pending. If so, make the first one current
 */
/*-----------------------------------------------------------------------------------*/
static void 
check_updates(struct ctk_term_state* ts)
{
  if (ts->updates_current != NULL) return;
  ts->updates_current = ctk_term_update_dequeue(ts);
  if (ts->updates_current != NULL) {
    ts->x = ts->updates_current->x;
    ts->y = ts->updates_current->y;
    ts->w = ts->updates_current->w;
    ts->h = ts->updates_current->h;
    ts->x1 = ts->x2 = ts->x;
    ts->y1 = ts->y2 = ts->y;
  }
}

/*-----------------------------------------------------------------------------------*/
/** \internal
 * Adds a cursor position change to buffer. Returns 0 if string doesn't fit else
 * number of bytes actually written is returned.
 *
 * \param x X coordinate (screen coordinates)
 * \param y Y coordinate (screen coordinates)
 * \param buf Output buffer
 * \param maxlen Maximum number of bytes to store in buffer
 */
/*-----------------------------------------------------------------------------------*/
static unsigned short
move_to(unsigned char x, unsigned char y, unsigned char* buf, unsigned short maxlen)
{
  if (maxlen < 14) return 0;
  return (unsigned short)sprintf((char*)buf, "\033[%d;%dH", y+1, x+1);
}

/*-----------------------------------------------------------------------------------*/
/** \internal
 * Adds a attribute string to buffer. Returns 0 if string doesn't fit else
 * number of bytes actually written is returned.
 *
 * \param c Color number
 * \param buf Output buffer
 * \param maxlen Maximum number of bytes to store in buffer
 */
/*-----------------------------------------------------------------------------------*/
static unsigned short
set_color(unsigned char c, unsigned char* buf, unsigned short maxlen)
{
  int len = strlen((const char*)colortheme[c]);
  if (maxlen < len) return 0;
  memcpy(buf, colortheme[c], len);
  return len;
}

/*-----------------------------------------------------------------------------------*/
/**
 * Stores terminal data in buffer provided by caller. Returns number of bytes written
 * to the output buffer.
 *
 * \param ts State information
 * \param buf Output buffer
 * \param maxlen Maximum number of bytes to store in buffer
 */
/*-----------------------------------------------------------------------------------*/
unsigned short 
ctk_term_send(struct ctk_term_state* ts, 
	      unsigned char* buf, 
	      unsigned short maxlen)
{
  unsigned char x, y, x0;
  unsigned char col, c;
  unsigned short tmp;
  unsigned short totlen;
  
  check_updates(ts);
  
  if (ts->updates_current == NULL) return 0;

  x0 = ts->x1;
  col = ts->c1;
  totlen = 0;
  /* Loop across the update region starting at (x1,y1) */
  for(y = ts->y1; y < ts->y + ts->h; ++y) {
    for(x = x0; x < ts->x + ts->w; ++x) {
      /* New line ? */
      if (x == ts->x) {
	/* Move cursor to start of line */
	tmp = move_to(x,y,buf,maxlen);
	if (tmp == 0) goto loopend;
	buf += tmp;
	totlen += tmp;
	maxlen -= tmp;
      }
      /* Check color */
      c = colorscreen[x + y * CHARS_WIDTH];
      if (c != col) {
	PRINTF(("colorchange at (%d, %d) to %d\n", x,y,c));
	/* Send new color information */
	tmp = set_color(c, buf, maxlen);
	if (tmp == 0) goto loopend;
	col = c;
	buf += tmp;
	totlen += tmp;
	maxlen -= tmp;
      }
      /* Check remaining space */
      if (maxlen < 1) goto loopend;
      /* Add character */
      *buf = screen[x + y * CHARS_WIDTH];
      buf++;
      maxlen--;
      totlen++;
    }
    x0 = ts->x;
  }
loopend:
  /* Always save current color state */
  ts->c2 = col;  
  PRINTF(("ending loop at (%d, %d)\n", x,y));
  /* Check if done */
  if (x == ts->x+ts->w && y == ts->y+ts->h) {
    /* Signal done with this update */
    ts->x2 = ts->y2 = 0;
  }
  else {
    /* Not done. Save state */
    ts->x2 = x;
    ts->y2 = y;
  }
  return totlen;
}

/*-----------------------------------------------------------------------------------*/
/**
 * Called by client when the data returned by ctk_term_send() are successfully sent.
 *
 * \param ts State information
 */
/*-----------------------------------------------------------------------------------*/
void ctk_term_sent(struct ctk_term_state* ts)
{
  if (ts->updates_current != NULL) {
    /* Check if current update done */
    if (ts->x2 == 0 && ts->y2 == 0) {
      /* Yes, free it */
      ctk_term_update_free(ts, ts->updates_current);
      ts->updates_current = NULL;
    }
    else {
      /* Nop. Update start posititions */
      ts->x1 = ts->x2;
      ts->y1 = ts->y2;
    }
    ts->c1 = ts->c2;
  }
}