#include <cstdlib>
#include <iostream>
#include <cstring>

#include "kwindow.h"

using namespace std;

extern kbox_type	VISUAL_KBOX;
extern klunk		MAXPC;

/* ----------------------------------------------------- */

kwindow_type::kwindow_type (void)
: Fl_Window
    { 0,0,WINDOW_WIDTH,WINDOW_HEIGHT,"VISUAL KBOX" }

{
  //basic control buttons for the KBOX simulator
  {
    step_button = new Fl_Button
      { WINDOW_WIDTH+BACKTAB5,25,100,50,"&STEP" };
    step_button->color(FL_GREEN);
    step_button->labelcolor(FL_BLACK);
    step_button->align(FL_ALIGN_CENTER);
    step_button->callback(cb_step,(void*)this);
    step_button_pushed = false;

    stop_button = new Fl_Button
      { WINDOW_WIDTH+BACKTAB5,85,100,50,"E&XIT" };
    stop_button->color(FL_RED);
    stop_button->labelcolor(FL_BLACK);
    stop_button->align(FL_ALIGN_CENTER);
    stop_button->callback(cb_stop,(void*)this);
    stop_button_pushed = false;

    run_button = new Fl_Button
      { WINDOW_WIDTH+BACKTAB5,145,100,50,"&RUN " };
    run_button->color(FL_YELLOW);
    run_button->labelcolor(FL_BLACK);
    run_button->align(FL_ALIGN_CENTER);
    run_button->callback(cb_run,(void*)this);
    run_button_pushed = false;

    memory_button = new Fl_Button
      { WINDOW_WIDTH+BACKTAB5,250,100,50,"&MEMORY" };
    memory_button->color(FL_BLUE);
    memory_button->labelcolor(FL_WHITE);
    memory_button->align(FL_ALIGN_CENTER);
    memory_button->callback(cb_memory,(void*)this);
    memory_button_pushed = false;

    stack_button = new Fl_Button
      { WINDOW_WIDTH+BACKTAB5,310,100, 50,"S&TACK " };
    stack_button->color(FL_BLUE);
    stack_button->labelcolor(FL_WHITE);
    stack_button->align(FL_ALIGN_CENTER);
    stack_button->callback(cb_stack,(void*)this);
    stack_button_pushed = false;

    heap_button = new Fl_Button
      { WINDOW_WIDTH+BACKTAB5,370,100,50,"&HEAP  " };
    heap_button->color(FL_BLUE);
    heap_button->labelcolor(FL_WHITE);
    heap_button->align(FL_ALIGN_CENTER);
    heap_button->callback(cb_heap,(void*)this);
    heap_button_pushed = false;
  }

  //integer register display
  {
    i_registers_label = new Fl_Box
      { FL_UP_BOX,TAB1,25,450,50,
      "INTEGER REGISTERS" };
    i_registers_label->color(FL_WHITE);
    i_registers_label->labelcolor(FL_RED);
    for (int i=0;i<16;i++)
    {
      string i_reg = "I"+to_string(i);
      i_reg_names[i] = new Fl_Box
        { FL_UP_BOX,TAB1,75+i*40,200,40,
        IREG_LABELS[i].data() };
      i_reg_names[i]->color(FL_WHITE);
      i_reg_names[i]->labelcolor(FL_RED);
      i_reg_names[i]->align(FL_ALIGN_CENTER);
      i_reg_values[i] = new Fl_Box
        { FL_UP_BOX,TAB2,75+i*40,250,40,klunk2hex
        (VISUAL_KBOX.REGISTERS.get_register(i_reg)) };
      i_reg_values[i]->color(FL_WHITE);
      i_reg_values[i]->labelcolor(FL_BLACK);
      i_reg_values[i]->align(FL_ALIGN_CENTER);
    }
  }

  //floating point register display
  {
    f_registers_label = new Fl_Box
      { FL_UP_BOX,TAB3, 25,450, 50,
      "FLOATING POINT REGISTERS" },
    f_registers_label->color(FL_WHITE);
    f_registers_label->labelcolor(FL_RED);
    for (int i=0;i<16;i++)
    {
      string f_reg = "F"+to_string(i);
      f_reg_names[i] = new Fl_Box
        { FL_UP_BOX,TAB3,75+i*40,200,40,
        FREG_LABELS[i].data() };
      f_reg_names[i]->color(FL_WHITE);
      f_reg_names[i]->labelcolor(FL_RED);
      f_reg_names[i]->align(FL_ALIGN_CENTER);
      f_reg_values[i] = new Fl_Box
        { FL_UP_BOX,TAB4,75+i*40,250,40,klunk2hex
        (VISUAL_KBOX.REGISTERS.get_register(f_reg)) };
      f_reg_values[i]->color(FL_WHITE);
      f_reg_values[i]->labelcolor(FL_BLACK);
      f_reg_values[i]->align(FL_ALIGN_CENTER);
    }
  }

  //program counter information
  {
    pc_label = new Fl_Box
      { FL_UP_BOX,500,150,200,50,
      "PROGRAM_COUNTER" };
    pc_label->color(FL_WHITE);
    pc_label->labelcolor(FL_RED);
    pc_label->align(FL_ALIGN_CENTER);

    pc_value = new Fl_Box
      { FL_UP_BOX,725,150,250,50,
      klunk2hex(VISUAL_KBOX.FLAGS.getPC()) };
    pc_value->color(FL_WHITE);
    pc_value->labelcolor(FL_BLACK);
    pc_value->align(FL_ALIGN_CENTER);
  }

  //instruction display
  {
    if (MAXPC == 0)
    {
      cout << " ... that's all folks!\n";
      exit(EXIT_SUCCESS);
    }
    code_entry instruction =
      VISUAL_KBOX.INSTRUCTIONS.
      get_instruction(0);
/*
Please note the variation in coding that follows:

The label in the Fl_Box constructor is "".
The actual label is defined using the copy_label method,
which is typically used to modify/update during runtime.

For some unknown reason (at least to me!), when I
compile and run the code with the value in the Fl_Box
constructor, the initial display contains gibberish.
Using the copy_label method the display is fine.

I find that the distinction between C++ string class and
C strings (char* and char[] especially present when
working with C++ and FLTK.
*/
    pc_opcode = new Fl_Box
      { FL_UP_BOX,640,260,200,50,"" };
    pc_opcode->color(FL_WHITE);
    pc_opcode->labelcolor(FL_BLACK);
    pc_opcode->align(FL_ALIGN_CENTER);
    pc_opcode->copy_label
      (instruction.get_opcode().data());

    pc_oper1 = new Fl_Box
      { FL_UP_BOX,640,320,200,50,"" };
    pc_oper1->color(FL_WHITE);
    pc_oper1->labelcolor(FL_BLACK);
    pc_oper1->align(FL_ALIGN_CENTER);
    pc_oper1->copy_label
      (instruction.get_operand1().data());

    pc_oper2 = new Fl_Box
      { FL_UP_BOX,600,380,280,50,"" };
    pc_oper2->color(FL_WHITE);
    pc_oper2->labelcolor(FL_BLACK);
    pc_oper2->align(FL_ALIGN_CENTER);
    pc_oper2->copy_label
      (instruction.get_operand2().data());

    pc_oper3 = new Fl_Box
      { FL_UP_BOX,640,440,200,50,"" };
    pc_oper3->color(FL_WHITE);
    pc_oper3->labelcolor(FL_BLACK);
    pc_oper3->align(FL_ALIGN_CENTER);
    pc_oper3->copy_label
      (instruction.get_operand3().data());
  }

  //comparison flags display
  {
    eq_flag = new Fl_Light_Button
      { 600,550,50,50,"EQ" };
    eq_flag->value(VISUAL_KBOX.FLAGS.getEQ());
    gt_flag = new Fl_Light_Button
      { 712,550,50,50,"GT" };
    gt_flag->value(VISUAL_KBOX.FLAGS.getGT());
    lt_flag = new Fl_Light_Button
      { 825,550,50,50,"LT" },
    lt_flag->value(VISUAL_KBOX.FLAGS.getLT());
  }

  //console display for to show program execution
  {
    console_display = new Fl_Simple_Terminal
      { 0,740,WINDOW_WIDTH,260 };
    console_display->ansi(true);
  }

  resizable(this);
  end();
}

/* ----------------------------------------------------- */

void kwindow_type::wait_for_step_button (void)
{
  while (!step_button_pushed)
    Fl::wait();
  step_button_pushed = false;
  redraw();
}

void kwindow_type::do_step (void)
{
  klunk pc = VISUAL_KBOX.FLAGS.getPC();
  bool halt_flag;
  fedex(pc,halt_flag);
  refresh();
}

/* ----------------------------------------------------- */

void kwindow_type::wait_for_stop_button (void)
{
  while (!stop_button_pushed)
    Fl::wait();
  stop_button_pushed = false;
  redraw();
}

void kwindow_type::do_stop (void)
{
  console_display->printf(" ... that's all folks!\n");
  exit(EXIT_SUCCESS);
}

/* ----------------------------------------------------- */

void kwindow_type::wait_for_run_button (void)
{
  while (!run_button_pushed)
    Fl::wait();
  run_button_pushed = false;
  redraw();
}

void kwindow_type::do_run (void)
{
  klunk pc = VISUAL_KBOX.FLAGS.getPC();
  bool halt_flag = false;
  while (true)
  {
    fedex(pc,halt_flag);
    refresh();
    if (halt_flag)
      break;
    pc = VISUAL_KBOX.FLAGS.getPC();
  }
}

/* ----------------------------------------------------- */

void kwindow_type::wait_for_memory_button (void)
{
  while (!memory_button_pushed)
    Fl::wait();
  memory_button_pushed = false;
  redraw();
}

void kwindow_type::do_memory (void)
{
  console_display->printf
    ("GLOBAL/STATIC MEMORY MAP:\n");
  for (klunk i=0;i<VISUAL_KBOX.MEMORY.map.size();i++)
    console_display->printf("%s  %d  %d\n",
      VISUAL_KBOX.MEMORY.map[i].get_name().data(),
      VISUAL_KBOX.MEMORY.map[i].get_address(),
      VISUAL_KBOX.MEMORY.map[i].get_size());
  console_display->printf("\n");
  console_display->printf
    ("GLOBAL/STATIC MEMORY:\n");
  for (klunk i=0;
       i<VISUAL_KBOX.MEMORY.get_next_available();
       i++)
    console_display->printf
      ("%d  %s\n",i,
      klunk2hex(VISUAL_KBOX.MEMORY.peek(i)));
  console_display->printf("\n");
}

/* ----------------------------------------------------- */

void kwindow_type::wait_for_stack_button (void)
{
  while (!stack_button_pushed)
    Fl::wait();
  stack_button_pushed = false;
  redraw();
}

void kwindow_type::do_stack (void)
{
  console_display->printf
    ("STACK:\n");
  string sp_reg = "I12";
  klunk sp = VISUAL_KBOX.REGISTERS.get_register(sp_reg);
  if (sp == MEMORY_SIZE)
    console_display->printf
      ("<empty>\n");
  else
    for (klunk i=MEMORY_SIZE-1;i>=sp;i--)
      console_display->printf
        ("%d  %s\n",
        i,klunk2hex(VISUAL_KBOX.MEMORY.peek(i)));
  console_display->printf("\n");
}

/* ----------------------------------------------------- */

void kwindow_type::wait_for_heap_button (void)
{
  while (!heap_button_pushed)
    Fl::wait();
  heap_button_pushed = false;
  redraw();
}

void kwindow_type::do_heap (void)
{
  console_display->printf
    ("HEAP:\n");
  string hp_reg = "I15";
  klunk hp = VISUAL_KBOX.REGISTERS.get_register(hp_reg);
  if (hp == VISUAL_KBOX.MEMORY.get_next_available())
    console_display->printf
      ("<empty>\n");
  else
    for (klunk i=VISUAL_KBOX.MEMORY.get_next_available();
        i<hp;
        i++)
      console_display->printf
        ("%d  %s\n",
        i,klunk2hex(VISUAL_KBOX.MEMORY.peek(i)));
  console_display->printf("\n");
}

/* ----------------------------------------------------- */

/*
void kwindow_type::fedex (klunk pc,bool& halt_flag)
is a very large implementation and
may be found in a separate source fie:

     kfedex.cpp

*/

/* ----------------------------------------------------- */

void popup (string prompt,char buffer[])
{
  Fl_Window window(400,120,prompt.data());
  window.set_modal();
  Fl_Input input_data(100,20,200,40);
  Fl_Button okay_btn(100,75,75,25,"&OK");
  Fl_Button cancel_btn(225,75,75,25,"&CANCEL");
  window.end();
  window.show();
  while (true)
  {
    Fl::wait();
    Fl_Widget *o;
    while (o = Fl::readqueue())
      if (o == &okay_btn)
      {
        strcpy(buffer,input_data.value());
        return;
      }
      else if (o == &cancel_btn || o == &window)
      {
        strcpy(buffer,"\0");
        return;
      }
  }
}

/* ----------------------------------------------------- */

void kwindow_type::refresh (void)
{
  for (klunk i=0;i<16;i++)
  {
    string i_reg = "I"+to_string(i);
    string f_reg = "F"+to_string(i);
    i_reg_values[i]->copy_label
      (klunk2hex
      (VISUAL_KBOX.REGISTERS.get_register(i_reg)));
    f_reg_values[i]->copy_label
      (klunk2hex
      (VISUAL_KBOX.REGISTERS.get_register(f_reg)));
  }
  klunk pc = VISUAL_KBOX.FLAGS.getPC();
  pc_value->copy_label(klunk2hex(pc));
  if (pc < MAXPC)
  {
    code_entry next_instr =
      VISUAL_KBOX.INSTRUCTIONS.get_instruction(pc);
    pc_opcode->copy_label
      (next_instr.get_opcode().data());
    pc_oper1->copy_label
      (next_instr.get_operand1().data());
    pc_oper2->copy_label
      (next_instr.get_operand2().data());
    pc_oper3->copy_label
      (next_instr.get_operand3().data());
    eq_flag->value(VISUAL_KBOX.FLAGS.getEQ());
    gt_flag->value(VISUAL_KBOX.FLAGS.getGT());
    lt_flag->value(VISUAL_KBOX.FLAGS.getLT());
  }
  else
  {
    pc_opcode->copy_label("HALT");
    pc_oper1->copy_label("");
    pc_oper2->copy_label("");
    pc_oper3->copy_label("");
    eq_flag->value(true);
    gt_flag->value(false);
    lt_flag->value(false);
  }
}

