#include "config.h"
#include "icart/icartrom.h"
#include "icart/icarttag.h"
#include "metadata/metadata.h"
#include "metadata/icarttag_metadata.h"
#include "misc/ll.h"

/* ======================================================================== */
/*  While building up the game_metadata_t, keep linked lists of records     */
/*  rather than resizing flat arrays.  The finalizer can then turn the      */
/*  list back into an array.                                                */
/* ======================================================================== */
typedef struct string_ll
{
    ll_t    l;
    char    *string;
} string_ll;

typedef struct tmp_gm_t
{
    int         failed;
    string_ll   *authors;
    string_ll   *game_artists;
    string_ll   *composers;
    string_ll   *sfx_artists;
    string_ll   *voice_actors;
    string_ll   *doc_writers;
    string_ll   *conceptualizers;
    string_ll   *box_artists;
    string_ll   *more_infos;
    string_ll   *publishers;
    string_ll   *licenses;
    string_ll   *descriptions;
    string_ll   *misc;
    string_ll   *versions;

    game_date_t *release_dates;
    game_date_t *build_dates;

    game_metadata_t *gm;
} tmp_gm_t;

/* ======================================================================== */
/*  Convert a linked list of strings into an array.  Free the list.         */
/* ======================================================================== */
LOCAL int gm_ict_string_ll_to_array(string_ll **ll, const char ***dest)
{
    int len = LL_LENGTH(*ll);
    const char **a = NULL;
    int i = len - 1;
    string_ll *curr;

    if (!len) goto leave;

    a = CALLOC(const char *, len + 1);
    if (!a) return IC_OUT_OF_MEMORY;

    for (curr = *ll; curr; curr = (string_ll *)(curr->l.next), --i)
    {
        a[i] = curr->string;
        curr->string = NULL;
    }

leave:
    LL_FREE(*ll);
    *ll = NULL;

    *dest = a;

    return 0;
}

/* ======================================================================== */
/*  GM_ICT_ASCII     -- Add a string to a list.  Build in reverse order.    */
/* ======================================================================== */
LOCAL int gm_ict_ascii(tmp_gm_t *tmp_gm, string_ll **ll, const char *string)
{
    char *string_copy = strdup(string);
    string_ll *head;

    if (!string_copy)
    {
        tmp_gm->failed = 1;
        return IC_OUT_OF_MEMORY;
    }

    head = CALLOC(string_ll, 1);
    if (!head)
    {
        free((void *)string_copy);
        tmp_gm->failed = 1;
        return IC_OUT_OF_MEMORY;
    }

    head->l.next = (ll_t *)*ll;
    head->string  = string_copy;
    *ll = head;

    return 0;
}

/* ======================================================================== */
/*  Forward the various string tag types up to gm_ict_ascii...              */
/* ======================================================================== */
LOCAL int gm_ict_publisher(void *opaque, const ict_publisher_t *t)
{
    tmp_gm_t *tmp_gm = (tmp_gm_t *)opaque;
    return gm_ict_ascii(tmp_gm, &(tmp_gm->publishers), t->name);
}
    
LOCAL int gm_ict_infourl(void *opaque, const ict_infourl_t *i)
{
    tmp_gm_t *tmp_gm = (tmp_gm_t *)opaque;
    return gm_ict_ascii(tmp_gm, &(tmp_gm->more_infos), i->url);
}
    
LOCAL int gm_ict_license(void *opaque, const ict_license_t *l)
{
    tmp_gm_t *tmp_gm = (tmp_gm_t *)opaque;
    return gm_ict_ascii(tmp_gm, &(tmp_gm->licenses), l->license);
}
    
LOCAL int gm_ict_description(void *opaque, const ict_desc_t *d)
{
    tmp_gm_t *tmp_gm = (tmp_gm_t *)opaque;
    return gm_ict_ascii(tmp_gm, &(tmp_gm->descriptions), d->text);
}

LOCAL int gm_ict_version(void *opaque, const ict_version_t *d)
{
    tmp_gm_t *tmp_gm = (tmp_gm_t *)opaque;
    return gm_ict_ascii(tmp_gm, &(tmp_gm->versions), d->text);
}

/* ======================================================================== */
/*  Update the release_date or build_date list.                             */
/* ======================================================================== */
LOCAL int gm_ict_push_date(tmp_gm_t *tmp_gm, game_date_t **da,
                           const game_date_t *d)
{
    int num_dates;

    if (!*da)
    {
        *da = CALLOC(game_date_t, 2);
        if (!*da)
        {
            tmp_gm->failed = 1;
            return IC_OUT_OF_MEMORY;
        }
        (*da)[0] = *d;
        return 0;
    }

    for (num_dates = 0; (*da)[num_dates].year; ++num_dates)
        ;

    *da = REALLOC(*da, game_date_t, num_dates + 2);

    if (!*da)
    {
        tmp_gm->failed = 1;
        return IC_OUT_OF_MEMORY;
    }

    (*da)[num_dates++] = *d;
    (*da)[num_dates].year = 0;
    (*da)[num_dates].month = 0;
    (*da)[num_dates].day = 0;

    return 0;
}

LOCAL int gm_ict_release_date(void *opaque, const game_date_t *d)
{
    tmp_gm_t *tmp_gm = (tmp_gm_t *)opaque;
    /* Since we don't expect more than a couple dates, realloc every time.  */
    game_date_t **da = &tmp_gm->release_dates;
    return gm_ict_push_date(tmp_gm, da, d);
}

LOCAL int gm_ict_build_date(void *opaque, const game_date_t *d)
{
    tmp_gm_t *tmp_gm = (tmp_gm_t *)opaque;
    /* Since we don't expect more than a couple dates, realloc every time.  */
    game_date_t **da = &tmp_gm->build_dates;
    return gm_ict_push_date(tmp_gm, da, d);
}
    
/* ======================================================================== */
/*  Unpack the gnarly CREDITS records                                       */
/* ======================================================================== */
LOCAL int gm_ict_credits(void *opaque, const ict_credits_t *c)
{
    tmp_gm_t *tmp_gm = (tmp_gm_t *)opaque;
    string_ll **by_bit[8] =
    {
        &(tmp_gm->authors),
        &(tmp_gm->game_artists),
        &(tmp_gm->composers),
        &(tmp_gm->sfx_artists),
        &(tmp_gm->voice_actors),
        &(tmp_gm->doc_writers),
        &(tmp_gm->conceptualizers),
        &(tmp_gm->box_artists)
    };
    int b, i, bit, err;

    for (i = 0; c[i].name; ++i)
    {
        for (b = 0; b < 8; ++b)
        {
            bit = 1 << b;
            if (c[i].credbits.u.raw[0] & bit)
            {
                err = gm_ict_ascii(tmp_gm, by_bit[b], c[i].name);
                if (err) return err;
            }
        }
    }

    return 0;
}

/* ======================================================================== */
/*  Unpack the COMPAT bits.  Assume we're non-default if we see this rec.   */
/* ======================================================================== */
LOCAL int gm_ict_compat(void *opaque, const ict_compat_t *c)
{
    tmp_gm_t *tmp_gm = (tmp_gm_t *)opaque;
    game_metadata_t *gm = tmp_gm->gm;

    gm->is_defaults = 0;
    
    gm->ecs_compat      = IC_TO_GM_COMPAT(c->u.s.ecs);
    gm->voice_compat    = IC_TO_GM_COMPAT(c->u.s.intellivoice);
    gm->intv2_compat    = IC_TO_GM_COMPAT(c->u.s.intellivision_2);
    gm->kc_compat       = IC_TO_GM_COMPAT(c->u.s.keyboard_component);
    gm->tv_compat       = IC_TO_GM_COMPAT(c->u.s.tutorvision);
    gm->lto_mapper      = c->u.s.lto_mapper;
    gm->jlp_accel       = (jlp_accel_t)c->u.s.jlp_accel;
    gm->jlp_flash       = c->u.s.jlp_flash_lo | (c->u.s.jlp_flash_hi << 8);

    return 0;
}

/* ======================================================================== */
/*  Now the two easiest ones: Full Title, Short Title.                      */
/* ======================================================================== */
LOCAL int gm_ict_title(tmp_gm_t *tmp_gm, const char **field, const char *name)
{
    char *name_copy = strdup(name);

    if (!name_copy)
    {
        tmp_gm->failed = 1;
        return IC_OUT_OF_MEMORY;
    }

    if (*field) free(remove_const((const void *)*field));
    *field = name_copy;
    return 0;
}

LOCAL int gm_ict_full_title(void *opaque, const ict_title_t *t)
{
    tmp_gm_t *tmp_gm = (tmp_gm_t *)opaque;
    return gm_ict_title(tmp_gm, &(tmp_gm->gm->name), t->name);
}

LOCAL int gm_ict_short_title(void *opaque, const ict_title_t *t)
{
    tmp_gm_t *tmp_gm = (tmp_gm_t *)opaque;
    return gm_ict_title(tmp_gm, &(tmp_gm->gm->short_name), t->name);
}

/* ======================================================================== */
/*  Start the visitor process.                                              */
/* ======================================================================== */
LOCAL int gm_ict_start(void *opaque)
{
    tmp_gm_t *tmp_gm = (tmp_gm_t *)opaque;

    /* See if we've already used this visitor. */
    if (tmp_gm->failed)
        return IC_BAD_ARGS;

    return 0;
}

/* ======================================================================== */
/*  Stop the visitor process.                                               */
/* ======================================================================== */
LOCAL int gm_ict_stop(void *opaque)
{
    tmp_gm_t *tmp_gm = (tmp_gm_t *)opaque;
    int err;

    /* See if we've failed along the way. */
    if (tmp_gm->failed)
        return IC_BAD_ARGS;

    /* We succeeded, so mark this structure as "no longer active." */
    tmp_gm->failed = -1;

    /* Now slurp out the linked lists into arrays. */
#   define SLURP(f) \
    do {                                                                    \
        err = gm_ict_string_ll_to_array(&(tmp_gm->f), &(tmp_gm->gm->f));    \
        if (err)                                                            \
        {                                                                   \
            tmp_gm->failed = 1;                                             \
            return err;                                                     \
        }                                                                   \
    } while (0) 

    SLURP(authors);
    SLURP(game_artists);
    SLURP(composers);
    SLURP(sfx_artists);
    SLURP(voice_actors);
    SLURP(doc_writers);
    SLURP(conceptualizers);
    SLURP(box_artists);
    SLURP(more_infos);
    SLURP(publishers);
    SLURP(licenses);
    SLURP(descriptions);
    SLURP(misc);
    SLURP(versions);

    tmp_gm->gm->release_dates = (const game_date_t *)tmp_gm->release_dates;
    tmp_gm->release_dates = NULL;

    tmp_gm->gm->build_dates = (const game_date_t *)tmp_gm->build_dates;
    tmp_gm->build_dates = NULL;
    
#   undef SLURP
    return 0;
}

const icarttag_visitor_t gm_ict_template =
{
    NULL,
    gm_ict_start,
    gm_ict_stop,
    gm_ict_full_title,
    gm_ict_publisher,
    gm_ict_credits,
    gm_ict_infourl,
    gm_ict_release_date,
    gm_ict_compat,
    gm_ict_short_title,
    gm_ict_license,
    gm_ict_description,
    gm_ict_build_date,
    gm_ict_version
};

/* ======================================================================== */
/*  GET_GAME_METADATA_ICARTTAG_VISITOR                                      */
/*                                                                          */
/*  Returns an icarttag_visitor_t that has an empty game_metadata_t object  */
/*  attached.  This object is suitable for passing to icarttag_decode.      */
/* ======================================================================== */
icarttag_visitor_t *get_game_metadata_icarttag_visitor(void)
{
    game_metadata_t *gm = default_game_metadata( );
    tmp_gm_t *tmp_gm = NULL;
    icarttag_visitor_t *ict_v = NULL;

    if (!gm) goto failed;

    tmp_gm = CALLOC(tmp_gm_t, 1);
    
    if (!tmp_gm) goto failed;

    game_metadata_set_compat_to_unspec( gm );
    tmp_gm->gm = gm;

    ict_v = CALLOC(icarttag_visitor_t, 1);

    if (!ict_v) goto failed;

    *ict_v = gm_ict_template;
    ict_v->opaque = (void *)tmp_gm;

    return ict_v;

failed:
    if (gm) free_game_metadata(gm);
    if (tmp_gm) free(tmp_gm);
    return NULL;
}

/* ======================================================================== */
/*  PUT_GAME_METADATA_ICARTTAG_VISITOR                                      */
/*                                                                          */
/*  Frees the icarttag_visitor_t from get_game_metadata_icarttag_visitor.   */
/*  Returns the game_metadata_t that was constructed, if any.               */
/* ======================================================================== */
struct game_metadata_t *
    put_game_metadata_icarttag_visitor(struct icarttag_visitor_t *ict_v)
{
    tmp_gm_t *tmp_gm = NULL;
    game_metadata_t *gm = NULL;

    if (!ict_v) goto failed;

    tmp_gm = (tmp_gm_t *)ict_v->opaque;

    if (!tmp_gm) goto failed;

    gm = tmp_gm->gm;

    if (tmp_gm->failed != -1) goto failed;
    if (!gm) goto failed;

    game_metadata_set_unspec_compat_to_defaults( gm );

    free(tmp_gm);
    free(ict_v);
    return gm;

failed:
    if (gm)     free_game_metadata(gm);
    if (tmp_gm) free(tmp_gm);
    if (ict_v)  free(ict_v);

    return NULL;
}

/* ======================================================================== */
/*  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., 675 Mass Ave, Cambridge, MA 02139, USA.               */
/* ======================================================================== */
/*                 Copyright (c) 2017-+Inf, Joseph Zbiciak                  */
/* ======================================================================== */
