/*  ICE STREAMER - X Music Multiplexer System
 *  Copyright (C) 2003-2011 ICE STREAMER Team
 *
 *  PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 */

#include "ice_streamer.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "frost_list.h"
#include "frost_node.h"
#include "frost_mem.h"
#include "frost_common.h"
#include "utilities_debug/cabot_assert.h"

#include "utilities_debug/cabot_assert.h"
#include "utilities_debug/trace.h"
#include "frost_tree.h"
#include "frost_basicstring.h"
#include "frost_str.h"

TRACE_IMPLEMENT("ice/ice_streamer_magic");

#define TRACE_ERROR(x)  printf x

static FROST_List *magic_list, *ext_list;


#define CMP(v1, entry, v2) \
    if (entry->pre_test_and_op) { \
        v1 &= entry->pre_test_and_op; \
    } \
\
    switch (entry->oper) { \
        case ICE_MAGIC_ENTRY_OPERATOR_EQUAL: \
            return (v1 == v2) ? frost_true : frost_false ; \
        case ICE_MAGIC_ENTRY_OPERATOR_LESS_THAN: \
            return (v1 < v2) ? frost_true : frost_false ; \
        case ICE_MAGIC_ENTRY_OPERATOR_GREATER_THAN: \
            return (v1 > v2) ? frost_true : frost_false ; \
        case ICE_MAGIC_ENTRY_OPERATOR_AND: \
            return ((v1 & v2) == v2) ? frost_true : frost_false ; \
        case ICE_MAGIC_ENTRY_OPERATOR_NAND: \
            return ((v1 & v2) != v2) ? frost_true : frost_false ; \
    } \

typedef enum ice_streamer_magic_entry_type_St {
    ICE_MAGIC_ENTRY_TYPE_UNKNOWN = 0,
    ICE_MAGIC_ENTRY_TYPE_BYTE,
    ICE_MAGIC_ENTRY_TYPE_INT16,
    ICE_MAGIC_ENTRY_TYPE_INT32,
    ICE_MAGIC_ENTRY_TYPE_STRING,
    ICE_MAGIC_ENTRY_TYPE_STRINGC,
} ice_streamer_magic_entry_type_t;

typedef enum ice_streamer_magic_entry_operator_St {
    ICE_MAGIC_ENTRY_OPERATOR_EQUAL = 0,
    ICE_MAGIC_ENTRY_OPERATOR_LESS_THAN,
    ICE_MAGIC_ENTRY_OPERATOR_GREATER_THAN,
    ICE_MAGIC_ENTRY_OPERATOR_AND,
    ICE_MAGIC_ENTRY_OPERATOR_NAND
} ice_streamer_magic_entry_operator_t;

typedef struct ice_streamer_magic_entry_St {
    frost_uint offset;
    ice_streamer_magic_entry_type_t type;
    frost_int endian;
    frost_uint len;
    frost_uint pre_test_and_op;
    ice_streamer_magic_entry_operator_t oper;

    union {
        frost_uint8 i8;
        frost_uint16 i16;
        frost_uint32 i32;
        char s[32];
    } value;
} ice_streamer_magic_entry_t;

typedef struct ice_streamer_magic_checker_St {
    ice_streamer_handler_t *xform;
    char *buf;
    frost_uint alloc;
    frost_uint read;
    frost_uint offset;
    frost_int dumpcount;
} ice_streamer_magic_checker_t;

typedef struct ice_streamer_magic_ext_data_St {
    char *type;
    char *pattern;
} ice_streamer_magic_ext_data_t;

static void ice_streamer_magic_tree_free (FROST_Node *tree);

static char *ice_streamer_magic_match (ice_streamer_magic_checker_t *c, const char *u);
static frost_uint ice_streamer_magic_complexity (FROST_Node *tree);

static void
ice_streamer_magic_entry_free (ice_streamer_magic_entry_t *e)
{
    frost_free (e);
}

static ice_streamer_magic_entry_type_t
parse_type (char **s, frost_int *endian)
{
    struct {
        const char *string;
        ice_streamer_magic_entry_type_t type;
        frost_int endian;
    } *t, types[] = {
        {"byte", ICE_MAGIC_ENTRY_TYPE_BYTE, FROST_BYTE_ORDER},
        {"short", ICE_MAGIC_ENTRY_TYPE_INT16, FROST_BYTE_ORDER},
        {"long", ICE_MAGIC_ENTRY_TYPE_INT16, FROST_BYTE_ORDER},
        {"beshort", ICE_MAGIC_ENTRY_TYPE_INT16, FROST_BIFROST_ENDIAN},
        {"belong", ICE_MAGIC_ENTRY_TYPE_INT32, FROST_BIFROST_ENDIAN},
        {"leshort", ICE_MAGIC_ENTRY_TYPE_INT16, FROST_LITTLE_ENDIAN},
        {"lelong", ICE_MAGIC_ENTRY_TYPE_INT32, FROST_LITTLE_ENDIAN},
        {"string/c", ICE_MAGIC_ENTRY_TYPE_STRINGC, FROST_BYTE_ORDER},
        {"string", ICE_MAGIC_ENTRY_TYPE_STRING, FROST_BYTE_ORDER},
        {NULL, ICE_MAGIC_ENTRY_TYPE_UNKNOWN, FROST_BYTE_ORDER}
    };

    for (t = types; t; t++) {
        int l = t->string ? strlen (t->string) : 0;

        if (!l || !strncmp (*s, t->string, l)) {
            *s += l;
            *endian = t->endian;

            return t->type;
        }
    }

    return ICE_MAGIC_ENTRY_TYPE_UNKNOWN ;//    CABOT_ASSERT_not_reached ();
}


static ice_streamer_magic_entry_operator_t
parse_oper (char **s)
{
    char c = **s;
    struct {
        char c;
        ice_streamer_magic_entry_operator_t o;
    } *o, opers[] = {
        {'=', ICE_MAGIC_ENTRY_OPERATOR_EQUAL},
        {'<', ICE_MAGIC_ENTRY_OPERATOR_LESS_THAN},
        {'>', ICE_MAGIC_ENTRY_OPERATOR_GREATER_THAN},
        {'&', ICE_MAGIC_ENTRY_OPERATOR_AND},
        {'^', ICE_MAGIC_ENTRY_OPERATOR_NAND},
        {'\0', ICE_MAGIC_ENTRY_OPERATOR_EQUAL}
    };

    for (o = opers; o; o++) {
        if (!o->c) {
            /* no operator found */
            return o->o;
        } else if (c == o->c) {
            (*s)++; /* skip operator */
            return o->o;
        }
    }

//    CABOT_ASSERT_not_reached ();
}

static frost_bool
parse_pre_test_and_op (ice_streamer_magic_entry_t *entry, char **end)
{
    frost_bool ret = frost_false;

    if (**end == ' ') {
        (*end)++;
        return frost_true;
    }

    switch (entry->type) {
        case ICE_MAGIC_ENTRY_TYPE_BYTE:
        case ICE_MAGIC_ENTRY_TYPE_INT16:
        case ICE_MAGIC_ENTRY_TYPE_INT32:
            if (**end == '&') {
                (*end)++;
                entry->pre_test_and_op = strtoul (*end, end, 0);
                ret = frost_true;
            }
        default:
            break;
    }

    return ret;
}

static ice_streamer_magic_entry_t *
parse_entry (const char *s)
{
    ice_streamer_magic_entry_t *entry;
    char *end = NULL;

    entry = frost_new0 (ice_streamer_magic_entry_t, 1);
    entry->endian = FROST_BYTE_ORDER;
    entry->oper = ICE_MAGIC_ENTRY_OPERATOR_EQUAL;
    entry->offset = strtoul (s, &end, 0);

    end++;

    entry->type = parse_type (&end, &entry->endian);
    if (entry->type == ICE_MAGIC_ENTRY_TYPE_UNKNOWN) {
        frost_free (entry);
        return NULL;
    }

    if (!parse_pre_test_and_op (entry, &end)) {
        frost_free (entry);
        return NULL;
    }

    /* @todo Implement string operators */
    switch (entry->type) {
        case ICE_MAGIC_ENTRY_TYPE_STRING:
        case ICE_MAGIC_ENTRY_TYPE_STRINGC:
            break;
        default:
            entry->oper = parse_oper (&end);
            break;
    }

    switch (entry->type) {
        case ICE_MAGIC_ENTRY_TYPE_BYTE:
            entry->value.i8 = strtoul (end, &end, 0);
            entry->len = 1;
            break;
        case ICE_MAGIC_ENTRY_TYPE_INT16:
            entry->value.i16 = strtoul (end, &end, 0);
            entry->len = 2;
            break;
        case ICE_MAGIC_ENTRY_TYPE_INT32:
            entry->value.i32 = strtoul (end, &end, 0);
            entry->len = 4;
            break;
        case ICE_MAGIC_ENTRY_TYPE_STRING:
        case ICE_MAGIC_ENTRY_TYPE_STRINGC:
            frost_strlcpy (entry->value.s, end, sizeof (entry->value.s));
            entry->len = strlen (entry->value.s);
            break;
        default:
            break; /* won't get here, handled above */
    }

    return entry;
}

static frost_bool
free_node (FROST_Node *node, ice_streamer_magic_entry_t *entry)
{
    if (FROST_NODE_IS_ROOT (node)) {
        frost_pointer *data = (frost_pointer *)node->data;

        /* this isn't a magic entry, but the description of the tree */
        frost_free (data[0]); /* desc */
        frost_free (data[1]); /* mime */
        frost_free (data);
    } else {
        ice_streamer_magic_entry_free (entry);
    }

    return frost_false; /* continue traversal */
}

static void
ice_streamer_magic_tree_free (FROST_Node *tree)
{
    frost_node_traverse (tree, ICE_PRE_ORDER, ICE_TRAVERSE_ALL, -1,
                     (FROST_NodeTraverseFunc) free_node, NULL);
}

static FROST_Node *
ice_streamer_magic_add_node (FROST_Node *tree, const char *s, FROST_Node *prev_node)
{
    ice_streamer_magic_entry_t *entry;
    frost_pointer *data = (frost_pointer *)tree->data;
    frost_uint indent = 0, prev_indent;

    CABOT_ASSERT (s);

    TRACE_INFO( ("adding magic spec to tree '%s'\n", (char *) data[0]));

    /* indent level is number of leading '>' characters */
    while (*s == '>') {
        indent++;
        s++;
    }

    entry = parse_entry (s);
    if (!entry) {
        TRACE_INFO( ("cannot parse magic entry\n"));
        return NULL;
    }

    if (!indent) {
        return frost_node_append_data (tree, entry);
    }

    if (!prev_node) {
        TRACE_INFO( ("invalid indent level\n"));
        ice_streamer_magic_entry_free (entry);
        return NULL;
    }

    prev_indent = frost_node_depth (prev_node) - 2;

    if (indent > prev_indent) {
        /* larger jumps are invalid */
        if (indent != prev_indent + 1) {
            TRACE_INFO( ("invalid indent level\n"));
            ice_streamer_magic_entry_free (entry);
            return NULL;
        }

        return frost_node_append_data (prev_node, entry);
    } else {
        while (indent < prev_indent) {
            prev_indent--;
            prev_node = prev_node->parent;
        }

        return frost_node_insert_after (prev_node->parent, prev_node,
                                    frost_node_new (entry));
    }
}

static frost_int
read_data (ice_streamer_magic_checker_t *c, frost_uint needed)
{
    ice_error_code_t e;

    if (needed > c->alloc) {
        c->alloc = needed;
        c->buf = (char*)frost_realloc (c->buf, c->alloc);
    }

    ice_streamer_error_reset (&e);

    return ice_streamer_handler_peek (c->xform, c->buf, needed, &e);
}

static frost_bool
node_match (ice_streamer_magic_checker_t *c, FROST_Node *node)
{
    ice_streamer_magic_entry_t *entry = (ice_streamer_magic_entry_t *)node->data;
    frost_uint needed = c->offset + entry->offset + entry->len;
    frost_uint8 i8;
    frost_uint16 i16;
    frost_uint32 i32;
    frost_int tmp;
    char *ptr;

    /* do we have enough data ready for this check?
     * if not, read some more
     */
    if (c->read < needed) {
        tmp = read_data (c, needed);
        if (tmp == -1) {
            return frost_false;
        }

        c->read = tmp;
        if (c->read < needed) {
            /* couldn't read enough data */
            return frost_false;
        }
    }

    ptr = &c->buf[c->offset + entry->offset];

    switch (entry->type) {
        case ICE_MAGIC_ENTRY_TYPE_BYTE:
            memcpy (&i8, ptr, sizeof (i8));
            CMP (i8, entry, entry->value.i8); /* returns */
        case ICE_MAGIC_ENTRY_TYPE_INT16:
            memcpy (&i16, ptr, sizeof (i16));
            SWAP16 (i16, entry->endian);
            CMP (i16, entry, entry->value.i16); /* returns */
        case ICE_MAGIC_ENTRY_TYPE_INT32:
            memcpy (&i32, ptr, sizeof (i32));
            SWAP32 (i32, entry->endian);
            CMP (i32, entry, entry->value.i32); /* returns */
        case ICE_MAGIC_ENTRY_TYPE_STRING:
            return (!strncmp (ptr, entry->value.s, entry->len)) ? frost_true : frost_false ;
        case ICE_MAGIC_ENTRY_TYPE_STRINGC:
            return (!frost_ascii_strncasecmp (ptr, entry->value.s, entry->len)) ? frost_true : frost_false ;
        default:
            return frost_false;
    }
}

static frost_bool
tree_match (ice_streamer_magic_checker_t *c, FROST_Node *tree)
{
    FROST_Node *n;

    /* empty subtrees match anything */
    if (!tree->children) {
        return frost_true;
    }

    for (n = tree->children; n; n = n->next) {
        if (node_match (c, n) && tree_match (c, n)) {
            return frost_true;
        }
    }

    return frost_false;
}

static char *
ice_streamer_magic_match (ice_streamer_magic_checker_t *c, const char *uri)
{
    const FROST_List *l;
    char *u, *dump;
    int i;

    frost_return_val_if_fail (c, NULL);

    /* only one of the contained sets has to match */
    for (l = magic_list; l; l = frost_list_next (l)) {
        FROST_Node *tree = (FROST_Node *)l->data;

        if (tree_match (c, tree)) {
            frost_pointer *data = (frost_pointer *)tree->data;
            TRACE_INFO( ("magic plugin detected '%s' (%s)\n",
                      (char *)data[1], (char *)data[0]));
            return (char *) (data[1]);
        }
    }

    if (!uri)
        return NULL;

    u = frost_ascii_strdown (uri, -1);
    for (l = ext_list; l; l = frost_list_next (l)) {
        ice_streamer_magic_ext_data_t *e = (ice_streamer_magic_ext_data_t *)l->data;
        if (frost_pattern_match_simple (e->pattern, u)) {
            TRACE_INFO( ("magic plugin detected '%s' (by extension '%s')", e->type, e->pattern));
            frost_free (u);
            return e->type;
        }
    }
    frost_free (u);

    if (c->dumpcount > 0) {
        dump = (char*)frost_malloc ((MIN (c->read, c->dumpcount) * 3) + 1);
        u = dump;

        TRACE_INFO( ("Magic didn't match anything..."));
        for (i = 0; i < c->dumpcount && i < c->read; i++) {
            frost_sprintf (u, "%02X ", (unsigned char)c->buf[i]);
            u += 3;
        }
        TRACE_INFO( ("%s", dump));

        frost_free (dump);
    }

    return NULL;
}

static frost_uint
ice_streamer_magic_complexity (FROST_Node *tree)
{
    return frost_node_n_nodes (tree, ICE_TRAVERSE_ALL);
}

static frost_int
cb_sort_magic_list (FROST_Node *a, FROST_Node *b)
{
    frost_uint n1, n2;

    n1 = ice_streamer_magic_complexity (a);
    n2 = ice_streamer_magic_complexity (b);

    if (n1 > n2) {
        return -1;
    } else if (n1 < n2) {
        return 1;
    } else {
        return 0;
    }
}


frost_bool
ice_streamer_magic_extension_add (const char *mime, const char *ext)
{
    ice_streamer_magic_ext_data_t *e;

    frost_return_val_if_fail (mime, frost_false);
    frost_return_val_if_fail (ext, frost_false);

    e = frost_new0 (ice_streamer_magic_ext_data_t, 1);
    e->pattern = frost_strdup (ext);
    e->type = frost_strdup (mime);

    ext_list = frost_list_prepend (ext_list, e);

    return frost_true;
}

frost_bool
ice_streamer_magic_add (const char *desc, const char *mime, ...)
{
    FROST_Node *tree, *node = NULL;
    va_list ap;
    char *s;
    frost_pointer *root_props;
    frost_bool ret = frost_true;

    frost_return_val_if_fail (desc, frost_false);
    frost_return_val_if_fail (mime, frost_false);

    /* now process the magic specs in the argument list */
    va_start (ap, mime);

    s = va_arg (ap, char *);
    if (!s) { /* no magic specs passed -> failure */
        va_end (ap);
        return frost_false;
    }

    /* root node stores the description and the mimetype */
    root_props = frost_new0 (frost_pointer, 2);
    root_props[0] = frost_strdup (desc);
    root_props[1] = frost_strdup (mime);
    tree = frost_node_new (root_props);

    do {
        if (!*s) {
            ret = frost_false;
            TRACE_ERROR( ("invalid magic spec: '%s'", s));
            break;
        }

        s = frost_strdup (s); /* we need our own copy */
        node = ice_streamer_magic_add_node (tree, s, node);
        frost_free (s);

        if (!node) {
            TRACE_ERROR( ("invalid magic spec: '%s'", s));
            ret = frost_false;
            break;
        }
    } while ((s = va_arg (ap, char *)));

    va_end (ap);

    /* only add this tree to the list if all spec chunks are valid */
    if (ret) {
        magic_list =
            frost_list_insert_sorted (magic_list, tree,
                                  (FROSTCompareFunc) cb_sort_magic_list);
    } else {
        ice_streamer_magic_tree_free (tree);
    }

    return ret;
}

static frost_bool
ice_streamer_magic_plugin_init (ice_streamer_handler_t *xform)
{
    ice_streamer_magic_checker_t c;
    char *res;
    const char *url;
    ice_streamer_config_property_t *cv;

    c.xform = xform;
    c.read = c.offset = 0;
    c.alloc = 128; /* start with a 128 bytes buffer */
    c.buf = (char*)frost_malloc (c.alloc);

    cv = (ice_streamer_config_property_t *)ice_streamer_handler_config_lookup (xform, "dumpcount");
    c.dumpcount = ice_streamer_config_property_get_int (cv);

    url = (char *)ice_streamer_handler_indata_find_str (xform, ICE_STREAM_TYPE_URL);

    res = ice_streamer_magic_match (&c, url);
    if (res) {
        ice_streamer_handler_metadata_set_str (xform, ICE_MEDIALIB_ENTRY_PROPERTY_MIME, res);
        ice_streamer_handler_outdata_type_add (xform,
                                     ICE_STREAM_TYPE_MIMETYPE,
                                     res,
                                     ICE_STREAM_TYPE_END);
    }

    frost_free (c.buf);

    return (!!res) ? frost_true : frost_false ;
}

static frost_bool
ice_streamer_magic_plugin_setup (ice_streamer_handler_plugin_t *xform_plugin)
{
    ice_streamer_handler_methods_t methods;

    memset (&methods,0,sizeof(ice_streamer_handler_methods_t));
    methods.init = ice_streamer_magic_plugin_init;
    methods.read = ice_streamer_handler_read;
    methods.seek = ice_streamer_handler_seek;

    ice_streamer_handler_plugin_methods_set (xform_plugin, &methods);

    ice_streamer_handler_plugin_indata_add (xform_plugin,
                                  ICE_STREAM_TYPE_MIMETYPE,
                                  "application/octet-stream",
                                  ICE_STREAM_TYPE_END);

    ice_streamer_handler_plugin_config_property_register (xform_plugin, "dumpcount",
                                                "16", NULL, NULL);

    return frost_true;
}

ICE_STREAMER_XFORM_BUILTIN (magic,
                            "Magic file identifier",
                            ICE_STREAMER_VERSION,
                            "Magic file identifier",
                            ice_streamer_magic_plugin_setup);

const ice_streamer_plugin_desc_t * getMagicPluginDesc()
{
    return &ice_streamer_builtin_magic ;
}

