/* ======================================================================== */
/*  SERIALIZER                                                              */
/*                                                                          */
/*  Writes out the current state of jzIntv as a bunch of key/value pairs.   */
/*  Key/value pair lists are hung off a hierarchy of namespaces to allow    */
/*  short, simple names to be used for keys and to simplify disambiguating  */
/*  multiple instances of a given object (e.g. two PSGs, etc.).             */
/* ======================================================================== */

#include "config.h"
#include "serializer/serializer.h"

static ser_hier_t *ser_hier = NULL;

/* ======================================================================== */
/*  SER_REGISTER:  Register key/value pair that will be serialized.         */
/* ======================================================================== */
void ser_register
(
    ser_hier_t  *hier,      /*  What namespace to put this under.           */
    char        *name,      /*  What name to give to the object.            */
    void        *object,    /*  Pointer to the object itself.               */
    ser_type_t  type,       /*  8/16/32 bit, scalar, string, array,etc.     */
    int         length,     /*  Length of object if an array.               */
    uint_32     flags       /*  mandatory/optional/informative/init         */
)
{
    ser_list_t *new_rec, *node, *prev;

    /* -------------------------------------------------------------------- */
    /*  First scan the list to make sure we have no dupes at this level.    */
    /*  If we find a dupe, error-out for now.  It's a programming error.    */
    /* -------------------------------------------------------------------- */
    prev = NULL;
    node = hier->obj_list;

    while (node)
    {
        if (!strcmp(node->name, name))
        {
            fprintf(stderr, "Error: Duplicate key '%s' in serializer\n", name);
            exit(1);
        }
        prev = node;
        node = node->next;
    }

    /* -------------------------------------------------------------------- */
    /*  Now append the new name.                                            */
    /* -------------------------------------------------------------------- */
    new_rec = calloc(1, sizeof(ser_list_t));

    new_rec->name   = strdup(name);
    new_rec->object = object;
    new_rec->type   = type;
    new_rec->length = length;
    new_rec->flags  = flags;
    new_rec->next   = NULL;

    if (prev) prev->next     = new_rec;
    else      hier->obj_list = new_rec;
    
    /* -------------------------------------------------------------------- */
    /*  If this key has its INIT flag set, set it in the hierarchies that   */
    /*  contain it.  If it does not have its INIT flag set, set the NONINIT */
    /*  flag in the hierarchy.                                              */
    /* -------------------------------------------------------------------- */
    flags &= SER_INIT;
    if (!flags)
        flags = SER_NONINIT;

    while (hier)
    {
        hier->flags |= flags;
        hier = hier->parent;
    }

    return;
}

/* ======================================================================== */
/*  SER_NEW_HIERARCHY:  Add a new namespace to the hierarchy.               */
/* ======================================================================== */
ser_hier_t *ser_new_hierarchy
(
    ser_hier_t  *parent_hier,
    char        *name
)
{
    ser_hier_t *new_hier, *node, *prev;

    /* -------------------------------------------------------------------- */
    /*  First scan the list to make sure we have no dupes at this level.    */
    /*  If we find a dupe, error-out for now.  It's a programming error.    */
    /* -------------------------------------------------------------------- */
    prev = NULL;
    node = parent_hier ? parent_hier->hier_list : ser_hier;

    while (node)
    {
        if (!strcmp(node->name, name))
        {
            fprintf(stderr, "Error: Duplicate hier '%s' in serializer\n", name);
            exit(1);
        }
        prev = node;
        node = node->next;
    }

    /* -------------------------------------------------------------------- */
    /*  Now create and append the new hierarchy.                            */
    /* -------------------------------------------------------------------- */
    new_hier = calloc(1, sizeof(ser_hier_t));

    new_hier->name   = strdup(name);
    new_hier->parent = parent_hier;
    new_hier->next   = NULL;
    new_hier->flags  = 0;

    if (prev)               prev->next             = new_hier;
    else if (parent_hier)   parent_hier->hier_list = new_hier;
    else                    ser_hier               = new_hier;

    return new_hier;
}

/* ======================================================================== */
/*  SER_GET_INT                                                             */
/* ======================================================================== */
uint_64 ser_get_int(void *object, ser_type_t type, void **next)
{
    uint_64 value;
    union
    {
        uint_8  *pu8;   sint_8  *ps8;
        uint_16 *pu16;  sint_16 *ps16;
        uint_32 *pu32;  sint_32 *ps32;
        uint_64 *pu64;  sint_64 *ps64;
        void    *v;
    } ptr;

    ptr.v = object;

    switch (type)
    {
        case ser_u8 : value = *ptr.pu8 ++; break;
        case ser_u16: value = *ptr.pu16++; break;
        case ser_u32: value = *ptr.pu32++; break;
        case ser_u64: value = *ptr.pu64++; break;
        case ser_s8 : value = *ptr.ps8 ++; break;
        case ser_s16: value = *ptr.ps16++; break;
        case ser_s32: value = *ptr.ps32++; break;
        case ser_s64: value = *ptr.ps64++; break;
        default:      fprintf(stderr, "ser_get_int called w/ bad type %d\n",
                              (int)type);
                      exit(1);
    }

    if (next) *next = ptr.v;

    return value;
}

static char ser_int_buf[20];

/* ======================================================================== */
/*  SER_INT_TO_STR                                                          */
/* ======================================================================== */
char *ser_int_to_str(uint_64 value, ser_type_t type, uint_32 flags, int fix)
{
    char *format;
    static char ffmt[10];

    if (flags & SER_HEX)
    {
        switch (type)
        {
            case ser_u8 : case ser_s8 :  format = "$%.2llX"; break;
            case ser_u16: case ser_s16:  format = "$%.4llX"; break;
            case ser_u32: case ser_s32:  format = "$%.8llX"; break;
            case ser_u64: case ser_s64:  format = "$%.16llX"; break;
            default: fprintf(stderr, "ser_int_to_str called w/ bad type %d\n",
                             (int)type);
                     exit(1);
        }
    } else if (fix)
    {
        format = ffmt;

        switch (type)
        {
            case ser_u8 : case ser_u16: 
            case ser_u32: case ser_u64: snprintf(ffmt,9,"%%%dllu", fix); break;
            case ser_s8 : case ser_s16: 
            case ser_s32: case ser_s64: snprintf(ffmt,9,"%%%dlld", fix); break;
            default: fprintf(stderr, "ser_int_to_str called w/ bad type %d\n",
                             (int)type);
                     exit(1);
        }
        ffmt[9] = 0;
    } else
    {
        switch (type)
        {
            case ser_u8 : case ser_u16: 
            case ser_u32: case ser_u64: format = "%llu"; break;
            case ser_s8 : case ser_s16: 
            case ser_s32: case ser_s64: format = "%lld"; break;
            default: fprintf(stderr, "ser_int_to_str called w/ bad type %d\n",
                             (int)type);
                     exit(1);
        }
    }

    snprintf(ser_int_buf, 19, format, value);
    ser_int_buf[19] = 0;

    return ser_int_buf;
}


/* ======================================================================== */
/*  SER_PRINT_ARRAY                                                         */
/* ======================================================================== */
void ser_print_array
(
    FILE        *f,
    ser_list_t  *obj,
    int         indent
)
{
    int     i, l, col;
    void    *p;
    uint_64 v;
    char    *s;

    fprintf(f, "%*s%s =\n%*s{\n%*s", 
            indent, "", obj->name, indent, "", indent + 4, "");

    col = indent + 4;
    
    p = obj->object;
    for (i = 0; i < obj->length; i++)
    {
        v = ser_get_int   (p, obj->type, &p);
        s = ser_int_to_str(v, obj->type, obj->flags, 0);
        l = strlen(s);

        if (col + l > 80 && col > indent + 4)
        {
            fprintf(f, "\n%*s", indent + 4, "");
            col = indent + 4;
        } else if (col > indent + 4)
            fputc(' ', f);

        fputs(s, f);
        col += l + 2;

        if (i != obj->length - 1)
            fputc(',', f);
    }

    fprintf(f, "\n%*s}\n", indent, "");

    return;
}

/* ======================================================================== */
/*  SER_PRINT_INT                                                           */
/* ======================================================================== */
void ser_print_int
(
    FILE        *f,
    ser_list_t  *obj,
    int         indent
)
{
    fprintf(f, "%*s%s = %s;\n", indent, "", obj->name, 
            ser_int_to_str(ser_get_int(obj->object, obj->type, NULL), 
                           obj->type, obj->flags, 0));

    return;
}

/* ======================================================================== */
/*  SER_PRINT_STRING                                                        */
/* ======================================================================== */
void ser_print_string
(
    FILE        *f,
    ser_list_t  *obj,
    int         indent
)
{
    fprintf(f, "%*s%s = \"%s\";\n", indent, "", obj->name, 
            obj->object ? (char *)obj->object : NULL);

    return;
}


/* ======================================================================== */
/*  SER_PRINT_HIERARCHY:  Do it!                                            */
/* ======================================================================== */
void ser_print_hierarchy(FILE *f, ser_hier_t *node, int init, int indent)
{
    ser_list_t *object;
    ser_hier_t *child;
 
    /* -------------------------------------------------------------------- */
    /*  Do this in two passes:  Catch all the initialization stuff first    */
    /*  and then capture all the rest.  This isn't necessary, but it's a    */
    /*  nicety.  This function's recursive.                                 */
    /* -------------------------------------------------------------------- */
    if (!node)
    {
        fprintf(f, "/* Initialization */\n");
        ser_print_hierarchy(f, ser_hier, 1, 0);

        fprintf(f, "\n/* Emulator state */\n");
        ser_print_hierarchy(f, ser_hier, 0, 0);

        return;
    }

    /* -------------------------------------------------------------------- */
    /*  Do the actual printing.                                             */
    /* -------------------------------------------------------------------- */
    while (node)
    {
        int nl_flag = 1;

        if ((!node->obj_list && !node->hier_list) ||
           ( init && (node->flags & SER_INIT)    == 0) ||
           (!init && (node->flags & SER_NONINIT) == 0))
        {
            node = node->next;
            continue;
        }

        fprintf(f, "%*s[%s]\n%*s{\n", indent, "", node->name, indent, "");
        for (child = node->hier_list; child; child = child->next)
        {
            if ( init && (node->flags & SER_INIT)    == 0) continue;
            if (!init && (node->flags & SER_NONINIT) == 0) continue;

            if (nl_flag == 0)
                fprintf(f, "\n");
            nl_flag = 0;

            ser_print_hierarchy(f, child, init, indent + 4);
        }

        for (object = node->obj_list; object; object = object->next)
        {
            if ( init && (object->flags & SER_INIT) == 0) continue;
            if (!init && (object->flags & SER_INIT) != 0) continue;

            if (nl_flag == 0)
                fprintf(f, "\n");
            nl_flag = 1;

            if (object->type == ser_string)
                ser_print_string(f, object, indent + 4);
            else if (object->length > 1)
                ser_print_array(f, object, indent + 4);
            else
                ser_print_int(f, object, indent + 4);
        }

        fprintf(f, "%*s}\n", indent, "");

        node = node->next;
    }

    return;
}


/* ======================================================================== */
/*  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) 1998-1999, Joseph Zbiciak                  */
/* ======================================================================== */
