/*
 * Copyright 2023 Alyssa Rosenzweig
 * Copyright 2020 Intel Corporation
 * SPDX-License-Identifier: MIT
 */

#include "compiler/clc/clc.h"
#include "util/hash_table.h"
#include "util/set.h"
#include "util/u_dynarray.h"

#include <getopt.h>
#include <inttypes.h>
#include <stdio.h>

static void
msg_callback(void *priv, const char *msg)
{
   (void)priv;
   fprintf(stderr, "%s", msg);
}

static void
print_usage(char *exec_name, FILE *f)
{
   fprintf(
      f,
      "Usage: %s [options] [input files] -- [clang args]\n"
      "Options:\n"
      "  -h  --help              Print this help.\n"
      "  -o, --out <filename>    Specify the output filename.\n"
      "  -v, --verbose           Print more information during compilation.\n",
      exec_name);
}

int
main(int argc, char **argv)
{
   static struct option long_options[] = {
      {"help", no_argument, 0, 'h'},
      {"in", required_argument, 0, 'i'},
      {"out", required_argument, 0, 'o'},
      {"depfile", required_argument, 0, 'd'},
      {"verbose", no_argument, 0, 'v'},
      {0, 0, 0, 0},
   };

   char *outfile = NULL, *depfile = NULL;
   struct util_dynarray clang_args;
   struct util_dynarray input_files;
   struct util_dynarray spirv_objs;
   struct util_dynarray spirv_ptr_objs;

   void *mem_ctx = ralloc_context(NULL);

   util_dynarray_init(&clang_args, mem_ctx);
   util_dynarray_init(&input_files, mem_ctx);
   util_dynarray_init(&spirv_objs, mem_ctx);
   util_dynarray_init(&spirv_ptr_objs, mem_ctx);

   struct set *deps =
      _mesa_set_create(mem_ctx, _mesa_hash_string, _mesa_key_string_equal);

   int ch;
   while ((ch = getopt_long(argc, argv, "he:i:o:d:v", long_options, NULL)) !=
          -1) {
      switch (ch) {
      case 'h':
         print_usage(argv[0], stdout);
         return 0;
      case 'o':
         outfile = optarg;
         break;
      case 'd':
         depfile = optarg;
         break;
      default:
         fprintf(stderr, "Unrecognized option \"%s\".\n", optarg);
         print_usage(argv[0], stderr);
         return 1;
      }
   }

   for (int i = optind; i < argc; i++) {
      char *arg = argv[i];
      bool option = arg[0] == '-';

      util_dynarray_append(option ? &clang_args : &input_files, char *, arg);
   }

   if (util_dynarray_num_elements(&input_files, char *) == 0) {
      fprintf(stderr, "No input file(s).\n");
      print_usage(argv[0], stderr);
      return -1;
   }

   if (outfile == NULL) {
      fprintf(stderr, "No output specified.\n");
      print_usage(argv[0], stderr);
      return -1;
   }

   struct clc_logger logger = {
      .error = msg_callback,
      .warning = msg_callback,
   };

   util_dynarray_foreach(&input_files, char *, infile) {
      FILE *fp = fopen(*infile, "rb");
      if (!fp) {
         fprintf(stderr, "Failed to open %s\n", *infile);
         ralloc_free(mem_ctx);
         return 1;
      }

      fseek(fp, 0L, SEEK_END);
      size_t len = ftell(fp);
      rewind(fp);

      char *map = ralloc_array_size(mem_ctx, 1, len + 1);
      if (!map) {
         fprintf(stderr, "Failed to allocate");
         ralloc_free(mem_ctx);
         return 1;
      }

      fread(map, 1, len, fp);
      map[len] = 0;
      fclose(fp);

      struct clc_compile_args clc_args = {
         .source.name = *infile,
         .source.value = map,
         .args = util_dynarray_begin(&clang_args),
         .num_args = util_dynarray_num_elements(&clang_args, char *),
         .c_compatible = true,
      };

      /* Enable all features, we don't know the target here and it is the
       * responsibility of the driver to only use features they will actually
       * support. Not our job to blow up here.
       */
      memset(&clc_args.features, true, sizeof(clc_args.features));

      struct clc_binary *spirv_out =
         util_dynarray_grow(&spirv_objs, struct clc_binary, 1);

      if (!clc_compile_c_to_spirv(&clc_args, &logger, spirv_out, deps)) {
         ralloc_free(mem_ctx);
         return 1;
      }
   }


   util_dynarray_foreach(&spirv_objs, struct clc_binary, p) {
      util_dynarray_append(&spirv_ptr_objs, struct clc_binary *, p);
   }

   struct clc_linker_args link_args = {
      .in_objs = util_dynarray_begin(&spirv_ptr_objs),
      .num_in_objs =
         util_dynarray_num_elements(&spirv_ptr_objs, struct clc_binary *),
      .create_library = true,
   };
   struct clc_binary final_spirv;
   if (!clc_link_spirv(&link_args, &logger, &final_spirv)) {
      ralloc_free(mem_ctx);
      return 1;
   }

   FILE *fp = fopen(outfile, "w");
   fwrite(final_spirv.data, final_spirv.size, 1, fp);
   fclose(fp);

   if (depfile) {
      FILE *fp = fopen(depfile, "w");
      fprintf(fp, "%s:", outfile);
      set_foreach(deps, ent) {
         fprintf(fp, " %s", (const char *)ent->key);
      }
      fprintf(fp, "\n");
      fclose(fp);
   }

   util_dynarray_foreach(&spirv_objs, struct clc_binary, p) {
      clc_free_spirv(p);
   }

   clc_free_spirv(&final_spirv);
   ralloc_free(mem_ctx);

   return 0;
}
