/*
* console.cc
* DIN Is Noise is copyright (c) 2006-2022 Jagannathan Sampath
* DIN Is Noise is released under GNU Public License 2.0
* For more information, please visit https://dinisnoise.org/
*/


#include "console.h"
#include "font.h"
#include "input.h"
#include "command.h"
#include "tcl_interp.h"
#include "tokenizer.h"
#include "globals.h"
#include "tcl_interp.h"
#include "ui_list.h"
#include "log.h"
#include <algorithm>
#include <string>
using namespace std;

extern cmdlist cmdlst;
extern char BUFFER [];
extern int line_height;

void console::calc_startl_iterator () {
  it_startl = lines.begin ();
  for (int i = 0; i < startl; ++i, ++it_startl);
}

void console::add_line (const mesg& ln) {
  lines.push_back (ln);
  ++nlines;
  if (nlines > maxlines) {
    lines.pop_front ();
    --nlines;
  }
  last ();
}

void console::last () { // ensures last line is always displayed
  end ();
  if (rollup_ == 0) up (lines_per_screen - 1);
}

void console::end () {
  startl = std::max (0, nlines - 1);
  calc_startl_iterator ();
}

void console::up (int i) {
  startl = std::max (0, startl - i);
  calc_startl_iterator ();
}

void console::down (int i) {
  startl += i;
  if (startl >= nlines) startl = std::max (0, nlines - 1);
  calc_startl_iterator ();
}

void console::pgdn () {
  down (lines_per_screen);
}

void console::pgup () {
  up (lines_per_screen);
}

void console::home () {
  startl = 0;
  calc_startl_iterator ();
}

console::console () : b_roll (10, arrow_button::up) {
  maxlines = 512;
  command_mode = 0;
  last_rollup_ = -1;
	rollup_ = 0;
  curs_loc = 0;
  curs_locx = 0;
  hid = -1;
  clear ();
  b_roll.set_listener (this);
  history.reserve (1024);
}

console& console::operator<< (const color& c) {
  clr = c;
  return *this;
}

console& console::operator<< (const string& s) {
  cur_line.text += s;
  return *this;
}

console& console::operator<< (unsigned int i) {
  sprintf (BUFFER, "%x", i);
  *this << BUFFER;
  return *this;
}

console& console::operator<< (int i) {
  sprintf (BUFFER, "%d", i);
  *this << BUFFER;
  return *this;
}

console& console::operator<< (unsigned long  i) {
  sprintf (BUFFER, "%lu", i);
  *this << BUFFER;
  return *this;
}

console& console::operator<< (float f) {
  sprintf (BUFFER, precision, f);
  *this << BUFFER;
  return *this;
}

console& console::operator<< (double d) {
  sprintf (BUFFER, precision, d);
  *this << BUFFER;
  return *this;
}

console& console::operator<< (char c) {
  if (c == '\n') {
		//if (suppress == 0) {
			cur_line.clr = clr;
			add_line (cur_line);
		//}
		cur_line = mesg ();
  } else cur_line.text += c;
  return *this;
}

void console::del () {
  int len = cmd_line.text.length ();
  if (curs_loc > 0 && curs_loc <= len) {
    cmd_line.text.erase (cmd_line.text.begin() + curs_loc - 1);
    curs_locx = (int) get_char_width (cmd_line.text.substr (0, --curs_loc));
  }
}

void console::calc_visual_params () {
  char_width = fnt.charwidth.max;
  lines_per_screen = (win.top - win.bottom) / line_height - GUTTER;
  startx = win.left + 15;
  starty = win.top - line_height;
  rollup (rollup());
}

void console::draw () {

  // draw lines
  int cx = startx, cy = starty;
  int n = 0, i = startl;
  line_iterator it = it_startl;
  while ((n++ < lines_per_screen) && (i < nlines)) {
    const mesg& linei = *it++; i++;
    const color& clr = linei.clr;
    glColor3f (clr.r, clr.g, clr.b);
    draw_string (linei.text, cx, cy);
    cy -= line_height;
  }

  // draw command line
	glColor3f (cmd_line.clr.r, cmd_line.clr.g, cmd_line.clr.b);
  draw_string (cmd_line.text, cx, cy);

  // draw cursor
  if (command_mode) {
    glColor3f (1, 0, 0);
    int px = cx + curs_locx, py = cy - 2;
    static int pts[4]={0};
    pts[0]=px;pts[1]=py;pts[2]=px+char_width;pts[3]=py;
    glVertexPointer (2, GL_INT, 0, pts);
    glDrawArrays (GL_LINES, 0, 2);
  }

  // draw rollup arrow
  int py = b_roll.posy;
  if (py != cy) b_roll.set_pos (1, cy);
  b_roll.draw ();
  
}

void console::clear () {
  lines.clear ();
	clear_cmd_line ();
  nlines = 0;
  startl = 0;
  calc_startl_iterator ();
  rollup (1);
}

void console::set_window (const box<int>& w) {
  win = w;
  calc_visual_params ();
}

char get_typed_char ();

int console::handle_input () {

  b_roll.handle_input ();

  int ret = 0;
  static float sc0 = 0.5, sc1 = 1./64;
  static float pg0 = 0.5, pg1 = 1./32;

  if (keypressed (SDLK_TAB) && !ALT) {
    toggle_command_mode ();
    return 1;
  } else if (command_mode) {
    if (keypressed (SDLK_ESCAPE)) { 
			toggle_command_mode (); 
			return 1;
		}

    ret = 1;
    char c = get_typed_char ();
    if (c != 0) {
      cmd_line.text.insert (cmd_line.text.begin() + curs_loc++, c);
      curs_locx = get_char_width (cmd_line.text.substr (0, curs_loc));
    }

    if (keypressedd (SDLK_BACKSPACE)) del (); else

    if (keypressed (SDLK_RETURN)) {

      add_line (cmd_line);

      history.push_back (cmd_line.text);
      hid = history.size () - 1;

      operator() (cmd_line.text);
			clear_cmd_line ();

    } else if (keypressedd (SDLK_LEFT)) {

      if (--curs_loc < 1)
        curs_loc = curs_locx = 0;
      else
        curs_locx = (int) get_char_width (cmd_line.text.substr (0, curs_loc));


    } else if (keypressedd (SDLK_RIGHT)) {

      int len = cmd_line.text.length ();
      if (++curs_loc > len) curs_loc = len;
      curs_locx = (int) get_char_width (cmd_line.text.substr (0, curs_loc));


    } else if (hid > -1) {

      if (keypressedd (SDLK_UP)) {
				set_cmd_line (history[hid--]);
        if (hid < 0) hid = history.size () - 1;
      } else if (keypressedd (SDLK_DOWN)) {
				set_cmd_line (history[hid++]);
        if (hid >= (int) history.size()) hid = 0;
      }

    }

    return 1;

  } else 
  if (keypressed (SDLK_HOME)) home (); else
  if (keypressed (SDLK_END)) end(); else
  if (keypressedd (SDLK_PAGEUP, pg0, pg1)) pgup(); else
  if (keypressedd (SDLK_PAGEDOWN, pg0, pg1)) pgdn(); else
  if (keypressedd (SDLK_BACKSPACE)) clear(); else
  if (keypressedd (SDLK_UP, sc0, sc1)) up(1); else
  if (keypressedd (SDLK_DOWN, sc0, sc1)) down(1);

  return ret;

}

void console::rollup (int r) {
  rollup_ = r;
  if (rollup_ == 1) 
		b_roll.set_dir (arrow_button::down); 
	else 
		b_roll.set_dir (arrow_button::up);
  last ();
}

console& console::operator() (const string& cmd) {
  into_lines (interpreter(cmd).result, *this);
  return *this;
}

void console::set_cmd_line (const string& s, const color& c) {
  cmd_line.text = s;
	cmd_line.clr = c;
  curs_loc = cmd_line.text.length ();
  curs_locx = get_char_width (cmd_line.text);
}

void console::clear_cmd_line () {
	cmd_line = mesg ();
	curs_loc = curs_locx = 0;
}

void console::toggle_command_mode () {
  command_mode = !command_mode;
  if (command_mode) {
    widget::focus = this; 
  } else widget::focus = 0;
  last ();
}

void console::clicked (button& b) {
  rollup (!rollup());
	if (command_mode) widget::focus = this;
}

console::~console() {
  dlog << "--- destroyed console ---" << endl;
}
