#include <assert.h>
#include "zori.h"
#include "zori_widget.h"
#include "zori_console.h"
#include "draw.h"


struct zori_console * zori_widget_to_console(struct zori_widget * widget) {
  if (!zori_widget_is_type(widget, ZORI_WIDGET_TYPE_CONSOLE)) return NULL;  
  return ZORI_CONTAINER_OF(widget, struct zori_console, widget);
}

/* Helper struct that keeps track of the BYTE positions within 
a c string or USTR where a line or word starts or ends with a given maxwidth. */
struct zori_textinfo {
  int from_char;
  int start_char;
  int stop_char;
  int maxwidth;
};


/* Creates a temporary ustr as per al_ref_ustr but with 
 start and stop as code positions, not byte positions. */
static const USTR * 
ustrinfo_newref(
  USTR_INFO * uinfo, const USTR * ustr, int start, int stop) {
  return ustr_refustr(uinfo, ustr, 
                      ustr_offset(ustr, start),
                      ustr_offset(ustr, stop)
                     );
}



/* Creates a temporary ustr that refers ustr but respecds the bounds of the 
textinfo (start_char and enc_char) */
const USTR * zori_textinfo_refustr(struct zori_textinfo * self, 
                                USTR_INFO  * uinfo,
                                const USTR * ustr) { 
  return ustrinfo_newref(uinfo, ustr, self->start_char, self->stop_char);
}


struct zori_textinfo * 
zori_textinfo_wordfromtext(struct zori_textinfo * self, USTR * ustr, zori_font * font) {
  int found;
  int start_pos;
  int end_pos;
  int now_char;
  int end_char;
  int len;
  int ch;
  if(!self) return NULL;
  now_char         = self->from_char;
  self->start_char = now_char;
  ch = ustr_getnext(ustr, &now_char); 
  while (ch > 0) { 
    switch(ch) { 
      case ' ': /* Found a space, here the word ends, include the space. */
        self->stop_char = now_char;
        return self;
      case '\n': /* A newline ends a word, include the newline. */
        self->stop_char = now_char;
        return self;
      default: /* Other characters mean the word is not finished yet. */
        break;
    }
    /* XXX: Should handle the case for languages that use no spaces, 
    * by checking with al_get_ustr_width but it's not a pressing matter yet.
    */
    ch = ustr_getnext(ustr, &now_char); 
  } 
  // no word found, just set end here and be done. 
  self->stop_char = now_char;
  /* return nULL to signify end */
  return NULL;
}


/** Prints a ustring, since puts or printf print too much some
 times for a refstring.*/
static int ustr_print(USTR * word) {
  size_t index;
    for(index = 0; index < ustr_length(word) ; index++) {
      putchar(ustr_get(word, index));
    }
  return index;
}


/** Gets the positions of the next line of text fort he given Unicode string 
and store them in the given info. If the info's from is set to 0 or less, 
the first line is assumed, otherwise, the line will be started from "from".
Uses the given font to determine the width of the text as it is built.
*/
struct zori_textinfo * 
zori_textinfo_linefromtext(struct zori_textinfo * self, USTR * ustr, zori_font * font) {
  struct zori_textinfo wordinfo;
  USTR_INFO  lineuinfo;
  const USTR     * line;

  USTR_INFO  worduinfo = { 0, 0, 0};
  const USTR     * word;
  int ch;
  int index;
  int width;
  int last_stop;
  self->start_char   = self->from_char;
  wordinfo.from_char = self->from_char;
  
  while(zori_textinfo_wordfromtext(&wordinfo, ustr, font)) {
    word = zori_textinfo_refustr(&wordinfo, &worduinfo, ustr);
    line = ustrinfo_newref(&lineuinfo, ustr, self->start_char, wordinfo.stop_char);
    width = al_get_ustr_width(font, line);
    if (width > self->maxwidth) { 
      /* XXX: handle case of text overflow by bluntly retuning the word as is.
      Should split single word based on length too.
      There is overflow if this is still the first word as see from wordinfo_start_char.
      */
      if (wordinfo.start_char == self->start_char) {
        self->stop_char  = wordinfo.stop_char;
      } else { 
        self->stop_char  = wordinfo.start_char;
      }
      return self;
    }
    // If we get here, the word way still end on a newline character 
    // check this case. XXX: It works like this because 
    // stop_char is a bit wonky... it points at the first character of the 
    // next word in this case...
    ch = ustr_get(ustr, wordinfo.stop_char - 1);
    if (ch == '\n') {
      self->start_char = self->from_char;
      self->stop_char  = wordinfo.stop_char - 1;
      return self;
    }
    wordinfo.from_char = wordinfo.stop_char;
  }
  /* if we get here, the whole string fits. */
  self->start_char = self->from_char;
  self->stop_char  = wordinfo.stop_char;
  /* Return NULL to tell caller text has been completely split up. */
  return NULL;
}



#define ZORI_WIDGET_BORDER 3 

/** Draws a rounded frame as background for a widget. */
void zori_widget_drawroundframe(struct zori_widget * self) {
  if(!self) return;
  draw_roundframe(zori_widget_x(self), zori_widget_y(self), 
                  zori_widget_w(self), zori_widget_h(self),
                  ZORI_WIDGET_BORDER,
                  zori_widget_forecolor(self), zori_widget_backcolor(self));
}


/** Skips the text info to the next word or line of text. Must be called 
when looping over zori_textinfo_linefromtext. */
struct zori_textinfo * zori_textinfo_next(struct zori_textinfo * self) { 
  if(!self) return NULL;
  self->from_char  = self->stop_char + 1;
  self->start_char = self->from_char;
  return self;
}



/* Converts a widget to a console. Only works if the pointer is wrapped correctly,
 by a console. */
struct zori_console * zori_widget_console(struct zori_widget * widget) { 
  if (!widget) return NULL;
  return ZORI_CONTAINER_OF(widget, struct zori_console, widget);
}


/** Sets the console's command function and data. */
void zori_console_command_(struct zori_console * self, zori_console_command * command, void * data) {
  self->command      = command;
  self->command_data = data;
}

/** Let the console perform a command if possible. returns nonzero on error,
zero if OK. */
int zori_console_docommand(struct zori_console * self, const char * text) {
  if(!self) return -1;
  if(!self->command) return -2;
  return self->command(&self->widget, text, self->command_data);
}



/** Adds a line of text to the console. */
int zori_console_addstr(struct zori_console * self, const char * str) {
  if(!self) return -1;
  if(!ustrlist_shiftcstr(&self->text, str)) { 
    return -3;
  }  
  while(ustrlist_size(&self->text) >= self->max) { // remove last node(s)
    ustrlist_droplast(&self->text);
  }
  return ustrlist_size(&self->text);
}

/** Adds a line of text to the console. */
int zori_console_addustr(struct zori_console * self, const USTR * ustr) {
  if(!self) return -1;
  if(!ustrlist_shiftustr(&self->text, ustr)) { 
    return -3;
  }  
  while(ustrlist_size(&self->text) >= self->max) { // remove last node(s)
    ustrlist_droplast(&self->text);
  }
  return ustrlist_size(&self->text);
}


/** Puts a string on the console .*/
int zori_console_puts(struct zori_console * self, const char * str) {
  int index;
  int size     = strlen(str);
  int leftsize = size;
  int lines = 0;
  USTR_INFO uinfo;
  struct zori_textinfo info = { 0, 0, 0, 0};
  info.maxwidth   = zori_widget_w(&self->widget) - 10;
  USTR * ustr;
  const USTR * uline;
  ustr = ustr_new(str);
  while(zori_textinfo_linefromtext(&info, ustr, self->widget.style.text.font)) {
    uline = zori_textinfo_refustr(&info, &uinfo, ustr);
    zori_console_addustr(self, uline);
    // don't forget to skip to next line!!!
    zori_textinfo_next(&info);
  }
  uline = zori_textinfo_refustr(&info, &uinfo, ustr);
  zori_console_addustr(self, uline);
  ustr_free(ustr);
  return lines;
} 

#define ZORI_CONSOLE_VPRINTF_MAX 1024

/** Prints a formatted string on the console, truncaded to 1024 characters.  */
int zori_console_vprintf(struct zori_console * self, const char * format, va_list args) {
  char buffer[ZORI_CONSOLE_VPRINTF_MAX] = { '\0' };
  vsnprintf(buffer, sizeof(buffer), format, args);
  return zori_console_puts(self, buffer);
}

/** Prints a formatted string on the console, truncaded to 1024 characters.  */
int zori_console_printf(struct zori_console * self, const char * format, ...) {
  int result;
  va_list args;
  va_start(args, format);
  result = zori_console_vprintf(self, format, args);
  va_end(args);
  return result;
}


/** Draws a console. */
int zori_console_draw(union zori_event * zevent) {
  struct zori_console * self  ;
  zori_font * font       ;
  zori_color color       ;
  USTRListNode * now;
  int high, linehigh, index, x, y, skip;
  int linew;
  struct zori_widget * widget = zevent->any.widget;
  
  if (!zori_widget_visible(widget)) return ZORI_HANDLE_IGNORE;
  
  self  = zori_widget_console(widget);
  font  = zori_widget_font(widget);
  color = zori_widget_forecolor(widget);
  
  zori_widget_drawroundframe(widget);
  high        = zori_widget_h(widget) - 10;
  x           = zori_widget_x(widget) +  5;
  y           = zori_widget_y(widget) -  5;
  linehigh    = zori_font_lineheight(font);
  
  /* now         = ustrlist_head(&self->text); */
  /* skip start lines (to allow scrolling backwards) */
  now         = ustrlist_skipnode(&self->text, self->start);
  
  for (index = high-(linehigh*2); index > 0; index -= linehigh) {
    USTR * textstr;
    if(!now) break;
    textstr = ustrlistnode_ustr(now);
    if(textstr) {
      zori_font_drawstr(font, color, x, y + index, 0, textstr);
    }
    now = ustrlistnode_next(now);
  }
  // draw input string
  if (self->input) { 
    zori_font_drawstr(font, color, x, y + high - linehigh, 0, self->input);
  }
  // Draw cursor
  linew = al_get_ustr_width(font, self->input);
  al_draw_line(x + linew, y + high - linehigh, x + linew, y + high, color, 1);
  // draw start for debugging
  al_draw_textf(font, color, x, y, 0, "start: %d, size: %d", self->start, 
                ustrlist_size(&self->text));
  return ZORI_HANDLE_PASS;
}

/** Activates or deactivates the console. */
void zori_console_active_(struct zori_console * self, int active) {
  if(!self) return;
  zori_widget_active_(&self->widget, active);
  zori_widget_visible_(&self->widget, active);
}

/** Returns nonzero if console is active zero if not. */
int zori_console_active(struct zori_console * self) {
  if(!self) return 0;
  return zori_widget_active(&self->widget);
}

/** scrolls the console 1 step in the given direction. */
int zori_console_scroll(struct zori_console * self, int direction) {
  if((!self) || (!direction)) return FALSE;
  if(direction < 0) self->start--;
  if(direction > 0) self->start++;
  /* Clamp start between 0 and size of list. */
  self->start = bad_clampi(self->start, 0, ustrlist_size(&self->text));
  return ZORI_HANDLE_DONE;
}



/* Key input event handler for console. */
int zori_console_handle_keychar(union zori_event * zevent) { 
  struct zori_console * self  = zori_widget_console(zori_event_widget(zevent));
  zori_system_event * event   = zori_event_system_event(zevent);
  int ch = event->keyboard.unichar;
  int kc = event->keyboard.keycode;
  switch(kc) {
    // ignore the start-console key
    case ALLEGRO_KEY_F1:
    case ALLEGRO_KEY_F3:
      return ZORI_HANDLE_DONE;
    case ALLEGRO_KEY_PGUP: return zori_console_scroll(self, 1);
    case ALLEGRO_KEY_PGDN: return zori_console_scroll(self, -1);
    case ALLEGRO_KEY_BACKSPACE:
      // remove last character typed.
      ustr_remove_chr(self->input, ustr_offset(self->input, -1));
      return ZORI_HANDLE_DONE;
    break;    
    case ALLEGRO_KEY_ENTER: {
      const char * command = ustr_c(self->input);
      // execute command
      if(zori_console_docommand(self, command)) { 
        zori_console_puts(self, "Error in running comand");
        zori_console_puts(self, command);
      }
      ustr_truncate(self->input, 0);
      // empty string by truncating it
      return ZORI_HANDLE_DONE;
      }
    default:
    break;
  }
  
  ustr_appendch(self->input, ch);
  return ZORI_HANDLE_DONE;
}


/* Key down event handler for console. */
int zori_console_handle_keydown(union zori_event * zevent) { 
  struct zori_console * self  = zori_widget_console(zori_event_widget(zevent));
  zori_system_event * event   = zori_event_system_event(zevent);
  int ch = event->keyboard.unichar;
  int kc = event->keyboard.keycode;
  switch(kc) {
    case ALLEGRO_KEY_F1:  
    case ALLEGRO_KEY_F3:
      zori_console_active_(self, false); 
      /* disable console if F1 is pressed. 
       * Note: this shouldnever happen if react is set up well.
       */ 
      return ZORI_HANDLE_DONE;
    default:
    break;
  }
  return ZORI_HANDLE_IGNORE;
}

/* Mouse axe event handler for console */
int zori_console_handle_mouseaxes(union zori_event * zevent) { 
  struct zori_console * self  = zori_widget_console(zori_event_widget(zevent));
  zori_system_event * event   = zori_event_system_event(zevent);
  int z                       = event->mouse.dz;
  // only capture mouse scroll wheel...
  if(z == 0) return ZORI_HANDLE_IGNORE;
  if(z < 0) return zori_console_scroll(self, -1);
  if(z > 0) return zori_console_scroll(self, +1);
  return ZORI_HANDLE_DONE;
}

int zori_console_handle_ignore(union zori_event * zevent) { 
  return ZORI_HANDLE_IGNORE;
}


static struct zori_handler zori_console_actions[] = {  
  { ZORI_SYSTEM_EVENT_KEY_DOWN  , zori_console_handle_keydown   , NULL }, 
  { ZORI_SYSTEM_EVENT_KEY_UP    , zori_console_handle_ignore    , NULL },
  { ZORI_SYSTEM_EVENT_KEY_CHAR  , zori_console_handle_keychar   , NULL },
  { ZORI_SYSTEM_EVENT_MOUSE_AXES, zori_console_handle_mouseaxes , NULL },
  { ZORI_EVENT_DRAW             , zori_console_draw             , NULL },
  { ZORI_EVENT_DONE             , zori_console_handle_ignore    , NULL },
  { ZORI_EVENT_FREE             , zori_console_handle_ignore    , NULL },
  { -1, NULL, NULL }
};


/** Let the console handle allegro events. Returns 0 if event was consumed,
positive if not, and negative on error. */

int zori_console_handle(struct zori_widget * widget, zori_system_event * sevent) { 
  union zori_event zevent;
  if (!widget) return ZORI_HANDLE_ERROR;
  if (!zori_widget_active(widget)) return ZORI_HANDLE_IGNORE;
  return zori_widget_raise_system_event(widget, sevent);
}


/** Cleans up a console. */
int zori_console_done(struct zori_widget * widget) {
  struct zori_console * self = zori_widget_console(widget);
  if(!self) return ZORI_HANDLE_IGNORE;
  free(self->buf);
  self->buf     = NULL;
  ustr_free(self->input);
  self->input   = NULL;
  ustrlist_done(&self->text);
  return ZORI_HANDLE_DONE;
}


/** Deallocates a console. */
int zori_console_free(struct zori_widget * widget) {
  struct zori_console * self = zori_widget_console(widget);
  zori_console_done(&self->widget);
  free(self);
  return ZORI_HANDLE_DONE;
}

/** Allocates a console. */
struct zori_console * zori_console_alloc() {
  return calloc(1 , sizeof(struct zori_console));
}

/** Destructor for console. */
void zori_console_destroy(struct zori_widget * widget) {
  /* struct zori_console * console = zori_widget_to_console(widget); */ 
  zori_console_done(widget);
}


/* Amount of lines of display the console hat o keep track of. */
#define ZORI_CONSOLE_MAX 200

/** Initializes a console. */
struct zori_console * zori_console_initall(struct zori_console * self, int id, zori_rebox * bounds, struct zori_style * style) {
  struct zori_root * root = zori_get_root();
  
  if(!self) return NULL;
  assert(root);
  /* Allow only a single console. */
  if (root->console) return NULL;
  if(!zori_widget_initall(&self->widget, ZORI_WIDGET_TYPE_CONSOLE, id, &root->widget, bounds, style,  ZORI_ARRAY_AND_AMOUNT(zori_console_actions) ) ) { 
    return NULL;
  }
  ustrlist_init(&self->text);
  // ustrlist_shiftcstr(&self->text, "empty line");
  zori_widget_active_(&self->widget, FALSE);
  zori_widget_visible_(&self->widget, FALSE);
  self->count = 0;
  // max MUST be at least 2, 3 to see anything...
  self->max   = ZORI_CONSOLE_MAX;
  self->start = 0;
  self->charw = 80; 
  self->buf   = calloc(self->charw + 1, 1);
   // one extra for NULL at end . 
  if (!self->buf) { zori_console_done(&self->widget); return NULL; }
  self->input = ustr_new("");
  self->cursor= 0;
  if (!self->input) { zori_console_done(&self->widget); return NULL; }
  self->command      = NULL;
  self->command_data = NULL;
  /* Ony one console may be active. */
  root->console = self;
  self->widget.destroy = zori_console_destroy;
  
  return self;
}

/** Initializes a new console. */
struct zori_console * zori_console_new(int id, zori_rebox * bounds, struct zori_style * style) {
  struct zori_console * self = zori_console_alloc();
  if(!zori_console_initall(self, id, bounds, style)) {
    zori_console_free(&self->widget);
    return NULL;
  }
  return self;
}







