/*
 *  SF3KtoProT - Converts Star Fighter 3000 music to Amiga ProTracker format
 *  Sound samples index file parser
 *  Copyright (C) 2009  Chris Bazley
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public Licence as published by
 *  the Free Software Foundation; either version 2 of the Licence, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public Licence for more details.
 *
 *  You should have received a copy of the GNU General Public Licence
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* ISO library header files */
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <limits.h>

/* Local header files */
#include "platform.h"
#include "samp.h"
#include "protracker.h"

#ifdef NDEBUG
#define DEBUG_OUT if (0) printf
#else /* NDEBUG */
#define DEBUG_OUT if (1) printf
#endif /* NDEBUG */

#define INIT_SIZE 4

static void report_error(const char   *error,
                         unsigned int  line_no,
                         const char   *index_file)
{
  fprintf(stderr, "%s at line %u of samples index file '%s'\n",
          error, line_no + 1, index_file);
}

SampleInfo *load_sample_index(bool          verbose,
                              const char   *index_file,
                              const char   *samples_dir,
                              unsigned int *nsamp)
{
  unsigned int line_no, slimit = 0;
  SampleInfo *sample_info = NULL;
  char *sample_path = NULL;
  char file_name[12], str_buf[256], type_buf[2];
  FILE *f = NULL, *sample_handle = NULL;
  bool success = false; /* pessimistic */

  assert(index_file != NULL);
  assert(samples_dir != NULL);
  assert(nsamp != NULL);

  /* Open samples index file */
  if (verbose)
    printf("Opening sound samples index file '%s'\n", index_file);

  f = fopen(index_file, "r"); /* open text for reading */
  if (f == NULL) {
    fprintf(stderr,"Failed to open samples index file '%s'\n", index_file);
    goto cleanup;
  }

  slimit = 0;
  for (line_no = 0; !feof(f); line_no++) {
    SampleInfo *write_ptr;
    int nread, tuning, repeat_offset, sample_id;
    const char *nws;
    long int len;

    /* Read one line of text at a time from the samples index file */
    if (fgets(str_buf, sizeof(str_buf), f) == NULL) {
      if (feof(f))
        break; /* don't ask me - I just work here */

      fprintf(stderr, "Error reading line %u from file '%s'\n",
              line_no + 1, index_file);
      goto cleanup;
    }

    /* Skip comments */
    if (*str_buf == '#') {
      DEBUG_OUT("Skipping a comment\n");
      continue;
    }

    /* Eat up any leading whitespace characters */
    for (nws = str_buf; *nws == ' ' || *nws == '\t'; nws++)
    {}
    DEBUG_OUT("Ate %u leading spaces\n", nws - str_buf);

    /* Skip blank lines */
    if (*nws == '\n') {
      DEBUG_OUT("Skipping a blank line\n");
      continue;
    }

    /* Parse the text line and extract the sample's attributes */
    nread = sscanf(nws, "%d %11s %d %1s %d\n",
                   &sample_id, file_name, &repeat_offset, type_buf, &tuning);
    if (nread != 5) {
      report_error("Syntax error", line_no, index_file);
      goto cleanup;
    }

    if (sample_id < 0 || sample_id > UCHAR_MAX) {
      report_error("Bad ID", line_no, index_file);
      goto cleanup;
    }

    if (sample_id >= slimit) {
      /* (Re-)allocate buffer for data */
      size_t new_size, new_limit;
      unsigned int s;
      SampleInfo *new_buf;

      DEBUG_OUT("Sample ID %u is beyond end of array\n", sample_id);
      new_limit = slimit;
      do {
        if (new_limit == 0)
          new_limit = INIT_SIZE;
        else
          new_limit *= 2;
      } while (sample_id >= new_limit);
      DEBUG_OUT("Extending samples array from %u to %u\n",
                slimit, new_limit);

      new_size = new_limit * sizeof(*sample_info);
      new_buf = realloc(sample_info, new_size);

      if (new_buf == NULL) {
        fprintf(stderr, "Failed to allocate %u bytes for samples index '%s'\n",
                new_size, index_file);
        goto cleanup;
      }

      /* Mark the newly allocated array elements as unused */
      for (s = slimit; s < new_limit; s++)
        new_buf[s].type = SampleInfo_Type_Unused;

      sample_info = new_buf;
      slimit = new_limit;
    }

    write_ptr = sample_info + sample_id;
    if (write_ptr->type != SampleInfo_Type_Unused) {
      report_error("ID already used", line_no, index_file);
      goto cleanup;
    }

    strncpy(write_ptr->file_name, file_name, sizeof(write_ptr->file_name) - 1);
    write_ptr->file_name[sizeof(write_ptr->file_name) - 1] = '\0';

    /* Construct full path name of sample data file */
    sample_path = append_leaf(samples_dir, file_name);
    if (sample_path == NULL) {
      fprintf(stderr,"Failed to allocate memory for sample file path\n");
      goto cleanup;
    }

    /* Get the length of the sample data file */
    if (verbose)
      printf("Opening sample data file '%s'\n", sample_path);

    sample_handle = fopen(sample_path, "rb"); /* open binary for reading */
    if (sample_handle == NULL) {
      fprintf(stderr, "Failed to open sample data file '%s'\n", sample_path);
      goto cleanup;
    }

    if (!fseek(sample_handle, 0, SEEK_END))
      len = ftell(sample_handle);
    else
      len = -1L;

    if (len < 0) {
      fprintf(stderr, "Couldn't determine length of sample data file '%s'\n",
                      sample_path);
      goto cleanup;
    }
    /* Note that even a sample that is apparently too long for ProTracker
       format might only be included in a form where it has been pre-tuned
       upward by one or more octaves (thus shortening it). */

    free(sample_path);
    sample_path = NULL;

    if (verbose)
      puts("Closing sample data file");
    fclose(sample_handle);
    sample_handle = NULL;

    /* ProTracker isn't capable of representing odd sample lengths or offsets
       within a sample (only multiples of 2). Also, the length is in bytes
       (2 bytes per sample frame) */
    if (repeat_offset < 0 ||
        repeat_offset / 2l >= len / 4) {
      report_error("Bad repeat offset", line_no, index_file);
      goto cleanup;
    }
    write_ptr->len = len;
    write_ptr->repeat_offset = repeat_offset;

    switch (*type_buf) {
      case 'e':
      case 'E':
        write_ptr->type = SampleInfo_Type_Effect;
        break;

      case 'm':
      case 'M':
        write_ptr->type = SampleInfo_Type_Music;
        break;

      default:
        report_error("Bad sample type", line_no, index_file);
        goto cleanup;
    }

    if (!check_tuning(tuning)) {
      report_error("Bad tuning value", line_no, index_file);
      goto cleanup;
    }
    write_ptr->tuning = tuning;

    if (verbose)
      printf("Sample %u ('%s') has length %lu, tuning %d and repeats from %d\n",
             sample_id,
             write_ptr->file_name,
             write_ptr->len,
             write_ptr->tuning,
             write_ptr->repeat_offset);
  }

  success = true;

cleanup:
  if (f != NULL) {
    if (verbose)
      puts("Closing sound samples index file");

    fclose(f);
  }

  free(sample_path);
  if (sample_handle != NULL) {
    if (verbose)
      puts("Closing sample data file");
    fclose(sample_handle);
  }

  if (!success) {
    free(sample_info);
    sample_info = NULL;
    *nsamp = 0;
  } else {
    *nsamp = slimit;
  }
  return sample_info;
}
