#include <nall/file.hpp>
#include <nall/stdint.hpp>
#include <nall/string.hpp>
using namespace nall;

static bool cycle_accurate;

struct opcode_t {
  string name;
  lstring args;
  uint opcode;
};

auto generate(const char* sourceFilename, const char* targetFilename) -> void {
  file fp;
  fp.open(targetFilename, file::mode::write);

  string filedata;
  filedata.readfile(sourceFilename);
  filedata.replace("\r", "");

  lstring block;
  block.split("\n\n", filedata);

  foreach(data, block) {
    lstring lines;
    lines.split("\n", data);

    linear_vector<opcode_t> array;

    uint sourceStart = 0;
    foreach(line, lines, currentLine) {
      line.transform("()", "``");
      lstring part;
      part.split("`", line);
      lstring arguments;
      arguments.split(", ", part[1]);

      opcode_t opcode;
      opcode.name = part[0];
      opcode.args = arguments;
      opcode.opcode = hex(arguments[0]);
      array.append(opcode);

      line.rtrim(",", 1L);
      if(line.endswith(" {")) {
        line.rtrim("{ ", 1L);
        sourceStart = currentLine + 1;
        break;
      }
    }

    if(cycle_accurate == false) {
      foreach(opcode, array) {
        fp.print("case 0x", hex(opcode.opcode, 2L), ": {\n");

        for(uint n = sourceStart; n < lines.size(); n++) {
          if(lines[n] == "}") break;

          string output;

          if(lines[n].beginswith("  ")) {
            output = lines[n];
          } else {
            lstring part;
            part.split<1>(":", lines[n]);
            output = { "  ", part[1] };
          }

          output.replace("$1", opcode.args[1]);
          output.replace("$2", opcode.args[2]);
          output.replace("$3", opcode.args[3]);
          output.replace("$4", opcode.args[4]);
          output.replace("$5", opcode.args[5]);
          output.replace("$6", opcode.args[6]);
          output.replace("$7", opcode.args[7]);
          output.replace("$8", opcode.args[8]);
          output.replace("end;", "break;");

          fp.print(output, "\n");
        }

        fp.print("  break;\n");
        fp.print("}\n\n");
      }
    } else {
      foreach(opcode, array) {
        fp.print("case 0x", hex(opcode.opcode, 2L), ": {\n");
        fp.print("  switch(opcode_cycle++) {\n");

        for(uint n = sourceStart; n < lines.size(); n++) {
          if(lines[n] == "}") break;

          bool nextLineEndsCycle = false;
          if(lines[n + 1] == "}") nextLineEndsCycle = true;
          if(lines[n + 1].beginswith("  ") == false) nextLineEndsCycle = true;

          string output;

          if(lines[n].beginswith("  ")) {
            output = { "  ", lines[n] };
          } else {
            lstring part;
            part.split(":", lines[n], 1L);
            fp.print("  case ", (unsigned)decimal(part[0]), ":\n");
            output = { "    ", part[1] };
          }

          output.replace("$1", opcode.args[1]);
          output.replace("$2", opcode.args[2]);
          output.replace("$3", opcode.args[3]);
          output.replace("$4", opcode.args[4]);
          output.replace("$5", opcode.args[5]);
          output.replace("$6", opcode.args[6]);
          output.replace("$7", opcode.args[7]);
          output.replace("$8", opcode.args[8]);
          output.replace("end;", "{ opcode_cycle = 0; break; }");

          fp.print(output, "\n");
          if(nextLineEndsCycle) {
            if(lines[n + 1].beginswith("}")) {
              fp.print("    opcode_cycle = 0;\n");
            }
            fp.print("    break;\n");
          }
        }

        fp.print("  }\n");
        fp.print("  break;\n");
        fp.print("}\n\n");
      }
    }
  }

  fp.close();
}

auto main() -> int {
  cycle_accurate = false;
  generate("op_misc.b", "op_misc.cpp");
  generate("op_mov.b",  "op_mov.cpp" );
  generate("op_pc.b",   "op_pc.cpp" );
  generate("op_read.b", "op_read.cpp");
  generate("op_rmw.b",  "op_rmw.cpp" );

  cycle_accurate = true;
  generate("op_misc.b", "opcycle_misc.cpp");
  generate("op_mov.b",  "opcycle_mov.cpp" );
  generate("op_pc.b",   "opcycle_pc.cpp" );
  generate("op_read.b", "opcycle_read.cpp");
  generate("op_rmw.b",  "opcycle_rmw.cpp" );

  return 0;
}
