/** \file   src/lib/hvsc.h
 * \brief   Library header
 *
 * This file only contains prototypes, types and defines which are supposed to
 * be public.
 *
 * XXX: currently exposes to much internal stuff, perhaps use some opaque
 *      types (ie void*)?
 *
 * \author  Bas Wassink <b.wassink@ziggo.nl>
 */

/*
 *  HVSClib - a library to work with High Voltage SID Collection files
 *  Copyright (C) 2018-2022  Bas Wassink <b.wassink@ziggo.nl>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, 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 License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.*
 */

#ifndef HVSC_HVSC_H
#define HVSC_HVSC_H

#if 0
/** \brief  Make hvsclib standalone
 *
 * If defined hvsclib will use its own memory allocation, string and path
 * handling functions, not the ones provided by VICE, this allows me to further
 * develop hvsclib as a standalone library as well.
 */
#define HVSC_STANDALONE
#endif

#ifndef HVSC_STANDALONE
# include "vice.h"
#else
/* Compatibility with VICE's defines (keeps hits in src/findhacks.sh low): */
# ifdef _WIN32
/* Windows */
#  define WINDOWS_COMPILE
#  ifdef _WIN64
/* 64-bit Windows */
#   define WIN64_COMPILE
#  endif
# endif
#endif

/* for size_t, fixed width types and bool */
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>

/*
 * Microsoft's msvcrt does support C99's %z printf format specifier, so we
 * need to work around that.
 *
 * archdep_defs.h defines PRI_SIZE_T, but I want to keep this code independent
 * from VICE code so it can be used in other projects.
 */
#ifndef PRI_SIZE_T
# ifdef WINDOWS_COMPILE
#  define PRI_SIZE_T    "Iu"
# else
#  define PRI_SIZE_T    "zu"
# endif
#endif


/** \brief  Error codes
 */
typedef enum hvsc_err_e {
    HVSC_ERR_OK = 0,            /**< no error */
    HVSC_ERR_IO,                /**< I/O error */
    HVSC_ERR_FILE_TOO_LARGE,    /**< file too large (> 2GB) */
    HVSC_ERR_GCRYPT,            /**< error in gcrypt library */
    HVSC_ERR_TIMESTAMP,         /**< error parsing a timestamp */
    HVSC_ERR_NOT_FOUND,         /**< entry/tune not found */
    HVSC_ERR_INVALID,           /**< invalid data or operation detected */

    HVSC_ERR_CODE_COUNT         /**< number of error messages */

} hvsc_err_t;


/** \brief  STIL entry field type enumeration
 */
typedef enum hvsc_stil_field_type_e {
    HVSC_FIELD_INVALID = -1,    /**< unknow field type */
    HVSC_FIELD_ARTIST,          /**< Artist (in case of cover) */
    HVSC_FIELD_AUTHOR,          /**< Author (of SID/subtune) */
    HVSC_FIELD_BUG,             /**< Bug note (only in BUGlist.txt) */
    HVSC_FIELD_COMMENT,         /**< Comment */
    HVSC_FIELD_NAME,            /**< (sub)tune name */
    HVSC_FIELD_TITLE,           /**< title of the cover */

    HVSC_FIELD_TYPE_COUNT       /**< number of valid field types */
} hvsc_stil_field_type_t;


/** \brief  Handle for the text file reader functions
 */
typedef struct hvsc_text_file_s {
    FILE   *fp;         /**< file pointer */
    char   *path;       /**< copy of the path of the file (for error messages) */
    long    lineno;     /**< line number in file */
    size_t  linelen;    /**< line length */
    char   *buffer;     /**< buffer for line data, grows when required */
    char   *prevbuf;    /**< previous content of buffer (for path lookup via md5sum) */
    size_t  buflen;     /**< size of buffer, grows when needed */
} hvsc_text_file_t;


/** \brief  STIL timestamp object
 *
 * Set `to` to -1 to signal only `from` should be used. Set `from` to -1 to
 * signal the entire timestamp is unused. Both entries are in milliseconds.
 *
 * Examples: no timestamp would result in { -1, -1 }
 *           "(0:30)" would result in { 300000, -1 }
 *           "(0:30-2:15)" would result in { 30000, 135000 }
 */
typedef struct hvsc_stil_timestamp_s {
    long from;  /**< 'from' timestamp, or only timestamp */
    long to;    /**< 'to' timestamp, optional */
} hvsc_stil_timestamp_t;

/** \brief  STIL field object
 */
typedef struct hvsc_stil_field_s {
    hvsc_stil_field_type_t  type;       /**< field type */
    char                   *text;       /**< field content */
    hvsc_stil_timestamp_t   timestamp;  /**< timestamp (optional) */
    char                   *album;      /**< cover info (optional) */
} hvsc_stil_field_t;

/** \brief  STIL block object
 */
typedef struct hvsc_stil_block_s {
    int                 tune;           /**< tune number
                                             (0 = global/only tune) */
    hvsc_stil_field_t **fields;         /**< list of STIL fields, in order of
                                             the text in the STIL.txt file */
    size_t              fields_max;     /**< size of the fields array */
    size_t              fields_used;    /**< used entries in the fields array */
} hvsc_stil_block_t;

/** \brief  Handle for the STIL functions
 */
typedef struct hvsc_stil_s {
    hvsc_text_file_t    stil;           /**< handle for the STIL.txt file */
    char               *psid_path;      /**< path to PSID file */
    char                md5sum[33];     /**< md5sum as hexadecimal string */
    char              **entry_buffer;   /**< content of the STIL entry */
    size_t              entry_bufmax;   /**< number of available entries in
                                             the entry_buffer */
    size_t              entry_bufused;  /**< number of used entries in the
                                             entry_buffer */
    char               *sid_comment;    /**< global comment (optional) */
    hvsc_stil_block_t **blocks;         /**< STIL blocks */
    size_t              blocks_max;     /**< number of available blocks */
    size_t              blocks_used;    /**< number of used blocks */
} hvsc_stil_t;

/** \brief  Handle for the BUGlist functions
 */
typedef struct hvsc_bugs_s {
    hvsc_text_file_t  bugs;             /**< handle for the BUGlist.txt file */
    char             *psid_path;        /**< path to PSID file */
    char             *text;             /**< text about the bug */
    char             *user;             /**< person reporting the bug */
} hvsc_bugs_t;

/** \brief  STIL tune entry object
 */
typedef struct hvsc_stil_tune_entry_s {
    int                 tune;           /**< tune number in the SID (1-256) */
    hvsc_stil_field_t **fields;         /**< STIL fields array */
    size_t              field_count;    /**< number of fields in \a fields */
} hvsc_stil_tune_entry_t;



/*
 * psid.c public defines and types
 */

/** \brief  Length of the magic bytes in the header
 * \ingroup psid
 */
#define HVSC_PSID_MAGIC_LEN         0x04

/** \brief  Length of a text field in the header
 *
 * This is the size of the name, author, and copyright fields. These fields are
 * in ASCII (Latin-1) and are NOT nul-terminated.
 *
 * \ingroup psid
 */
#define HVSC_PSID_TEXT_LEN          0x20


/*
 * Bitmasks for the 'flags' word in a PSID header
 */

/** \brief  Bit 0: Compute!'s Sidplayer
 * \ingroup psid
 */
#define HVSC_PSID_FLAGS_MUS_PLAYER      0x0001

/** \brief  Bit 1: tune is PlaySID specific
 * \ingroup psid
 */
#define HVSC_PSID_FLAGS_PSID_SPECIFIC   0x0002

/** \brief  Bits 2-3: video standard (clock)
 *
 * 00 = unknown, 01 = PAL, 10 = NTSC, 11 = PAL and NTSC
 * \ingroup psid
 */
#define HVSC_PSID_FLAGS_CLOCK           0x000c

/** \brief  Bits 4-5: first SID model
 *
 * 00 = unkown, 01 = MOS6581, 10 = MOS8580, 11 = MOS6581 and MOS8580
 * \ingroup psid
 */
#define HVSC_PSID_FLAGS_SID_MODEL1      0x0030

/** \brief  Bits 6-7: second SID model
 *
 * 00 = unkown, 01 = MOS6581, 10 = MOS8580, 11 = MOS6581 and MOS8580
 * \ingroup psid
 */
#define HVSC_PSID_FLAGS_SID_MODEL2      0x00c0

/** \brief  Bits 8-9: third SID model
 *
 * 00 = unkown, 01 = MOS6581, 10 = MOS8580, 11 = MOS6581 and MOS8580
 * \ingroup psid
 */
#define HVSC_PSID_FLAGS_SID_MODEL3      0x0300

/*
 * bits 10-15 of the 'flags' word are reserved for future use
 */

/** \brief  PSID handle
 *
 * \see https://www.hvsc.c64.org/download/C64Music/DOCUMENTS/SID_file_format.txt
 * \ingroup psid
 */
typedef struct hvsc_psid_s {

    /* Information on the entire file */
    char     *path;                                 /**< path to psid file */
    uint8_t  *data;                                 /**< data of psid file */
    size_t    size;                                 /**< size of psid file */

    /* Header data */
    uint8_t   magic[HVSC_PSID_MAGIC_LEN];           /**< magic bytes */
    uint16_t  version;                              /**< version number */

    uint16_t  data_offset;                          /**< offset to SID data */
    uint16_t  load_address;                         /**< load address on C64 */
    uint16_t  init_address;                         /**< init address on C64 */
    uint16_t  play_address;                         /**< play address on C64 */

    uint16_t  songs;                                /**< number of songs */
    uint16_t  start_song;                           /**< starting song */
    uint32_t  speed;                                /**< song speed flags */

    char      name[HVSC_PSID_TEXT_LEN + 1];         /**< SID name */
    char      author[HVSC_PSID_TEXT_LEN + 1];       /**< SID author */
    char      copyright[HVSC_PSID_TEXT_LEN + 1];    /**< SID copyright */

    /* PSIDv2NG+ only fields */
    uint16_t  flags;                                /**< PSID flags */
    uint8_t   start_page;                           /**< starting page of free
                                                         memory not touched by
                                                         the SID */
    uint8_t   page_length;                          /**< number of free pages
                                                         after start page */
    uint16_t  second_sid;                           /**< 2nd SID I/O address */
    uint16_t  third_sid;                            /**< 3rd SID I/O adress */

} hvsc_psid_t;


/*
 * main.c stuff
 */

bool        hvsc_init           (const char *path);
void        hvsc_exit           (void);
const char *hvsc_lib_version_str(void);
void        hvsc_lib_version_num(int *major, int *minor, int *revision);

/*
 * base.c stuff
 */

extern int hvsc_errno;

const char *hvsc_strerror  (int n);
void        hvsc_perror    (const char *prefix);
bool        hvsc_md5_digest(const char *psid, char *digest);

/*
 * sldb.c stuff
 */

char *      hvsc_sldb_get_entry_md5   (const char *psid);
char *      hvsc_sldb_get_entry_txt   (const char *psid);
int         hvsc_sldb_get_lengths     (const char *psid, long **lengths);
int         hvsc_sldb_get_lengths_md5 (const char *digest, long **lengths);
char *      hvsc_sldb_get_path_for_md5(const char *digest);

/*
 * stil.c stuff
 */

bool        hvsc_stil_open       (const char *psid,   hvsc_stil_t *handle);
bool        hvsc_stil_open_md5   (const char *digest, hvsc_stil_t *handle);
void        hvsc_stil_close      (hvsc_stil_t *handle);
bool        hvsc_stil_read_entry (hvsc_stil_t *handle);
void        hvsc_stil_dump_entry (hvsc_stil_t *handle);
void        hvsc_stil_parse_entry(hvsc_stil_t *handle);
void        hvsc_stil_dump       (hvsc_stil_t *handle);

/* XXX: needs much better name
 *
 * This combines calls of stil_open(), stil_read_entry() and stil_parse_entry()
 * It's probably best to make those functions static and leave this one.
 * */
bool        hvsc_stil_get            (hvsc_stil_t *stil, const char *path);
bool        hvsc_stil_get_md5        (hvsc_stil_t *stil, const char *digest);
bool        hvsc_stil_get_tune_entry (const hvsc_stil_t      *handle,
                                      hvsc_stil_tune_entry_t *entry,
                                      int                     tune);
void        hvsc_stil_dump_tune_entry(const hvsc_stil_tune_entry_t *entry);

/*
 * bugs.c stuff
 */

bool        hvsc_bugs_open (const char *psid, hvsc_bugs_t *handle);
void        hvsc_bugs_close(hvsc_bugs_t *handle);


/*
 * psid.c stuff
 */

bool            hvsc_psid_open         (const char *path, hvsc_psid_t *handle);
void            hvsc_psid_close        (hvsc_psid_t *handle);
void            hvsc_psid_dump         (const hvsc_psid_t *handle);
bool            hvsc_psid_write_bin    (const hvsc_psid_t *handle, const char *path);
unsigned int    hvsc_psid_get_model_id (const hvsc_psid_t *handle, int sid);
const char *    hvsc_psid_get_model_str(const hvsc_psid_t *handle, int sid);
unsigned int    hvsc_psid_get_clock_id (const hvsc_psid_t *handle);
const char *    hvsc_psid_get_clock_str(const hvsc_psid_t *handle);

/*
 * Misc
 */

const char *    hvsc_get_field_display(int type);

#endif
