/*==========================================================
  File:  console.c
  Author:  _pragma

  Description:  In game console routines (rendering, input).
  ==========================================================*/


#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <GL/gl.h>
#include <SDL/SDL.h>

#include "util.h"
#include "sdl.h"
#include "gl.h"
#include "interp.h"
#include "font.h"
#include "texture.h"
#include "console.h"
#include "cvar.h"

console_t gc_console// our global console object

/*==========================================================
  Function:  CON_initConsole( void )
  Parameters:  none
  Returns:  true  - console was successfully initialized
            false - failed to initialize console

  Description:  Initializes the game console.
  ==========================================================*/


int CON_initConsolevoid )
{
  extern cvar_t *cvar_scr_width, *cvar_scr_height;
  extern font_struct_t gf_fonts[];
  int widthheight;

  fprintf(stderr"----------- Init Console -----------\n");

  // obtain screen width and height with failsafe ?: operation
  width =  cvar_scr_width  ? atoi(cvar_scr_width->value)  : SCREEN_WIDTH
  height = cvar_scr_height ? atoi(cvar_scr_height->value) : SCREEN_HEIGHT;

  // set all members of gc_console to 0
  memset(&gc_console0sizeof(gc_console));

  // allocate all console buffers
  if(!(gc_console.buffer_commands = malloc(CON_BUFFER_COMMANDS_SIZE)))
    return 0;
  if(!(gc_console.buffer_keys = malloc(CON_BUFFER_KEYSTROKE_SIZE)))
    return 0;
  if(!(gc_console.texture = malloc(sizeof(texture_t))))
    return 0;

  // initialize buffers to 0
  memset(gc_console.buffer_commands0CON_BUFFER_COMMANDS_SIZE);
  memset(gc_console.buffer_keys0CON_BUFFER_KEYSTROKE_SIZE);
  memset(&gc_console.buffer_out0sizeof(struct console_output_buffer_s));

  gc_console.x = width;
  gc_console.y = 0.75;

  gc_console.chars_width  = width  / gf_fonts[FONT_CONSOLE].char_xstep;
  gc_console.chars_height = (height / gf_fonts[FONT_CONSOLE].char_height) / 2;

  // initialize some animation variables
  gc_console.velocity = 0;  
  gc_console.acceleration = 3500;
  gc_console.alpha_max = 0.75;

  CON_printf("console.width:          %5.2f"gc_console.x);
  CON_printf("console.chars_width:    %5d"gc_console.chars_width);
  CON_printf("console.chars_height:   %5d"gc_console.chars_height);
  CON_printf("console.stop:           %5.2f", (height/2.0));
  CON_printf("console.velocity:       %5.2f"gc_console.velocity);
  CON_printf("console.alpha:          %5.2f"gc_console.alpha);
  CON_printf("console.alpha_mod:      %5.2f"gc_console.alpha_mod);

  CON_printf("------------------------------------");

  // success
  return 1;
}

/*==========================================================
  Function: CON_loadTexture(void)
  Parameters: none
  Returns:  void

  Description:  Loads console background texture.
  ==========================================================*/

void CON_loadTexture(void)
{
  if(!(gc_console.texture->id = T_loadTextureTGA("data/textures/system/console.tga"1)))
    CON_printf("failed to load console texture");
  else
    CON_printf("Console texture loaded.");
}

/*==========================================================
  Function: CON_destroyConsole(void)
  Parameters: none
  Returns: void

  Description: Frees all memory allocated for console.
  ==========================================================*/

void CON_destroyConsole(void)
{
  console_output_node_t *node, *next;

  fprintf(stderr"-------- Destroying Console --------\n");

  for(node = gc_console.buffer_out.headnodenode = next)
  {
    next = node->next;
    free(node->text);
    free(node);
  }

  if(gc_console.buffer_commands)
    free(gc_console.buffer_commands);
  if(gc_console.buffer_keys)
    free(gc_console.buffer_keys);

  if(gc_console.texture)
  {
    glDeleteTextures(1, &gc_console.texture->id);
    free(gc_console.texture);
  }
  fprintf(stderr"console destroyed\n");
  fprintf(stderr"------------------------------------\n");
}

/*==========================================================
  Function: CON_executeCommands(void)
  Parameters: none
  Returns:  void

  Description:  If any commands are present in the console
                command buffer, they are extracted and sent
                to the interpreter.
  ==========================================================*/

void CON_executeCommands(void)
{
  char command[CON_BUFFER_KEYSTROKE_SIZE];
  int length;

#ifdef CON_EXEC_DEBUG
  int i;
#endif

  if(!*gc_console.buffer_commands)
    return;

  do
  {
    length = strlen(strcpy(commandgc_console.buffer_commands)) + 1;
    gc_console.index_commands -= length;

#ifdef CON_EXEC_DEBUG
    printf("Executing command: %s, %d\n"commandlength);
    for(i = 0i < CON_BCSi++)
      printf("[%c]"gc_console.buffer_commands[i]);
    printf("\n----------------\n");
#endif
  
    memmove(gc_console.buffer_commands
            gc_console.buffer_commands + length
            gc_console.index_commands);
    memset(gc_console.buffer_commands + gc_console.index_commands0length);
 
    I_interpretCommand(command);
  } while (*gc_console.buffer_commands);
}

/*==========================================================
  Function: CON_splitCommand(char *command, char **nextcommand)
  Parameters:  command     - command string being processed
               nextcommand - pointer to next command after ';'
  Returns: pointer to 'command'

  Description:  This takes a command string such as:

                  "set foo bar;echo foo is $foo" 

                and splits it into commands seperated by the
                semi-colon by finding the first semi-colon and
                setting it to '\0' and pointing 'nextcommand'
                to the character following it.
  ==========================================================*/

char *CON_splitCommand(char *commandchar **nextcommand)
{
  int quoted = 0;

  if(!command || !*command)
    return 0;

   
  *nextcommand = command;

  while(**nextcommand)
  {
    if(**nextcommand == '\"')
    {
      quoted = !quoted;
      **nextcommand = ' ';
    }

    if(**nextcommand == ';' && (quoted == false))
      break;

    (*nextcommand)++;
  }

  if(**nextcommand)  // non-zero, implies ';'
  {
    **nextcommand = 0;
    (*nextcommand)++;
  }
  return command;
}

/*==========================================================
  Function: CON_addCommand(char *command)
  Parameters: command - command to be added
  Returns: true if command was successfully added to the buffer
           or false otherwise

  Description: Preprocesses a command string fresh off the input
               buffer and adds the commands to the command
               buffer.
  ==========================================================*/

int CON_addCommand(char *command)
{
  int length;
  char *cmd, *nextcommand = 0buf[CON_BCS];

  strcpy(bufcommand);
  command = buf;

  while((cmd = CON_splitCommand(command, &nextcommand)))
  {
    length = strlen(cmd)+1;

    if(length == 1 || 
       gc_console.index_commands + length >= CON_BUFFER_COMMANDS_SIZE)
    {
      // fixme: recycle instead of ignoring
      fprintf(stderr"addCommand warning: ignoring \'%s\'\n"cmd);
      command = nextcommand;
      continue;
    }

    memcpy(gc_console.buffer_commands + gc_console.index_commands
         cmdlength);
    gc_console.index_commands += length;
    command = nextcommand;
  }
  return 1;
}

/*==========================================================
  Function: CON_processKeybuffer(void)
  Parameters: none
  Returns: void

  Description: The user has just finished typing a command in
               the console and has pressed 'enter'.
  ==========================================================*/

void CON_processKeybuffer(void)
{
  CON_printf("^3>^7%s"gc_console.buffer_keys);
  CON_addCommand(gc_console.buffer_keys);
  memset(gc_console.buffer_keys0gc_console.index_keys+1);
  gc_console.index_keys = 0;
}

/*==========================================================
  Function: CON_processKeyRelease(SDL_keysym *keysym)
  Parameters: keysym - information about the keystroke
  Returns: true if we do not want the engine to continue processing
           this keystroke after the console has processed it,
           or false if we do.

  Description: Processes a keystroke that was released.
  ==========================================================*/

int CON_processKeyRelease(SDL_keysym *keysym)
{
  SDLKey key = keysym->sym;
 
  if(gc_console.state == CON_STATE_CLOSED)
    return 0;

  if(key >= 32 && key <= 122/* ASCII mapped keystrokes */
    return 1;    

  switch(key)
  { 
    case SDLK_BACKQUOTE:
    case SDLK_PAGEUP:
    case SDLK_PAGEDOWN:
    case SDLK_END:
    case SDLK_HOME:
    case SDLK_RETURN:
    case SDLK_BACKSPACE:
    case SDLK_DELETE:
      return 1;
      break;
    defaultreturn 0;
  }
}

/*==========================================================
  Function: CON_processKeystroke(SDL_keysym *keysym)
  Parameters: keysym - information about the keystroke
  Returns: true if want the engine to continue processing
           this keystroke after the console has processed it,
           or false if we do not. (FIXME,TODO: this is backwards
           from CON_processKeyRelease() above.)

  Description: Processes a keystroke.
  ==========================================================*/

int CON_processKeystroke(SDL_keysym *keysym)
{
  SDLKey key = keysym->sym;
  extern cvar_t *cvar_paging_size;
  int paging_size = atoi(cvar_paging_size->value), i = 0;

  // fixme: perhaps all these if()'s can be a switch()

  if(key == SDLK_BACKQUOTE)
  {
    if(gc_console.state < CON_STATE_OPENING)
    {
      gc_console.state = CON_STATE_OPENING;
      SDL_EnableUNICODE(1);
    }
    else
      gc_console.state = CON_STATE_CLOSING;
    return 1;
  }

  if(gc_console.state <= CON_STATE_CLOSING)
    return 0;

  if(key == SDLK_PAGEUP)
  {
    while(gc_console.buffer_out.paging->prev && i++ < paging_size)
      gc_console.buffer_out.paging = gc_console.buffer_out.paging->prev;
    return 1;
  }

  if(key == SDLK_END)
  {
    gc_console.buffer_out.paging = gc_console.buffer_out.tail;
    return 1;
  }

  if(key == SDLK_HOME)
  {
    gc_console.buffer_out.paging = gc_console.buffer_out.head;
    return 1
  }

  if(key == SDLK_PAGEDOWN)
  {
    while(gc_console.buffer_out.paging->next && i++ < paging_size)
      gc_console.buffer_out.paging = gc_console.buffer_out.paging->next;
    return 1;
  }

  if(key == SDLK_RETURN)
  {
    gc_console.buffer_keys[gc_console.index_keys++] = 0;
    CON_processKeybuffer();
    return 1;
  }

  if(key == SDLK_BACKSPACE || key == SDLK_DELETE)
  {
    if(gc_console.index_keys > 0)
      gc_console.buffer_keys[--gc_console.index_keys] = 0;
    return 1;
  }

  if(key >= 32 && key <= 122/* ASCII mapped keystrokes, add to keybuffer */
  {
    if(gc_console.index_keys < CON_BUFFER_KEYSTROKE_SIZE - 2)
    {
      key = keysym->unicode & 0x7f;
      gc_console.buffer_keys[gc_console.index_keys++] = key;
    }
    return 1;
  }
  return 0;
}

/*==========================================================
  Function: CON_rotateScreenBuffer(void)
  Parameters: none
  Returns: void

  Description:  Rotates the buffer that is displayed when the
                console is hidden.
  ==========================================================*/

void CON_rotateScreenBuffer(void)
{
  int i;

  for(i = 0i < gc_console.index_screeni++)
    strcpy(gc_console.buffer_screen[i], gc_console.buffer_screen[i+1]);
  gc_console.index_screen--;
}

/*==========================================================
  Function:     CON_popOutputNode(void)
  Parameters:   none
  Returns:      void

  Description:  Removes the head node and makes the next node
                the head.
  ==========================================================*/

void CON_popOutputNode(void)
{
  console_output_node_t *node;

  if(!gc_console.buffer_out.head || gc_console.buffer_out.nodes < 200)
    return;

  node = gc_console.buffer_out.head;

  gc_console.buffer_out.head = node->next;
  gc_console.buffer_out.head->prev = 0;

  if(gc_console.buffer_out.paging == node)
    gc_console.buffer_out.paging = node->next;

  gc_console.buffer_out.nodes--;

  free(node->text);
  free(node);
}

/*==========================================================
  Function:     CON_createOutputNode(char *text)
  Parameters:   text -  text to store in this node
  Returns:      newly allocated output node

  Description:  Allocates and initializes a new output node.
  ==========================================================*/

console_output_node_t *CON_createOutputNode(char *text)
{
  console_output_node_t *node;

  node = malloc(sizeof(console_output_node_t));
  if(!node)
    return 0;

  node->text = strdup(text);
  if(!node->text)
  {
    free(node);
    return 0;
  }
  node->next = 0;
  node->prev = 0;
  return node;
}

/*==========================================================
  Function:     CON_pushOutputNode(console_output_node_t *node)
  Parameters:   node -  node to push onto the end of the list
  Returns:      void

  Description:  Adds a new node to the end of the output list.
  ==========================================================*/

void CON_pushOutputNode(console_output_node_t *node)
{
  if(!gc_console.buffer_out.head)
  {
    gc_console.buffer_out.head   = node;
    gc_console.buffer_out.tail   = node;
    gc_console.buffer_out.paging = node;
  }
  else
  {
    gc_console.buffer_out.tail->next = node;
    node->prev = gc_console.buffer_out.tail;
    if(gc_console.buffer_out.paging == gc_console.buffer_out.tail)
      gc_console.buffer_out.paging = node;
    gc_console.buffer_out.tail = node;
  }
  gc_console.buffer_out.nodes++;  
}

/*==========================================================
  Function: CON_printf(const char *fmt, ...)
  Parameters: fmt - va args format
              ... - va args parameters
  Returns: void

  Description:  Adds a string to the console output buffers.
  ==========================================================*/

void CON_printf(const char *fmt...)
{
  char text[1024];  // fixme:  make va() command a-la quake2
  char *p, *qc;
  va_list ap;
  int lengthreal_length;
  extern int gt_curtime;
  console_output_node_t *node;

  if(fmt == 0return;

  va_start(apfmt);
    vsnprintf(textsizeof(text), fmtap);
  va_end(ap);

  length = F_strlen(text);        // TODO: make F_strlen(text, &length, &real_length);
  real_length = strlen(text) + 1// TODO: and eliminate this line

  if(!length)
    return;

  fprintf(stderr"%s\n"F_color_stripped(text));


  if(length > gc_console.chars_width)
  {
    p = q = text;
    do
    {
      q += (length > gc_console.chars_width) ? gc_console.chars_width : length;
      while(*q && *q != ' ')
        q--;
      *q = 0;
      
      node = CON_createOutputNode(p);
      strcpy(gc_console.buffer_screen[gc_console.index_screen], p);
      gc_console.index_screen++;
      if(gc_console.index_screen > 4)
        CON_rotateScreenBuffer();
      CON_popOutputNode();  
      CON_pushOutputNode(node);

      if(length < gc_console.chars_width)
        break;

      q++;
      p = q;
      length = F_strlen(p);
      
    } while(length);
  }
  else
  {
    CON_popOutputNode();                // remove head node, make next node head, if applicable
    node = CON_createOutputNode(text);  // allocate and initialize new node
    CON_pushOutputNode(node);           // add this node to tail and reset tail pointer
    strcpy(gc_console.buffer_screen[gc_console.index_screen], text);
    gc_console.index_screen++;
    if(gc_console.index_screen > 4)
      CON_rotateScreenBuffer();
  }
  gc_console.time_screen = gt_curtime;
}

/*==========================================================
  Function: CON_updateConsole(int msec)
  Parameters: msec - time since last frame in milliseconds
  Returns: void

  Description: Updates certain console variables such as
               animation for opening and closing, screen buffers,
               and whatnot.
  ==========================================================*/

void CON_updateConsole(int msec)
{
  float time = (float)msec/1000.0;
  extern int gt_curtime;  
  extern cvar_t *cvar_scr_width
                *cvar_scr_height,
                *cvar_con_rottime;
  float width  = atof(cvar_scr_width->value), 
        height = atof(cvar_scr_height->value);

  if(gc_console.index_screen && 
     gt_curtime - gc_console.time_screen >= atoi(cvar_con_rottime->value))
  {
    CON_rotateScreenBuffer();
    gc_console.time_screen = gt_curtime - 250;
  }

  if(gc_console.state == CON_STATE_OPENING)
  {
    if(gc_console.y < height/2)             // fixme: add max y to struct
    {
      gc_console.velocity += gc_console.acceleration * time;
      gc_console.y += gc_console.velocity * time;
      gc_console.alpha += gc_console.alpha_max / ((height/2)/(gc_console.velocity * time));
    }
    if(gc_console.y >= height/2)
    {
      gc_console.y = height/2;
      gc_console.state = CON_STATE_OPEN;
      gc_console.alpha = gc_console.alpha_max;
      gc_console.velocity = 0;  // console is at rest
    }
  }
  else if(gc_console.state == CON_STATE_CLOSING)
  {
    if(gc_console.y > 0)
    {
      gc_console.velocity += gc_console.acceleration * time;
      gc_console.y -= gc_console.velocity * time
      gc_console.alpha -= gc_console.alpha_max / ((height/2)/(gc_console.velocity * time));
    }
    else
    if(gc_console.y <= 0)
    {
      gc_console.alpha = 0;
      gc_console.y = 0;
      gc_console.state = CON_STATE_CLOSED;
      gc_console.velocity = 0;  // console is at rest
      SDL_EnableUNICODE(0);
    }
  }
}

/*==========================================================
  Function: CON_drawScreenBuffer(void)
  Parameters: none
  Returns: void

  Description: Displays the buffer that is shown when the console
               is up.
  ==========================================================*/

void CON_drawScreenBuffer(void)
{
  extern font_struct_t gf_fonts[];
  int i;

  for(i = 0i < gc_console.index_screeni++)
    F_printf(FONT_CONSOLE0i * gf_fonts[FONT_CONSOLE].char_heightgc_console.buffer_screen[i]);
}

/*==========================================================
  Function: CON_drawConsole(int msec)
  Parameters: msec - time since last frame in milliseconds
  Returns: void

  Description: Draws the console.
  ==========================================================*/

void CON_drawConsole(int msec)
{
  extern texture_t gt_textures[T_MAX_TEXTURES];
  extern cvar_t *cvar_condebug;
  extern font_struct_t gf_fonts[];
  float y = gc_console.y;
  int i,j;
  char *p;
  static int blink = trueblinktime = false;
  console_output_node_t *node;
  extern cvar_t *cvar_scr_width, *cvar_scr_height;
  float width = atof(cvar_scr_width->value), height = atof(cvar_scr_height->value);

  if(gc_console.state == CON_STATE_CLOSED)
  {
    glBlendFunc(GL_ONEGL_ONE);
    CON_drawScreenBuffer();
    return;
  }

  glBlendFunc(GL_SRC_ALPHAGL_ONE_MINUS_SRC_ALPHA);

  if(gc_console.texture)
    glBindTexture(GL_TEXTURE_2Dgc_console.texture->id);
 
  glColor4f(0.30.50.7gc_console.alpha);

  glLoadIdentity();

  glBegin(GL_QUADS);
    glTexCoord2f(0.00.0);    glVertex3f(0.0y0.0);
    glTexCoord2f(1.00.0);    glVertex3f(widthy0.0);
    glTexCoord2f(1.01.0);    glVertex3f(widthy-height0.0);
    glTexCoord2f(0.01.0);    glVertex3f(0.0y-height0.0);
  glEnd();

  glBlendFunc(GL_SRC_ALPHAGL_ONE);
  glBindTexture(GL_TEXTURE_2Dgt_textures[0].id);
  glColor4f(1.01.01.0gc_console.alpha + 0.25);

  y = gc_console.y - 22;
  for(node = gc_console.buffer_out.pagingi = 0node && i < gc_console.chars_heightnode = node->previ++)
  {
    F_printf(FONT_CONSOLE0y - gf_fonts[FONT_CONSOLE].char_heightnode->text);
    y -= gf_fonts[FONT_CONSOLE].char_height;
  }

  blinktime += msec;
  if(blinktime > 350)
  {
    blink = !blink;
    blinktime = 0;
  }

  F_printf(FONT_CONSOLE0gc_console.y-22"^11%c^15%s^11%c"
           0 ? '|' : '>',  
           gc_console.buffer_keys,
           blink ? '_' : ' ');

  glColor4f(1.01.01.01.0);

  if(cvar_condebug && atoi(cvar_condebug->value))
  {
    F_printf(FONT_DEFAULT0height-(16*11), "Console status");
    F_printf(FONT_DEFAULT0height-(16*10), "x: %4.2f, y: %4.2f, alpha: %4.2f, state: %d"
                            gc_console.x,
                            gc_console.ygc_console.alpha,
                            gc_console.state);
  }
}