#define STRVIEW_IMPLEMENTATION
#include "strview.h"

#include "compiler.h"
#include "parser.h"

#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static char* read_file(const char* path) {
  FILE* file = fopen(path, "rb");
  if (file == NULL) {
    return NULL;
  }
  if (fseek(file, 0, SEEK_END) != 0) {
    fclose(file);
    return NULL;
  }
  long length = ftell(file);
  if (length < 0 || fseek(file, 0, SEEK_SET) != 0) {
    fclose(file);
    return NULL;
  }
  char* text = malloc((size_t)length + 1);
  if (text == NULL) {
    fclose(file);
    return NULL;
  }
  size_t read_count = fread(text, 1, (size_t)length, file);
  bool ok = read_count == (size_t)length && !ferror(file);
  fclose(file);
  if (!ok) {
    free(text);
    return NULL;
  }
  text[length] = '\0';
  return text;
}

static void print_error(const char* stage, const Error* error) {
  if (error->line > 0) {
    fprintf(stderr, "%s:%d: %s\n", stage, error->line, error->message);
  } else {
    fprintf(stderr, "%s: %s\n", stage, error->message);
  }
}

static void usage(FILE* stream, const char* name) {
  fprintf(stream, "usage: %s [-n] PROGRAM OUTPUT [SCRIPT_ARGUMENT ...]\n", name);
  fprintf(stream, "  -n  compile and print without running ffmpeg\n");
}

int main(int argc, char** argv) {
  if (argc == 4 && sv_equal(sv_from_cstr(argv[1]), svlit("--internal-subtitle-clip"))) {
    char* end = NULL;
    errno = 0;
    double duration = strtod(argv[3], &end);
    Error error = {0};
    if (errno != 0 || end == argv[3] || *end != '\0' || !isfinite(duration) || duration < 0 ||
        !subtitle_clip_file(argv[2], duration, &error)) {
      if (error.message[0] == '\0') snprintf(error.message, sizeof(error.message), "invalid duration");
      print_error("subtitle clip", &error);
      return 1;
    }
    return 0;
  }
  if (argc == 5 &&
      sv_equal(sv_from_cstr(argv[1]), svlit("--internal-subtitle-concat-list"))) {
    Error error = {0};
    if (!subtitle_write_concat_list(argv[2], argv[3], argv[4], &error)) {
      print_error("subtitle concat", &error);
      return 1;
    }
    return 0;
  }
  if (argc == 4 && sv_equal(sv_from_cstr(argv[1]), svlit("--internal-write-file"))) {
    Error error = {0};
    if (!write_generated_file(argv[2], argv[3], &error)) {
      print_error("generated metadata", &error);
      return 1;
    }
    return 0;
  }
  if (argc == 4 && sv_equal(sv_from_cstr(argv[1]), svlit("--internal-write-file-hex"))) {
    Error error = {0};
    if (!write_generated_hex_file(argv[2], argv[3], &error)) {
      print_error("generated metadata", &error);
      return 1;
    }
    return 0;
  }
  if (argc == 4 && sv_equal(sv_from_cstr(argv[1]), svlit("--internal-copy-file"))) {
    Error error = {0};
    if (!copy_generated_file(argv[2], argv[3], &error)) {
      print_error("font attachment", &error);
      return 1;
    }
    return 0;
  }
  if (argc == 3 && sv_equal(sv_from_cstr(argv[1]), svlit("--internal-mkdir"))) {
    Error error = {0};
    if (!ensure_generated_directory(argv[2], &error)) {
      print_error("font directory", &error);
      return 1;
    }
    return 0;
  }
  bool dry_run = false;
  int argument = 1;
  if (argument < argc && sv_equal(sv_from_cstr(argv[argument]), svlit("-n"))) {
    dry_run = true;
    argument++;
  }
  if (argc - argument < 2) {
    usage(stderr, argv[0]);
    return 2;
  }

  const char* program_path = argv[argument];
  const char* output_path = argv[argument + 1];
  size_t script_argument_count = (size_t)(argc - argument - 2);
  const char* const* script_arguments = (const char* const*)(argv + argument + 2);
  char* text = read_file(program_path);
  if (text == NULL) {
    fprintf(stderr, "%s: cannot read %s: %s\n", argv[0], program_path, strerror(errno));
    return 1;
  }

  Error error = {0};
  Program program;
  TypedProgram typed;
  CompiledCommand command;
  ProbeCache probes;
  probe_cache_init(&probes, NULL, NULL);

  if (!parse_program_with_args(text, script_argument_count, script_arguments, &program, &error)) {
    print_error(program_path, &error);
    free(text);
    probe_cache_destroy(&probes);
    return 1;
  }
  free(text);
  if (!typecheck_program(&program, &typed, &error)) {
    print_error(program_path, &error);
    program_destroy(&program);
    probe_cache_destroy(&probes);
    return 1;
  }
  if (!compile_program(&program, &typed, &probes, output_path, &command, &error)) {
    print_error(program_path, &error);
    typed_program_destroy(&typed);
    program_destroy(&program);
    probe_cache_destroy(&probes);
    return 1;
  }

  printf("%s\n", command.shell_command);
  int exit_code = 0;
  if (!dry_run && !execute_command(&command, &exit_code, &error)) {
    print_error("ffmpeg", &error);
    exit_code = 1;
  }

  compiled_command_destroy(&command);
  typed_program_destroy(&typed);
  program_destroy(&program);
  probe_cache_destroy(&probes);
  return exit_code;
}