/*
 *
 *  C++ Portable Types Library (PTypes)
 *  Version 2.1.1  Released 27-Jun-2007
 *
 *  Copyright (C) 2001-2007 Hovik Melikyan
 *
 *  http://www.melikyan.com/ptypes/
 *
 */


#include <stdlib.h>
#include <stdio.h>
#include <limits.h>

#include "ptypes.h"


PTYPES_BEGIN


const variant nullvar;


struct _varitem
{
    string key;
    variant var;

    _varitem(const string& ikey, const variant& ivar): key(ikey), var(ivar) {}
};
typedef _varitem* pvaritem;


class ptpublic _varray: protected tobjlist<_varitem>
{
protected:
    int refcount;

    virtual int compare(const void* key, const void* item) const;

    friend class variant;

public:
    _varray();
    _varray(const _varray& a);
    virtual ~_varray();

    int get_count()                                     { return tobjlist<_varitem>::get_count(); }
    void clear()                                        { tobjlist<_varitem>::clear(); }
    void pack()                                         { tobjlist<_varitem>::pack(); }
    _varitem* doget(int index) const                    { return tobjlist<_varitem>::doget(index); }
    const variant& get(int index) const                 { if (unsigned(index) < unsigned(count)) return doget(index)->var; else return nullvar; }
    const string& getkey(int index) const               { if (unsigned(index) < unsigned(count)) return doget(index)->key; else return nullstring; }
    const variant& get(const char* key) const;
    int put(const string& key, const variant& var);
    void put(int index, const variant& var)             { if (unsigned(index) < unsigned(count)) doget(index)->var = var; }
    void ins(int index, const variant& var)             { if (unsigned(index) < unsigned(count)) doins(index, new _varitem(nullstring, var)); }
    int addvar(const variant& var);
    void del(int index)                                 { if (unsigned(index) < unsigned(count)) dodel(index); }
    void del(const string& key)                         { put(key, nullstring); }
};
typedef _varray* pvarray;


_varray::_varray()
    : tobjlist<_varitem>(true), refcount(0)
{
    config.sorted = true;
    config.casesens = true;
}


_varray::_varray(const _varray& a)
    : tobjlist<_varitem>(true), refcount(0)
{
    config.sorted = true;
    config.casesens = true;
    set_capacity(a.count);
    for (int i = 0; i < a.count; i++)
    {
        _varitem* v = a.doget(i);
        doins(i, new _varitem(v->key, v->var));
    }
}


_varray::~_varray()
{
}


int _varray::compare(const void* key, const void* item) const
{
   if (config.casesens)
        return strcmp(pconst(key), pvaritem(item)->key);
    else
        return strcasecmp(pconst(key), pvaritem(item)->key);
}


const variant& _varray::get(const char* key) const
{
    int index;
    if (search(key, index))
        return doget(index)->var;
    else
        return nullvar;
}


int _varray::put(const string& key, const variant& var)
{
    int index;
    if (search(pconst(key), index))
    {
        if (isnull(var))
            dodel(index);
        else
            doget(index)->var = var;
    }
    else if (!isnull(var))
        doins(index, new _varitem(key, var));
    return index;
}


int _varray::addvar(const variant& v)
{
    int i;
    if (count > 0 && isempty(doget(count - 1)->key))
        i = count;
    else
        i = 0;
    doins(i, new _varitem(nullstring, v));
    return i;
}


static void vconverr(large v);


static void vfatal()
{
    fatal(CRIT_FIRST + 60, "Variant data corrupt");
}


evariant::~evariant()
{
}


void variant::initialize(_varray* a)
{
    tag = VAR_ARRAY;
#ifdef PTYPES_ST
    a->refcount++;
#else
    pincrement(&a->refcount);
#endif
    value.a = a;
}


void variant::initialize(component* o)
{
    tag = VAR_OBJECT;
    value.o = addref(o);
}


void variant::initialize(const variant& v)
{
    switch (v.tag)
    {
    case VAR_NULL:
        tag = VAR_NULL;
        break;
    case VAR_INT:
    case VAR_BOOL:
    case VAR_FLOAT:
        tag = v.tag;
        value = v.value;
        break;
    case VAR_STRING:
        initialize(PTR_TO_STRING(v.value.s));
        break;
    case VAR_ARRAY:
        initialize(v.value.a);
        break;
    case VAR_OBJECT:
        initialize(v.value.o);
        break;
    default:
        vfatal();
    }
}


void variant::finalize()
{
    if (tag >= VAR_COMPOUND)
    {
        switch (tag)
        {
        case VAR_STRING:
            PTYPES_NAMESPACE::finalize(PTR_TO_STRING(value.s));
            break;
        case VAR_ARRAY:
#ifdef PTYPES_ST
            if (--value.a->refcount == 0)
#else
            if (pdecrement(&value.a->refcount) == 0)
#endif
                delete value.a;
            break;
        case VAR_OBJECT:
            release(value.o);
            break;
        default:
            vfatal();
        }
    }
    tag = VAR_NULL;
}


void variant::assign(large v)           { finalize(); initialize(v); }
void variant::assign(bool v)            { finalize(); initialize(v); }
void variant::assign(double v)          { finalize(); initialize(v); }
void variant::assign(const char* v)     { finalize(); initialize(v); }


void variant::assign(const string& v)   
{ 
    if (tag == VAR_STRING)
        PTR_TO_STRING(value.s) = v;
    else
    {
        finalize();
        initialize(v);
    }
}


void variant::assign(_varray* a)
{
    if (tag == VAR_ARRAY && value.a == a)
        return;
    finalize();
    initialize(a);
}


void variant::assign(component* o)
{
    if (tag == VAR_OBJECT)
    {
        if (value.o == o)
            return;
        else
            release(value.o);
    }
    else
        finalize();
    initialize(o);
}


void variant::assign(const variant& v)
{
    switch (v.tag)
    {
    case VAR_NULL:
        finalize();
        tag = VAR_NULL;
        break;
    case VAR_INT:
    case VAR_BOOL:
    case VAR_FLOAT:
        finalize();
        tag = v.tag;
        value = v.value;
        break;
    case VAR_STRING:
        assign(PTR_TO_STRING(v.value.s));
        break;
    case VAR_ARRAY:
        assign(v.value.a);
        break;
    case VAR_OBJECT:
        assign(v.value.o);
        break;
    default:
        vfatal();
    }
}


void ptdecl clear(variant& v)
{
    v.finalize();
    v.initialize();
}


variant::operator int() const
{
    large t = operator large();
    if (t < INT_MIN || t > INT_MAX)
        vconverr(t);
    return int(t);
}


variant::operator unsigned int() const
{
    large t = operator large();
    if (t < 0 || t > UINT_MAX)
        vconverr(t);
    return uint(t);
}


variant::operator long() const
{
    large t = operator large();
    if (t < LONG_MIN || t > LONG_MAX)
        vconverr(t);
    return int(t);
}


variant::operator unsigned long() const
{
    large t = operator large();
    if (t < 0 || t > large(ULONG_MAX))
        vconverr(t);
    return uint(t);
}


variant::operator large() const
{
    switch(tag)
    {
    case VAR_NULL: return 0;
    case VAR_INT: return value.i;
    case VAR_BOOL: return int(value.b);
    case VAR_FLOAT: return int(value.f);
    case VAR_STRING: 
        {
            const char* p = PTR_TO_STRING(value.s);
            bool neg = *p == '-';
            if (neg)
                p++;
            large t = stringtoi(p);
            if (t < 0)
                return 0;
            else
                return neg ? -t : t;
        }
    case VAR_ARRAY: return value.a->count != 0;
    case VAR_OBJECT: return 0;
    default: vfatal();
    }
    return 0;
}


variant::operator bool() const
{
    switch(tag)
    {
    case VAR_NULL: return false;
    case VAR_INT: return value.i != 0;
    case VAR_BOOL: return value.b;
    case VAR_FLOAT: return value.f != 0;
    case VAR_STRING: return !isempty((PTR_TO_STRING(value.s)));
    case VAR_ARRAY: return value.a->count != 0;
    case VAR_OBJECT: return value.o != nil;
    default: vfatal();
    }
    return false;
}


variant::operator double() const
{
    switch(tag)
    {
    case VAR_NULL: return 0;
    case VAR_INT: return double(value.i);
    case VAR_BOOL: return int(value.b);
    case VAR_FLOAT: return value.f;
    case VAR_STRING: 
        {
            char* e;
            double t = strtod(PTR_TO_STRING(value.s), &e);
            if (*e != 0)
                return 0;
            else
                return t;
        }
    case VAR_ARRAY: return int(value.a->count != 0);
    case VAR_OBJECT: return 0;
    default: vfatal();
    }
    return 0;
}


void string::initialize(const variant& v)
{
    switch(v.tag)
    {
    case VAR_NULL: initialize(); break;
    case VAR_INT: initialize(itostring(v.value.i)); break;
    case VAR_BOOL: if (v.value.b) initialize('1'); else initialize('0'); break;
    case VAR_FLOAT:
        {
            char buf[256];
            sprintf(buf, "%g", v.value.f);
            initialize(buf);
        }
        break;
    case VAR_STRING: initialize(PTR_TO_STRING(v.value.s)); break;
    case VAR_ARRAY: initialize(); break;
    case VAR_OBJECT: initialize(); break;
    default: vfatal();
    }
}


variant::operator string() const
{
    // this is a 'dirty' solution to gcc 3.3 typecast problem. most compilers
    // handle variant::operator string() pretty well, while gcc 3.3 requires
    // to explicitly declare a constructor string::string(const variant&).
    // ironically, the presence of both the typecast operator and the constructor
    // confuses the MSVC compiler. so the only thing we can do to please all 
    // those compilers [that "move towards the c++ standard"] is to conditionally
    // exclude the constructor string(const variant&). and this is not the whole
    // story. i see you are bored with it and i let you go. nobody would ever care
    // about this. it just works, though i'm not happy with what i wrote here:
    string t;
    t.initialize(*this);
    return t;
}


variant::operator component*() const
{
    if (tag == VAR_OBJECT)
        return value.o;
    else
        return nil;
}


bool variant::equal(const variant& v) const
{
    if (tag != v.tag)
        return false;
    switch (tag)
    {
    case VAR_NULL: return true;
    case VAR_INT: return value.i == v.value.i;
    case VAR_BOOL: return value.b == v.value.b;
    case VAR_FLOAT: return value.f == v.value.f;
    case VAR_STRING: return strcmp(value.s, v.value.s) == 0;
    case VAR_ARRAY: return value.a == v.value.a;
    case VAR_OBJECT: return value.o == v.value.o;
    default: vfatal(); return false;
    }
}


static string numkey(large key)
{
    return itostring(key, 16, 16, '0');
}


void ptdecl aclear(variant& v)
{
    if (v.tag == VAR_ARRAY)
        v.value.a->clear();
    else
    {
        v.finalize();
        v.initialize(new _varray());
    }
}


void ptdecl apack(variant& v)
{
    if (v.tag == VAR_ARRAY)
        v.value.a->pack();
}


variant ptdecl aclone(const variant& v)
{
    if (v.tag == VAR_ARRAY)
        return variant(new _varray(*(v.value.a)));
    else
        return variant(new _varray());
}


int ptdecl alength(const variant& v)
{
    if (v.tag == VAR_ARRAY)
        return v.value.a->get_count();
    else
        return 0;
}


const variant& ptdecl get(const variant& v, const string& key)
{
    if (v.tag == VAR_ARRAY)
        return v.value.a->get(key);
    else
        return nullvar;
}


const variant& ptdecl get(const variant& v, large key)
{
    return get(v, numkey(key));
}


void ptdecl put(variant& v, const string& key, const variant& item)
{
    if (v.tag != VAR_ARRAY)
        aclear(v);
    v.value.a->put(key, item);
}


void ptdecl put(variant& v, large key, const variant& item)
{
    put(v, numkey(key), item);
}


void ptdecl del(variant& v, const string& key)
{
    if (v.tag == VAR_ARRAY)
        v.value.a->del(key);
}


void ptdecl del(variant& v, large key)
{
    del(v, numkey(key));
}


bool ptdecl anext(const variant& array, int& index, variant& item)
{
    string key;
    return anext(array, index, item, key);
}


bool ptdecl anext(const variant& array, int& index, variant& item, string& key)
{
    if (array.tag != VAR_ARRAY)
    {
        clear(item);
        return false;
    }
    if (index < 0 || index >= array.value.a->get_count())
    {
        clear(item);
        return false;
    }
    item = array.value.a->doget(index)->var;
    key = array.value.a->doget(index)->key;
    index++;
    return true;
}


int ptdecl aadd(variant& array, const variant& item)
{
    if (array.tag != VAR_ARRAY)
        aclear(array);
    return array.value.a->addvar(item);
}


const variant& ptdecl aget(const variant& array, int index)
{
    if (array.tag == VAR_ARRAY)
        return array.value.a->get(index);
    else
        return nullvar;
}


void ptdecl adel(variant& array, int index)
{
    if (array.tag == VAR_ARRAY)
        array.value.a->del(index);
}


void ptdecl aput(variant& array, int index, const variant& item)
{
    if (array.tag == VAR_ARRAY)
        array.value.a->put(index, item);
}


void ptdecl ains(variant& array, int index, const variant& item)
{
    if (array.tag == VAR_ARRAY)
        array.value.a->ins(index, item);
}


#ifdef _MSC_VER
// disable "unreachable code" warning for throw (known compiler bug)
#  pragma warning (disable: 4702)
#endif

static void vconverr(large v)
{
    throw new evariant("Value out of range: " + itostring(v));
}


PTYPES_END
