#include <inttypes.h>
#include <stdlib.h>
#include <string.h>

#include <libaudcore/audstrings.h>
#include <libaudcore/runtime.h>
#include <libaudcore/vfs.h>

#include "ayemu_vtxfile.h"
#include "vtx.h"

/* Read 8-bit integer from file.
 * Return 1 if error occurs.
 */
static int read_byte(VFSFile & fp, int * p)
{
    unsigned char c;
    if (fp.fread(&c, 1, sizeof c) != sizeof c)
    {
        AUDERR("read_byte() error\n");
        return 1;
    }

    *p = c;
    return 0;
}

/* Read 16-bit integer from file.
 * Return 1 if error occurs.
 */
static int read_word16(VFSFile & fp, int * p)
{
    unsigned char c[2];
    if (fp.fread(&c, 1, sizeof c) != sizeof c)
    {
        AUDERR("read_word16() error\n");
        return 1;
    }

    *p = c[0] | (c[1] << 8);
    return 0;
}

/* Read 32-bit integer from file.
 * Return 1 if error occurs.
 */
static int read_word32(VFSFile & fp, int32_t * p)
{
    unsigned char c[4];
    if (fp.fread(&c, 1, sizeof c) != sizeof c)
    {
        AUDERR("read_word32() error\n");
        return 1;
    }

    *p = c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24);
    return 0;
}

/* Read NTstring: reads null-terminated string from file.
 * Return 1 if error occurs.
 */
static int read_NTstring(VFSFile & fp, char s[])
{
    int i, n = 0;
    unsigned char c;

    for (i = 0; i < AYEMU_VTX_NTSTRING_MAX &&
                (n = fp.fread(&c, 1, sizeof c)) == sizeof c && c;
         i++)
    {
        s[i] = c;
    }
    s[i] = '\0';

    if (n != sizeof c)
    {
        AUDERR("Unexpected end of file!\n");
        return 1;
    }

    return 0;
}

/**
 * Read VTX file header.
 * \return true on success, else false.
 */
bool ayemu_vtx_t::read_header(VFSFile & file)
{
    char buf[2];
    int error = 0;
    int32_t int_regdata_size = 0;

    if (file.fread(buf, sizeof buf, 1) != 1)
    {
        AUDERR("Can't read from %s\n", file.filename());
        error = 1;
    }

    if (!strcmp_nocase(buf, "ay", 2))
        hdr.chiptype = AYEMU_AY;
    else if (!strcmp_nocase(buf, "ym", 2))
        hdr.chiptype = AYEMU_YM;
    else
    {
        AUDERR("File %s is _not_ a VORTEX format!\n"
               "It does not begin with AY or YM.\n",
               file.filename());
        error = 1;
    }

    /* read VTX header info in order format specified */
    if (!error)
        error = read_byte(file, &hdr.stereo);
    if (!error)
        error = read_word16(file, &hdr.loop);
    if (!error)
        error = read_word32(file, &hdr.chipFreq);
    if (!error)
        error = read_byte(file, &hdr.playerFreq);
    if (!error)
        error = read_word16(file, &hdr.year);
    if (!error)
    {
        error = read_word32(file, &int_regdata_size);
        hdr.regdata_size = int_regdata_size;
    }
    if (!error)
        error = read_NTstring(file, hdr.title);
    if (!error)
        error = read_NTstring(file, hdr.author);
    if (!error)
        error = read_NTstring(file, hdr.from);
    if (!error)
        error = read_NTstring(file, hdr.tracker);
    if (!error)
        error = read_NTstring(file, hdr.comment);

    return !error;
}

/**
 * Read and decode lha data from .vtx file.
 * \return true on success, false when decoding failed.
 */
bool ayemu_vtx_t::load_data(VFSFile & file)
{
    /* read packed AY register data to end of file */
    Index<char> packed_data = file.read_all();

    regdata.resize(hdr.regdata_size);
    if (!lh5_decode(packed_data, regdata))
        return false;

    pos = 0;
    return true;
}

/**
 * Get next 14-bytes frame of AY register data.
 * \return true on success, false if there is not enough data.
 */
bool ayemu_vtx_t::get_next_frame(unsigned char * regs)
{
    int numframes = hdr.regdata_size / 14;
    if (pos++ >= numframes)
        return false;

    unsigned char * p = &regdata[pos];
    for (int n = 0; n < 14; n++, p += numframes)
        regs[n] = *p;

    return true;
}

/**
 * Print formatted file name.
 * If fmt is nullptr, the default format %a - %t will be used.
 *
 * %% the % sign
 * %a author of song
 * %t song title
 * %y year
 * %f song from
 * %T tracker
 * %C comment
 * %s stereo type (ABC, BCA, ...)
 * %l 'looped' or 'non-looped'
 * %c chip type: 'AY' or 'YM'
 * %F chip freq
 * %P player freq
 */
StringBuf ayemu_vtx_t::sprintname(const char * fmt)
{
    static const char * stereo_types[] = {
        "MONO", "ABC", "ACB", "BAC", "BCA",  "CAB", "CBA"
    };

    if (!fmt)
        fmt = "%a - %t";

    StringBuf buf(0);

    while (*fmt)
    {
        if (*fmt == '%')
        {
            switch (*++fmt)
            {
            case 'a':
                buf.insert(-1, hdr.author);
                break;
            case 't':
                buf.insert(-1, hdr.title);
                break;
            case 'y':
                str_insert_int(buf, -1, hdr.year);
                break;
            case 'f':
                buf.insert(-1, hdr.from);
                break;
            case 'T':
                buf.insert(-1, hdr.tracker);
                break;
            case 'C':
                buf.insert(-1, hdr.comment);
                break;
            case 's':
                buf.insert(-1, stereo_types[hdr.stereo]);
                break;
            case 'l':
                buf.insert(-1, (hdr.loop) ? "looped" : "non-looped");
                break;
            case 'c':
                buf.insert(-1, (hdr.chiptype == AYEMU_AY) ? "AY" : "YM");
                break;
            case 'F':
                str_insert_int(buf, -1, hdr.chipFreq);
                break;
            case 'P':
                str_insert_int(buf, -1, hdr.playerFreq);
                break;
            default:
                buf.insert(-1, fmt, 1);
            }
            fmt++;
        }
        else
        {
            const char * p = strchr(fmt, '%');
            if (!p)
                p = fmt + strlen(fmt);

            buf.insert(-1, fmt, p - fmt);
            fmt = p;
        }
    }

    return buf;
}
