//////////////////////////////////////////////////////////////////////////////
// Radiant Silvergun EXTRA
//
// Miscellaneous CUE sheet adjustment tool
//
// Copyright (C)2023-2024 Alex "trap15" Marshall <trap15@raidenii.net>
// All rights reserved.
//////////////////////////////////////////////////////////////////////////////
//gcc -static -fno-strict-aliasing -o cuetool ./cuetool.c
// yes this code leaks, no I don't care

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

static bool str_begins_with(const char *str, const char *needle) {
  return (memcmp(str, needle, strlen(needle)) == 0);
}

static char *skip_whitespace(char *str) {
  while(isspace(*str)) str++;
  return str;
}

static void lba_to_msf(char *ptr, unsigned int lba) {
  unsigned int f = lba % 75;
  unsigned int s = (lba / 75) % 60;
  unsigned int m = (lba / (75*60)) % 100;
  sprintf(ptr, "%02u:%02u:%02u", m,s,f);
}

static int str_read_msf(const char *ptr) {
  unsigned int m, s, f;
  if(sscanf(ptr, "%02u:%02u:%02u", &m, &s, &f) != 3) {
    fprintf(stderr, "Invalid MM:SS:FF format in command\n");
    return -1;
  }
  return (((m * 60) + s) * 75) + f;
}

static const char *get_filename(const char *name) {
  const char *fnptr = strrchr(name, '/');
  if(fnptr == NULL) {
    fnptr = strrchr(name, '\\');
  }
  if(fnptr == NULL) {
    fnptr = name;
  }else{
    fnptr++;
  }
  return strdup(fnptr);
}

static const char *get_pathname(const char *name) {
  const char *fnptr = strrchr(name, '/');
  if(fnptr == NULL) {
    fnptr = strrchr(name, '\\');
  }
  if(fnptr == NULL) {
    return strdup("");
  }else{
    char *newname = strdup(name);
    newname[fnptr-name+1] = '\0';
    return newname;
  }
}

int main(int argc, const char *argv[]) {
  if(argc != 2 && argc != 5) {
    fprintf(stderr,
            "Usage:\n"
            "\t%s out.cue in.cue new.bin old.bin\n"
            "OR\n"
            "\t%s in.cue\n"
            ,argv[0], argv[0]);
    return -1;
  }
  const char *arg_outcuefn = NULL;
  const char *arg_incuefn = NULL;
  const char *arg_newbinfn = NULL;
  const char *arg_oldbinfn = NULL;
  bool is_getname = false;

  if(argc == 5) {
    arg_outcuefn = argv[1];
    arg_incuefn = argv[2];
    arg_newbinfn = argv[3];
    arg_oldbinfn = argv[4];
  }else{
    is_getname = true;
    arg_incuefn = argv[1];
  }

  FILE *ifp = NULL, *ofp = NULL;
  size_t new_size = 0, old_size = 0;
  if(arg_newbinfn) {
    ifp = fopen(arg_newbinfn, "rb");
    if(ifp == NULL) {
      fprintf(stderr, "Can't open '%s' binary for reading\n", arg_newbinfn);
      return 1;
    }
    fseek(ifp, 0, SEEK_END);
    new_size = ftell(ifp);
    fclose(ifp);
  }

  if(arg_oldbinfn) {
    ifp = fopen(arg_oldbinfn, "rb");
    if(ifp == NULL) {
      fprintf(stderr, "Can't open '%s' binary for reading\n", arg_oldbinfn);
      return 1;
    }
    fseek(ifp, 0, SEEK_END);
    old_size = ftell(ifp);
    fclose(ifp);
  }

  long int size_diff = new_size - old_size;

  ifp = fopen(arg_incuefn, "r");
  if(ifp == NULL) {
    fprintf(stderr, "Can't open '%s' cue sheet for reading\n", arg_incuefn);
    return 1;
  }
  if(arg_outcuefn) {
    ofp = fopen(arg_outcuefn, "w");
    if(ofp == NULL) {
      fprintf(stderr, "Can't open '%s' cue sheet for writing\n", arg_outcuefn);
      return 1;
    }
  }
  int current_file = 0;
  int current_track = 0;
  for(;;) {
    char line[512];
    if(fgets(line, sizeof(line), ifp) == NULL) {
      break;
    }
    // Only process the file that contains track 1
    if(current_file >= 2) {
      fputs(line, ofp);
      continue;
    }
    char *ptr = skip_whitespace(line);
    if(str_begins_with(ptr, "FILE")) {
      current_file++;
      // Only care about the first file
      if(current_file != 1) goto cmd_done;

      ptr = skip_whitespace(ptr + 4);
      if(*ptr != '"') {
        fprintf(stderr, "Missing expected \" in FILE command\n");
        return 2;
      }
      ptr++;
      if(is_getname) {
        int len = 0;
        char newpath[512];
        for(int i = 0; ptr[i]; i++) {
          if(ptr[i] == '\\') {
            if(ptr[i+1] == '\\' || ptr[i+1] == '"') {
              i++; // escape next char
              newpath[len++] = ptr[i];
            }else{
              newpath[len++] = ptr[i];
            }
          }else if(ptr[i] == '"') {
            break;
          }else{
            newpath[len++] = ptr[i];
          }
        }
        newpath[len] = '\0';

        const char *fname = get_filename(newpath);
        const char *cuepath = get_pathname(arg_incuefn);

        printf("%s%s", cuepath, fname);
        fflush(stdout);
        return 0;
      }else if(arg_newbinfn) {
        const char *fname = get_filename(arg_newbinfn);
        sprintf(ptr, "%s\" BINARY\n", fname);
      }
    }else if(str_begins_with(ptr, "TRACK")) {
      ptr = skip_whitespace(ptr + 5);
      sscanf(ptr, "%02d", &current_track);
    }else if(str_begins_with(ptr, "PREGAP")) {
      // don't care
    }else if(str_begins_with(ptr, "INDEX")) {
      // Don't adjust track 1
      if(current_track == 1) goto cmd_done;
      // No need to adjust if unchanged
      if(size_diff == 0) goto cmd_done;

      ptr = skip_whitespace(ptr + 5);
      ptr = skip_whitespace(ptr + 2);
      int position = str_read_msf(ptr);
      position += (size_diff + 2351) / 2352;
      char outstr[3+3+3];
      lba_to_msf(outstr, position);
      memcpy((char*)ptr, outstr, 3+3+2);
    }
cmd_done:
    if(ofp) {
      fputs(line, ofp);
    }
  }
  if(ofp) {
    fclose(ofp);
  }

  return 0;
}
