#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#ifdef USE_NLLIBC
#include <nllibc.h>
#endif

#include "config.h"
#include "lib.h"
#include "objlist.h"
#include "type.h"
#include "defines.h"
#include "syntax.h"
#include "lex.h"
#include "asm.h"
#include "asm_format.h"
#include "asm_code.h"
#include "asm_queue.h"
#include "argument.h"
#include "builtin.h"

static int fd = 0;

static int read_func(char *buffer, int size)
{
  static int builtin_offset = 0;
  static int builtin_len = -1;

  if (builtin_len < 0)
    builtin_len = strlen(builtin_symbols);

  if (builtin_len > 0) {
    if (size > builtin_len)
      size = builtin_len;
    memcpy(buffer, &builtin_symbols[builtin_offset], size);
    builtin_offset += size;
    builtin_len -= size;
    return size;
  }

  return read(fd, buffer, size);
}

static int verbose = 0;
static int silent = 0;
static int lex = 0;
static int optlevel = DEFAULT_OPTIMIZE_LEVEL;
static char *outfile = "a.out";
static char *arch = NULL;
static int dummy = 0;
static char *dummystr = NULL;

static argument_t args[] = {
  { "-verbose", ARGUMENT_TYPE_FLAG_ON, &verbose },
  { "-silent" , ARGUMENT_TYPE_FLAG_ON, &silent },
  { "-lex"    , ARGUMENT_TYPE_FLAG_ON, &lex },
  { "-O"      , ARGUMENT_TYPE_FLAG_ON, &optlevel },
  { "-O;"     , ARGUMENT_TYPE_INTEGER, &optlevel },
  { "-o="     , ARGUMENT_TYPE_STRING,  &outfile },
  { "-o"      , ARGUMENT_TYPE_STRING,  &outfile },
  { "-march=" , ARGUMENT_TYPE_STRING,  &arch },

  /* dummy options */
  { "-S"       , ARGUMENT_TYPE_FLAG_ON, &dummy },
  { "-Wall"    , ARGUMENT_TYPE_FLAG_ON, &dummy },
  { "-g"       , ARGUMENT_TYPE_FLAG_ON, &dummy },
  { "-static"  , ARGUMENT_TYPE_FLAG_ON, &dummy },
  { "-shared"  , ARGUMENT_TYPE_FLAG_ON, &dummy },
  { "-v"       , ARGUMENT_TYPE_FLAG_ON, &dummy },
  { "-quiet"   , ARGUMENT_TYPE_FLAG_ON, &dummy },
  { "-version" , ARGUMENT_TYPE_FLAG_ON, &dummy },
  { "-f;"      , ARGUMENT_TYPE_STRING,  &dummystr },
  { "-m;"      , ARGUMENT_TYPE_STRING,  &dummystr },
  { "-I;"      , ARGUMENT_TYPE_STRING,  &dummystr },
  { "-D;"      , ARGUMENT_TYPE_STRING,  &dummystr },
  { "-x"       , ARGUMENT_TYPE_STRING,  &dummystr },
  { "-dumpbase", ARGUMENT_TYPE_STRING,  &dummystr },
  { "-auxbase" , ARGUMENT_TYPE_STRING,  &dummystr },
  { "-mtune="  , ARGUMENT_TYPE_STRING,  &dummystr },

  { NULL, ARGUMENT_TYPE_NONE, NULL }
};

int main(int argc, char *argv[])
{
  objlist_t list, defines_list, symbols;
  area_t static_area;
  syntax_t syntax;
  defines_t defines;
  FILE *out;

  argc--;
  argv++;

  argument_read(&argc, argv, args, 0);

  if (arch == NULL) asm_init_arch(ASM_CODE_ARCH_DEFAULT);
  else if (!strcmp(arch, ""       )) asm_init_arch(ASM_CODE_ARCH_DEFAULT);
  else if (!strcmp(arch, "default")) asm_init_arch(ASM_CODE_ARCH_DEFAULT);
  else if (!strcmp(arch, "i386"   )) asm_init_arch(ASM_CODE_ARCH_I386);
  else if (!strcmp(arch, "x86"    )) asm_init_arch(ASM_CODE_ARCH_I386);
  else if (!strcmp(arch, "amd64"  )) asm_init_arch(ASM_CODE_ARCH_AMD64);
  else if (!strcmp(arch, "x86_64" )) asm_init_arch(ASM_CODE_ARCH_AMD64);
  else if (!strcmp(arch, "arm"    )) asm_init_arch(ASM_CODE_ARCH_ARM);
  else if (!strcmp(arch, "arm32"  )) asm_init_arch(ASM_CODE_ARCH_ARM);
  else if (!strcmp(arch, "mips"   )) asm_init_arch(ASM_CODE_ARCH_MIPS);
  else if (!strcmp(arch, "powerpc")) asm_init_arch(ASM_CODE_ARCH_POWERPC);
  else if (!strcmp(arch, "ppc"    )) asm_init_arch(ASM_CODE_ARCH_POWERPC);
  else if (!strcmp(arch, "aarch64")) asm_init_arch(ASM_CODE_ARCH_AARCH64);
  else if (!strcmp(arch, "arm64"  )) asm_init_arch(ASM_CODE_ARCH_AARCH64);
  else if (!strcmp(arch, "thumb"  )) asm_init_arch(ASM_CODE_ARCH_THUMB);
  else if (!strcmp(arch, "arm16"  )) asm_init_arch(ASM_CODE_ARCH_THUMB);
  else if (!strcmp(arch, "thumb2" )) asm_init_arch(ASM_CODE_ARCH_THUMB2);
  else if (!strcmp(arch, "mips16" )) asm_init_arch(ASM_CODE_ARCH_MIPS16);
  else if (!strcmp(arch, "osecpu" )) asm_init_arch(ASM_CODE_ARCH_OSECPU);
  else if (!strcmp(arch, "riscv32")) asm_init_arch(ASM_CODE_ARCH_RISCV32);
  else if (!strcmp(arch, "riscv"  )) asm_init_arch(ASM_CODE_ARCH_RISCV32);
  else if (!strcmp(arch, "rx"     )) asm_init_arch(ASM_CODE_ARCH_RX);
  else {
    printf("Unknown architecture: %s", arch);
    ERREXITAT(0, 1);
  }

  if (argc > 0) {
    fd = open(argv[0], O_RDONLY);
  }

  list = lex_list_read(read_func);

  if (lex) {
    lex_list_print(list);
    return 0;
  }

  defines_list = objlist_create(NULL);
  defines = defines_create();
  objlist_insert_head(defines_list, &defines->entry);
  symbols = objlist_create(NULL);
  static_area = area_create(C_TYPE_STATIC);

  syntax_init_builtin(defines_list, symbols, static_area);

  list = syntax_list_read_structure(list, NULL, NULL, defines_list,
				    symbols, NULL, static_area, static_area);
  if (list != NULL)
    ERREXITAT(0, 1);

  syntax = syntax_create(C_TYPE_BLOCK, 0, 0, NULL);
  syntax->type = C_TYPE_BLOCK;
  syntax->obj.block.defines = defines;
  syntax->obj.block.symbols = symbols;
  syntax->obj.block.syntaxes = objlist_create(NULL);

  syntax_list_read_control(&syntax->obj.block);

  if (verbose) {
    area_print(static_area, 0);
    printf("\n");

    syntax_print(syntax, 0);
  }

  out = fopen(outfile, "w");
  if (out == NULL) {
    printf("Cannot open: %s", outfile);
    ERREXITAT(0, 1);
  }

  if (optlevel)
    asm_queue_set_level(optlevel);

  if (!silent) {
    asm_symbol_list(out, static_area->symbols);
  }

  asm_queue_flush();

  return 0;
}
