/*
  Console
*/

#include "console.h"

#include "alloc.h"
#include "head.h"
#include "infocom.h"
#include "os.h"
#include "print.h"
#include "queue.h"
#include "wio.h"

console con;
bool newlines_are_significant;

static int quick_redraw;
#define reformatting 0

static void wipe_slack(int x1, int y1, int x2, int y2)
{
  if(y1 < y2 && x1 < x2)
  {
    int i, j;
    console_cell blank;
    start_update();
    /* Build a blank character */
    blank.text = ' ';
    blank.attr = con.cursor.attr;
    blank.attr.font = FONT_NORMAL;
    /* Blank the first row */
    for(j = x1; j < x2; ++j)
      con.row[y1].slack[j] = blank;
    /* Blat that into the subsequent rows */
    for(i = y1 + 1; i < y2; ++i)
      os_mcpy(&con.row[i].slack[x1], &con.row[y1].slack[x1], (x2-x1) * sizeof(blank));
    /* Touch the lines and adjust cached lengths */      
    for(i = y1; i < y2; ++i)
    {
      int last = con.row[i].held; /* Index of last known character */
      /* Line needs redrawing if characters up to the last were altered */
      if(x1 <= last)
        touch(i);
      /* Line has been truncated if the last character has changed */
      if(x1 <= last && last < x2)
        con.row[i].held = x1;
    }
    finish_update();
  }
}

static void wipe_fixed(int x1, int y1, int x2, int y2)
{
  int i;
  console_cell blank;
  blank.text = 0;
  blank.attr = con.cursor.attr;
  blank.attr.font = FONT_NORMAL;
  start_update();
  for(i = y1; i < y2; ++i)
    if(con.row[i].fix)
    {
      int j;
      bool f;
      for(j = x1; j < x2; ++j)
        con.row[i].fixed[j] = blank;
      f = 0;
      for(j = 0; j < x1 && !f; ++j)
        f = con.row[i].fixed[j].text != 0;
      for(j = x2; j < MAX_FIXED && !f; ++j)
        f = con.row[i].fixed[j].text != 0;
      con.row[i].fix = f;
      touch(i);
    }
  finish_update();
}

int find_eol(int row)
{
  int i = con.row[row].held;
  while(i && is_a_space(con.row[row].slack[i - 1])) --i;
  con.row[row].held = i;
  return i;
}


void init_console(int rows, int fixed)
{
  if(!con.cursor.attr.font)
  {
    con.cursor.attr.font = FONT_NORMAL;
    con.cursor.attr.fore = 2;
    con.cursor.attr.back = 9;
  }
  con.shape.fixed   = min(MAX_FIXED, fixed);
  con.shape.height  = min(MAX_HEIGHT, rows);
  /* con.top        = 0; */
  con.bottom        = con.shape.height;
  hd_set_size(con.shape.height, con.shape.fixed);
  {
    erase_window(0, MAX_HEIGHT);
    con.cursor.y = con.shape.height - 1;
  }
}

void start_update(void)
{
  ++quick_redraw;
}

void finish_update(void)
{
  if(--quick_redraw <= 0)
    force_update();
}

void touch(int row)
{
  if(quick_redraw > 0)
    con.row[row].flag = 1;
  else
    repaint(row);
}

void force_update(void)
{
  int y = con.shape.height;
  while(--y >= 0)
    if(con.row[y].flag)
      repaint(y);
  redraw_cursor();
}

void set_xy(int x, int y)
{
  hide_cursor();
  if(x < 0) x = 0;
  if(y < 0) y = 0;
  if(con.cursor.align)
  {
    con.cursor.x = min(x, MAX_FIXED - 1);
    con.cursor.y = min(y, MAX_HEIGHT - 1);
  }
  else
  {
    con.cursor.x = min(x, MAX_SLACK - 1);
    con.cursor.y = min(y, con.shape.height - 1);
  }
  show_cursor();
}

void goto_xy(int x, int y)
{
  con.cursor.align = 1;
  set_xy(x, y);
}

void put_char(word c)
{
  extern bool enable_screen;

  if(!enable_screen)
    return;
  if(is_font_request(c))
  {
    con.cursor.attr.font = c;
  }
  else if(is_attr_request(c))
  {
    byte b = request_get_data(c);
    word fore = attr_get_fore(b);
    word back = attr_get_back(b);
    if(fore == 1) fore = 2;
    if(2 <= fore && fore <= 9)
      con.cursor.attr.fore = fore;
    if(back == 1) back = 9;
    if(2 <= back && back <= 9)
      con.cursor.attr.back = back;
  }
  else if(kind(c) == 0)
  {
    start_update();
    switch(c)
    {
      case 0:
        c = ' ';
      /* Fall through ... */
      default:
        if(c >= 32)
        {
          console_row *r  = &con.row[con.cursor.y];
          console_cell *p = con.cursor.align
                          ? &r->fixed[con.cursor.x]
                          : &r->slack[con.cursor.x];
          if(con.cursor.align)
            r->fix = 1;
          else if(con.cursor.x >= r->held)
            r->held = con.cursor.x + 1;
          r->flag = 1;
          p->text = c;
          p->attr = con.cursor.attr;
          if(p->attr.font != FONT_FIXED
          && p->attr.font != FONT_FIXED_REVS
          && (con.cursor.align || hd_get_fixed()))
            p->attr.font = p->attr.font == FONT_REVS ? FONT_FIXED_REVS : FONT_FIXED;
          set_xy(con.cursor.x + 1, con.cursor.y);
        }
        break;
      case '\r':
        set_xy(0, con.cursor.y);
        break;
      case '\f':
        /* Enable justification on current line */
        con.row[con.cursor.y].just = 1;
        con.row[con.cursor.y].flag = 1;
        break;
           case '\n':
        if(con.cursor.align
        || con.cursor.y + 1 < con.bottom)
        {
          set_xy(0, con.cursor.y + 1);
        }
        else if(con.top < con.bottom)
        {
          int i;
          console_cell *s = con.row[con.top].slack;
          /* Scroll the slack plane and clear its last line */
          for(i = con.top; i < con.bottom - 1; ++i)
          {
            con.row[i].slack = con.row[i+1].slack;
            con.row[i].flag  = con.row[i+1].flag;
            con.row[i].just  = con.row[i+1].just;
            con.row[i].held  = con.row[i+1].held;
          }
          con.row[con.bottom - 1].slack = s;
          con.row[con.bottom - 1].just  = 0;
          wipe_slack(0, con.bottom - 1, MAX_SLACK, con.bottom);
          set_xy(0, con.bottom - 1);
          /* Scroll the fixed plane (possibly more than before) unless we are reformatting */
          if(!reformatting)
          {
            int fixed_bottom = con.bottom == con.shape.height ? MAX_HEIGHT : con.bottom;
            if(con.top < fixed_bottom)
            {
              console_cell *f = con.row[con.top].fixed;
              int b = con.row[con.top].fix;
              for(i = con.top; i < fixed_bottom - 1; ++i)
              {
                con.row[i].fixed = con.row[i+1].fixed;
                con.row[i].fix   = con.row[i+1].fix;
              }
              con.row[fixed_bottom - 1].fixed = f;
              con.row[fixed_bottom - 1].fix   = b;
              wipe_fixed(0, fixed_bottom - 1, MAX_FIXED, fixed_bottom);
            }
            scrollup();
          }
        }
        else
        {
          set_xy(0, con.cursor.y);
          erase_to_eoln();
        }
        break;
    }
    finish_update();
  }
}

void use_window(int window)
{
  static int active_window = FULL_SCREEN;
  extern word status_height;
  if(window == STATUS_WINDOW && active_window != STATUS_WINDOW)
    start_update();
  if(window != STATUS_WINDOW && active_window == STATUS_WINDOW)
    finish_update();
  switch(window)
  {
    case TEXT_WINDOW:
      con.top          = status_height;
      con.bottom       = con.shape.height;
      break;
    case STATUS_WINDOW:
      con.top          = 0;
      con.bottom       = status_height;
      break;
    case FULL_SCREEN:
      con.top          = 0;
      con.bottom       = con.shape.height;
      break;
  }
  active_window = window;
}

void erase_to_eoln(void)
{
  if(con.cursor.align)
    wipe_fixed(con.cursor.x, con.cursor.y, MAX_FIXED, con.cursor.y + 1);
  else
    wipe_slack(con.cursor.x, con.cursor.y, MAX_SLACK, con.cursor.y + 1);
}

void erase_window(word top_of_window, word bottom_of_window)
{
  start_update();
  wipe_fixed(0, top_of_window, MAX_FIXED, bottom_of_window);
  wipe_slack(0, top_of_window, MAX_SLACK, bottom_of_window);
  newlines_are_significant = 0;
  finish_update();
}

void swap_font_status(int *font)
{
  int t = con.cursor.attr.font; con.cursor.attr.font = *font; *font = t;
}

static console_context saved_cursor[2];

void save_attributes(int context)
{
  saved_cursor[context] = con.cursor;
}

void restore_attributes(int context)
{
  con.cursor = saved_cursor[context];
}

void clip_attributes(void)
{
  saved_cursor[0].x = con.cursor.x;
  saved_cursor[0].y = con.cursor.y;
}

void get_xy(int *x, int *y)
{
  *x = con.cursor.x;
  *y = con.cursor.y;
}

#ifdef OLD
int is_a_space(const console_cell *c)
{
  return c->text == ' ' && c->attr.font != FONT_FIXED_REVS;
}
#endif

void allocate_console(void)
{
  int i, lump = 8; /* Do this many lines at a time */
  console_cell *s, *f;
  for(i = 0; i < MAX_HEIGHT; ++i)
  {
    int r = i % lump;
    if(r == 0)
    {
      s = alloc0(lump * MAX_SLACK, console_cell);
      f = alloc0(lump * MAX_FIXED, console_cell);
    }
    con.row[i].held  = MAX_SLACK;
    con.row[i].slack = s + r * MAX_SLACK;
    con.row[i].fixed = f + r * MAX_FIXED;
  }
}

