#include <cstdlib>
#include <iostream>
#include <iomanip>
#include <string>
#include <cstring>

#include "kbox.h"

using namespace std;

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

int64 klunk2int (klunk k)		// klunk -> int64
{  return *(reinterpret_cast<int64*>(&k));  }

flt64 klunk2flt (klunk k)		// klunk -> flt64
{  return *(reinterpret_cast<flt64*>(&k));  }

chr64 klunk2chr (klunk k)		// klunk -> chr64
{  return *(reinterpret_cast<chr64*>(&k));  }

str64 klunk2str (klunk k)		// klunk -> str64
{  return (reinterpret_cast<str64>(k));  }

klunk int2klunk (int64 i)		// int64 -> klunk
{  return *(reinterpret_cast<klunk*>(&i));  }

klunk flt2klunk (flt64 f)		// flt64 -> klunk
{  return *(reinterpret_cast<klunk*>(&f));  }

klunk chr2klunk (chr64 c)		// chr64 -> klunk
{  return *(reinterpret_cast<klunk*>(&c));  }

klunk str2klunk (str64 s)		// str64 -> klunk
{  return (reinterpret_cast<klunk>(s));  }

klunk literal2klunk (const string& lit)	// literal -> data
{
  char symbol = lit[0];
  if (symbol == '\"')
  // string literal
  {
    // remove leading and trailing '\"'
    str64 result = (char*) malloc (LITERAL_SIZE);
    int new_len = lit.size()-2;
    for (int i = 0;i < new_len;i++)
      result[i] = lit[i+1];
    result[new_len] = '\0';
    return str2klunk (result);
  }
  else if (symbol == '\'')
  // character literal
    return chr2klunk (lit[1]);
  else if ((symbol == '+') ||
           (symbol == '-') ||
           (isdigit(symbol)))
  // numeric literal
  if (lit.find_first_of(".eE") != string::npos)
    // floating point literal
      return flt2klunk (stof(lit));
    else
    // integer literal
      return int2klunk (stoll(lit));
  else
  {
    cerr << "invalid literal encountered: "
         << lit << endl;
    exit (EXIT_FAILURE);
  }
}

char* klunk2hex (klunk k)               // klunk -> hex
{
  static const char* HEX_DIGITS = "0123456789ABCDEF";
  char* result = new char[17];
  result[16] = '\0';
  for (int i=0;i<16;i++)
  {
    result[15-i] = HEX_DIGITS[k%16];
    k = k/16;
  }
  return result;
}

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

void flags_type::display (void) const
{
  cout << setw(5) << left << "PC"
       << setw(5) << left << getPC()
       << setw(5) << left << "EQ"
       << setw(5) << left << getEQ()
       << setw(5) << left << "GT"
       << setw(5) << left << getGT()
       << setw(5) << left << "LT"
       << setw(5) << left << getLT()
       << endl << endl;
}

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

bool memory_type::is_map_entry
  (const string& n,map_entry& me) const
{
  for (klunk i=0;i<map.size();i++)
    if (map[i].get_name() == n)
    {
      me = map[i];
      return true;
    }
  return false;
}

void memory_type::insert_map_entry
  (const string& n,klunk a,klunk s)
{
  map_entry me(n,a,s);
  map.push_back(me);
}

void memory_type::define
  (const string& name,klunk val)
{
  map_entry me;
  if (is_map_entry(name,me))
  {
    cerr << "duplicate identifier ("
         << name << ") encountered!\n";
    return;
  }
  klunk address = get_next_available();
  insert_map_entry(name,address,1);
  poke(address,val);
  set_next_available(get_next_available()+1);
}

void memory_type::reserve
  (const string& name,klunk size)
{
  map_entry me;
  if (is_map_entry(name,me))
  {
    cerr << "duplicate identifier ("
         << name << ") encountered!\n";
    return;
  }
  klunk address = get_next_available();
  insert_map_entry(name,address,size);
  set_next_available(get_next_available()+size);
}

void memory_type::display_map (void) const
{
  cout << "GLOBAL/STATIC MEMORY MAP:" << endl;
  for (klunk i=0;i<map.size();i++)
    cout << setw(20) << left  << map[i].get_name()
         << setw(10) << right << map[i].get_address()
         << setw(10) << right << map[i].get_size()
         << endl;
  cout << endl;
};
void memory_type::display_static (void) const
{
  cout << "GLOBAL/STATIC MEMORY:" << endl;
  for (klunk i=0;i<get_next_available();i++)
    cout << setw(10) << right << i
         << setw(20) <<right << klunk2hex(peek(i))
         << endl;
  cout << endl;
};

void memory_type::display_stack (klunk sp) const
{
  cout << "STACK:" << endl;
  for (klunk i=MEMORY_SIZE-1;i>=sp;i--)
    cout << i << "   " << klunk2hex(peek(i)) << endl;
  cout << endl;
}

void memory_type::display_heap (klunk hp) const
{
  cout << "HEAP:" << endl;
  for (klunk i=get_next_available();i<hp;i++)
    cout << i << "  " << klunk2hex(peek(i)) << endl;
  cout << endl;
}

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

bool registers_type::is_register
  (string& name,char& kind,klunk& index) const
{
static map<string,string> ALIASES =
  {
    {"IA","I0"},{"IB","I1"},{"IC","I2"},{"ID","I3"},
    {"SAR","I4"},{"DAR","I5"},{"OR","I6"},{"IR","I7"},
    {"ZR","I11"},
    {"SP","I12"},{"FP","I13"},{"RP","I14"},{"HP","I15"},
    {"FA","F0"},{"FB","F1"},{"FC","F2"},{"FD","F3"}
  };

  for (auto iter=ALIASES.cbegin();
       iter!=ALIASES.cend();
       iter++)
    if (iter->first == name)
      name = iter->second;
  kind = name[0];
  if ((kind != 'I') && (kind != 'F'))
  {
    kind = 'X';
    index = 0;
    return false;
  }
  try
  {
    index = stoull(name.substr(1,name.size()-1));
    if (0 <= index < 16)
      return true;
    else
    {
      kind = 'X';
      index = 0;
      return false;
    }
  }
  catch (const runtime_error& e)
  {
    kind = 'X';
    index = 0;
    return false;
  }
}

klunk registers_type::get_register
  (string& name) const
{
  char kind;
  klunk index;
  if (is_register(name,kind,index))
    if (kind == 'I')
      return Iregisters[index];
    else
      return Fregisters[index];
  else
  {
    cerr << "invalid register (" << name << ")\n";
    exit(EXIT_FAILURE);
  }

}
void registers_type::set_register
  (string& name,klunk val)
{
  char kind;
  klunk index;
  if (is_register(name,kind,index))
    if (kind == 'I')
      Iregisters[index] = val;
    else
      Fregisters[index] = val;
}

void registers_type::display_Iregisters (void) const
{
  cout << "I registers" << endl;
  for (int i=0;i<NUMBER_REGISTERS;i++)
    cout << setw(5) << left << "I"+to_string(i)
         << setw(16) << left << klunk2hex(Iregisters[i])
         << endl;
  cout << endl;
}

void registers_type::display_Fregisters (void) const
{
  cout << "F registers" << endl;
  for (int i=0;i<NUMBER_REGISTERS;i++)
    cout << setw(5) << left << "F"+to_string(i)
         << setw(16) << left << klunk2hex(Fregisters[i])
         << endl;
  cout << endl;
}

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

bool global_table::is_global (const string& n) const
{
  for (klunk i=0;i<data.size();i++)
    if (data[i] == n)
      return true;
  return false;
}

void global_table::display (void) const
{
  cout << "GLOBAL TABLE:" << endl;
  for (klunk i=0;i<data.size();i++)
    cout << setw(20) << left << data[i]
         << endl;
  cout << endl;
}

bool extern_table::is_extern (const string& n) const
{
  for (klunk i=0;i<data.size();i++)
    if (data[i] == n)
      return true;
  return false;
}

void extern_table::display (void) const
{
  cout << "EXTERN TABLE:" << endl;
  for (klunk i=0;i<data.size();i++)
    cout << setw(20) << left << data[i]
         << endl;
  cout << endl;
}

bool label_table::is_label (const string& n) const
{
  for (klunk i=0;i<data.size();i++)
    if (data[i].get_name() == n)
      return true;
  return false;
}

void label_table::display (void) const
{
  cout << "LABEL TABLE:" << endl;
  for (klunk i=0;i<data.size();i++)
    cout << setw(20) << left << data[i].get_name()
         << setw(10) << left << data[i].get_location()
         << endl;
  cout << endl;
}

klunk label_table::get_location (const string& n) const
{
  for (klunk i=0;i<data.size();i++)
    if (data[i].get_name() == n)
      return data[i].get_location();
  return 0ULL;
}

