
#include "config.h"

#include <orc/orc.h>
#include <orc/orcparse.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

void output_code_emulate (OrcProgram *p, FILE *output);

int verbose = 0;
int error = 0;
int compat;

char *target = "sse";

#define ORC_VERSION(a,b,c,d) ((a)*1000000 + (b)*10000 + (c)*100 + (d))
#define REQUIRE(a,b,c,d) do { \
  if (ORC_VERSION((a),(b),(c),(d)) > compat) { \
    fprintf(stderr, "Feature used that is incompatible with --compat\n"); \
    exit (1); \
  } \
} while (0)

void help (void)
{
  printf("Usage:\n");
  printf("  generate-emulation [OPTION...]\n");
  printf("\n");
  printf("Help Options:\n");
  printf("  -h, --help              Show help options\n");
  printf("\n");
  printf("Application Options:\n");
  printf("  -o, --output FILE       Write output to FILE\n");
  printf("      --header            Write header instead of .c file\n");
  printf("\n");

  exit (0);
}

int
main (int argc, char *argv[])
{
  char *output_file = NULL;
  char *input_file = NULL;
  FILE *output;
  int i;
  OrcOpcodeSet *opcode_set;
  int output_header = FALSE;

  orc_init ();

  for(i=1;i<argc;i++) {
    if (strcmp (argv[i], "--output") == 0 ||
        strcmp(argv[i], "-o") == 0) {
      if (i+1 < argc) {
        output_file = argv[i+1];
        i++;
      } else {
        help();
      }
    } else if (strcmp (argv[i], "--header") == 0) {
      output_header = TRUE;
    } else if (strncmp(argv[i], "-", 1) == 0) {
      printf("Unknown option: %s\n", argv[i]);
      exit (1);
    } else {
      if (input_file == NULL) {
        input_file = argv[i];
      } else {
        printf("More than one input file specified: %s\n", argv[i]);
        exit (1);
      }
    }
  }

  if (output_file == NULL) {
    output_file = output_header ? "out.h" : "out.c";
    printf("Writing to file: %s\n", output_file);
  }

  output = fopen (output_file, "w");
  if (!output) {
    printf("Could not write output file: %s\n", output_file);
    exit(1);
  }

  opcode_set = orc_opcode_set_get ("sys");

  fprintf(output, "\n");
  fprintf(output, "/* autogenerated by generate-emulation */\n");
  fprintf(output, "\n");

  if (output_header) {
    fprintf(output, "#ifndef _ORC_EMULATE_OPCODES_H_\n");
    fprintf(output, "#define _ORC_EMULATE_OPCODES_H_\n");
    fprintf(output, "\n");
    for(i=0;i<opcode_set->n_opcodes;i++){
      OrcStaticOpcode *opcode = opcode_set->opcodes + i;

      fprintf(output,
          "void emulate_%s (OrcOpcodeExecutor *ex, int i, int n);\n",
          opcode->name);
    }
    fprintf(output, "\n");
    fprintf(output, "#endif\n");
    fprintf(output, "\n");
  } else {
  
    fprintf(output, "#ifdef HAVE_CONFIG_H\n");
    fprintf(output, "#include \"config.h\"\n");
    fprintf(output, "#endif\n");
    fprintf(output, "#include <math.h>\n");
    fprintf(output, "#include <orc/orc.h>\n");
    fprintf(output, "\n");
    fprintf(output, "%s", orc_target_get_asm_preamble ("c"));
    fprintf(output, "\n");

    for(i=0;i<opcode_set->n_opcodes;i++){
      char s[40];
      OrcProgram *program;
      OrcStaticOpcode *opcode = opcode_set->opcodes + i;
      int args[4] = { -1, -1, -1, -1 };
      int n_args = 0;

      program = orc_program_new ();

      sprintf(s, "emulate_%s", opcode->name);
      orc_program_set_name (program, s);

      if (opcode->dest_size[0] != 0) {
        if (opcode->flags & ORC_STATIC_OPCODE_ACCUMULATOR) {
          args[n_args++] =
            orc_program_add_accumulator (program, opcode->dest_size[0], "d1");
        } else {
          args[n_args++] =
            orc_program_add_destination (program, opcode->dest_size[0], "d1");
        }
      }
      if (opcode->dest_size[1] != 0) {
        args[n_args++] =
          orc_program_add_destination (program, opcode->dest_size[1], "d2");
      }
      if (opcode->src_size[0] != 0) {
        if (opcode->src_size[1] == 0 &&
            opcode->flags & ORC_STATIC_OPCODE_SCALAR) {
          args[n_args++] =
            orc_program_add_parameter (program, opcode->src_size[0], "s1");
        } else {
          args[n_args++] =
            orc_program_add_source (program, opcode->src_size[0], "s1");
        }
      }
      if (opcode->src_size[1] != 0) {
        if (opcode->flags & ORC_STATIC_OPCODE_SCALAR) {
          args[n_args++] =
            orc_program_add_parameter (program, opcode->src_size[1], "s2");
        } else {
          args[n_args++] =
            orc_program_add_source (program, opcode->src_size[1], "s2");
        }
      }
      if (opcode->src_size[2] != 0) {
        if (opcode->flags & ORC_STATIC_OPCODE_SCALAR) {
          args[n_args++] =
            orc_program_add_parameter (program, opcode->src_size[2], "s3");
        } else {
          args[n_args++] =
            orc_program_add_source (program, opcode->src_size[2], "s3");
        }
      }

      orc_program_append_2 (program, opcode->name, 0, args[0], args[1],
          args[2], args[3]);

      output_code_emulate (program, output);
    }
  }

  fclose (output);

  if (error) exit(1);

  return 0;
}


const char *varnames[] = {
  "d1", "d2", "d3", "d4",
  "s1", "s2", "s3", "s4",
  "s5", "s6", "s7", "s8",
  "a1", "a2", "a3", "d4",
  "c1", "c2", "c3", "c4",
  "c5", "c6", "c7", "c8",
  "p1", "p2", "p3", "p4",
  "p5", "p6", "p7", "p8",
  "t1", "t2", "t3", "t4",
  "t5", "t6", "t7", "t8",
  "t9", "t10", "t11", "t12",
  "t13", "t14", "t15", "t16"
};

const char *enumnames[] = {
  "ORC_VAR_D1", "ORC_VAR_D2", "ORC_VAR_D3", "ORC_VAR_D4",
  "ORC_VAR_S1", "ORC_VAR_S2", "ORC_VAR_S3", "ORC_VAR_S4",
  "ORC_VAR_S5", "ORC_VAR_S6", "ORC_VAR_S7", "ORC_VAR_S8",
  "ORC_VAR_A1", "ORC_VAR_A2", "ORC_VAR_A3", "ORC_VAR_A4",
  "ORC_VAR_C1", "ORC_VAR_C2", "ORC_VAR_C3", "ORC_VAR_C4",
  "ORC_VAR_C5", "ORC_VAR_C6", "ORC_VAR_C7", "ORC_VAR_C8",
  "ORC_VAR_P1", "ORC_VAR_P2", "ORC_VAR_P3", "ORC_VAR_P4",
  "ORC_VAR_P5", "ORC_VAR_P6", "ORC_VAR_P7", "ORC_VAR_P8",
  "ORC_VAR_T1", "ORC_VAR_T2", "ORC_VAR_T3", "ORC_VAR_T4",
  "ORC_VAR_T5", "ORC_VAR_T6", "ORC_VAR_T7", "ORC_VAR_T8",
  "ORC_VAR_T9", "ORC_VAR_T10", "ORC_VAR_T11", "ORC_VAR_T12",
  "ORC_VAR_T13", "ORC_VAR_T14", "ORC_VAR_T15", "ORC_VAR_T16"
};

#if 0
void
output_prototype (OrcProgram *p, FILE *output)
{
  OrcVariable *var;
  int i;
  int need_comma;

  fprintf(output, "%s (", p->name);
  need_comma = FALSE;
  for(i=0;i<4;i++){
    var = &p->vars[ORC_VAR_D1 + i];
    if (var->size) {
      if (need_comma) fprintf(output, ", ");
      if (var->type_name) {
        fprintf(output, "%s * %s", var->type_name,
            varnames[ORC_VAR_D1 + i]);
      } else {
        fprintf(output, "orc_uint%d * %s", var->size*8,
            varnames[ORC_VAR_D1 + i]);
      }
      if (p->is_2d) {
        fprintf(output, ", int %s_stride", varnames[ORC_VAR_D1 + i]);
      }
      need_comma = TRUE;
    }
  }
  for(i=0;i<4;i++){
    var = &p->vars[ORC_VAR_A1 + i];
    if (var->size) {
      if (need_comma) fprintf(output, ", ");
      if (var->type_name) {
        fprintf(output, "%s * %s", var->type_name,
            varnames[ORC_VAR_A1 + i]);
      } else {
        fprintf(output, "orc_uint%d * %s", var->size*8,
            varnames[ORC_VAR_A1 + i]);
      }
      need_comma = TRUE;
    }
  }
  for(i=0;i<8;i++){
    var = &p->vars[ORC_VAR_S1 + i];
    if (var->size) {
      if (need_comma) fprintf(output, ", ");
      if (var->type_name) {
        fprintf(output, "const %s * %s", var->type_name,
            varnames[ORC_VAR_S1 + i]);
      } else {
        fprintf(output, "const orc_uint%d * %s", var->size*8,
            varnames[ORC_VAR_S1 + i]);
      }
      if (p->is_2d) {
        fprintf(output, ", int %s_stride", varnames[ORC_VAR_S1 + i]);
      }
      need_comma = TRUE;
    }
  }
  for(i=0;i<8;i++){
    var = &p->vars[ORC_VAR_P1 + i];
    if (var->size) {
      if (need_comma) fprintf(output, ", ");
      if (var->is_float_param) {
        REQUIRE(0,4,5,1);
      }
      fprintf(output, "%s %s",
          var->is_float_param ? "float" : "int",
          varnames[ORC_VAR_P1 + i]);
      need_comma = TRUE;
    }
  }
  if (p->constant_n == 0) {
    if (need_comma) fprintf(output, ", ");
    fprintf(output, "int n");
    need_comma = TRUE;
  }
  if (p->is_2d && p->constant_m == 0) {
    if (need_comma) fprintf(output, ", ");
    fprintf(output, "int m");
  }
  fprintf(output, ")");
}

void
output_code_header (OrcProgram *p, FILE *output)
{
  fprintf(output, "void ");
  output_prototype (p, output);
  fprintf(output, ";\n");
}
#endif

void
output_code_emulate (OrcProgram *p, FILE *output)
{

  fprintf(output, "void\n");
  if (p->constant_n) {
    fprintf(output, "%s (OrcOpcodeExecutor *ex, int offset)\n", p->name);
  } else {
    fprintf(output, "%s (OrcOpcodeExecutor *ex, int offset, int n)\n", p->name);
  }
  fprintf(output, "{\n");
  {
    OrcCompileResult result;

    result = orc_program_compile_full (p, orc_target_get_by_name("c"),
        ORC_TARGET_C_BARE | ORC_TARGET_C_OPCODE);
    if (ORC_COMPILE_RESULT_IS_SUCCESSFUL(result)) {
      fprintf(output, "%s\n", orc_program_get_asm_code (p));
    } else {
      printf("Failed to compile %s\n", p->name);
      error = TRUE;
    }
  }
  fprintf(output, "}\n");
  fprintf(output, "\n");

}


