/*
 * Copyright 2011 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkJSON.h"
#include "SkString.h"

#ifdef SK_DEBUG
//    #define TRACE_SKJSON_LEAKS
#endif

#ifdef TRACE_SKJSON_LEAKS
    static int gStringCount;
    static int gSlotCount;
    static int gObjectCount;
    static int gArrayCount;
    #define LEAK_CODE(code) code
#else
    #define LEAK_CODE(code)
#endif

///////////////////////////////////////////////////////////////////////////////

static char* alloc_string(size_t len) {
    LEAK_CODE(SkDebugf(" string[%d]\n", gStringCount++);)
    char* str = (char*)sk_malloc_throw(len + 1);
    str[len] = 0;
    return str;
}

static char* dup_string(const char src[]) {
    if (NULL == src) {
        return NULL;
    }
    size_t len = strlen(src);
    char* dst = alloc_string(len);
    memcpy(dst, src, len);
    return dst;
}

static void free_string(char* str) {
    if (str) {
        sk_free(str);
        LEAK_CODE(SkASSERT(gStringCount > 0); SkDebugf("~string[%d]\n", --gStringCount);)
    }
}

///////////////////////////////////////////////////////////////////////////////

struct SkJSON::Object::Slot {
    Slot(const char name[], Type type) {
        LEAK_CODE(SkDebugf(" slot[%d]\n", gSlotCount++);)
        SkASSERT(name);

        fNext = NULL;
        
        size_t len = strlen(name);
        // extra 1 for str[0] which stores the type
        char* str = alloc_string(1 + len);
        str[0] = (char)type;
        // str[1] skips the type, len+1 includes the terminating 0 byte.
        memcpy(&str[1], name, len + 1);
        fName = str;
        
        // fValue is uninitialized
    }
    ~Slot();
    
    Type type() const { return (Type)fName[0]; }
    const char* name() const { return &fName[1]; }
    
    Slot*   fNext;
    char*   fName;    // fName[0] is the type, &fName[1] is the "name"
    union {
        Object* fObject;
        Array*  fArray;
        char*   fString;
        int32_t fInt;
        float   fFloat;
        bool    fBool;
    } fValue;
};

SkJSON::Object::Slot::~Slot() {
    free_string(fName);
    switch (this->type()) {
        case kObject:
            delete fValue.fObject;
            break;
        case kArray:
            delete fValue.fArray;
            break;
        case kString:
            free_string(fValue.fString);
            break;
        default:
            break;
    }
    LEAK_CODE(SkASSERT(gSlotCount > 0); SkDebugf("~slot[%d]\n", --gSlotCount);)
}

///////////////////////////////////////////////////////////////////////////////

SkJSON::Object::Iter::Iter(const Object& obj) : fSlot(obj.fHead) {}

bool SkJSON::Object::Iter::done() const {
    return NULL == fSlot;
}

void SkJSON::Object::Iter::next() {
    SkASSERT(fSlot);
    fSlot = fSlot->fNext;
}

SkJSON::Type SkJSON::Object::Iter::type() const {
    SkASSERT(fSlot);
    return fSlot->type();
}

const char* SkJSON::Object::Iter::name() const {
    SkASSERT(fSlot);
    return fSlot->name();
}

SkJSON::Object* SkJSON::Object::Iter::objectValue() const {
    SkASSERT(fSlot);
    SkASSERT(kObject == fSlot->type());
    return fSlot->fValue.fObject;
}

SkJSON::Array* SkJSON::Object::Iter::arrayValue() const {
    SkASSERT(fSlot);
    SkASSERT(kArray == fSlot->type());
    return fSlot->fValue.fArray;
}

const char* SkJSON::Object::Iter::stringValue() const {
    SkASSERT(fSlot);
    SkASSERT(kString == fSlot->type());
    return fSlot->fValue.fString;
}

int32_t SkJSON::Object::Iter::intValue() const {
    SkASSERT(fSlot);
    SkASSERT(kInt == fSlot->type());
    return fSlot->fValue.fInt;
}

float SkJSON::Object::Iter::floatValue() const {
    SkASSERT(fSlot);
    SkASSERT(kFloat == fSlot->type());
    return fSlot->fValue.fFloat;
}

bool SkJSON::Object::Iter::boolValue() const {
    SkASSERT(fSlot);
    SkASSERT(kBool == fSlot->type());
    return fSlot->fValue.fBool;
}

///////////////////////////////////////////////////////////////////////////////

SkJSON::Object::Object() : fHead(NULL), fTail(NULL) {
    LEAK_CODE(SkDebugf(" object[%d]\n", gObjectCount++);)
}

SkJSON::Object::Object(const Object& other) : fHead(NULL), fTail(NULL) {
    LEAK_CODE(SkDebugf(" object[%d]\n", gObjectCount++);)

    Iter iter(other);
    while (!iter.done()) {
        switch (iter.type()) {
            case kObject:
                this->addObject(iter.name(), new Object(*iter.objectValue()));
                break;
            case kArray:
                this->addArray(iter.name(), new Array(*iter.arrayValue()));
                break;
            case kString:
                this->addString(iter.name(), dup_string(iter.stringValue()));
                break;
            case kInt:
                this->addInt(iter.name(), iter.intValue());
                break;
            case kFloat:
                this->addFloat(iter.name(), iter.floatValue());
                break;
            case kBool:
                this->addBool(iter.name(), iter.boolValue());
                break;
        }
        iter.next();
    }
}

SkJSON::Object::~Object() {
    Slot* slot = fHead;
    while (slot) {
        Slot* next = slot->fNext;
        delete slot;
        slot = next;
    }
    LEAK_CODE(SkASSERT(gObjectCount > 0); SkDebugf("~object[%d]\n", --gObjectCount);)
}

int SkJSON::Object::count() const {
    int n = 0;
    for (const Slot* slot = fHead; slot; slot = slot->fNext) {
        n += 1;
    }
    return n;
}

SkJSON::Object::Slot* SkJSON::Object::addSlot(Slot* slot) {
    SkASSERT(NULL == slot->fNext);
    if (NULL == fHead) {
        SkASSERT(NULL == fTail);
        fHead = fTail = slot;
    } else {
        SkASSERT(fTail);
        SkASSERT(NULL == fTail->fNext);
        fTail->fNext = slot;
        fTail = slot;
    }
    return slot;
}

void SkJSON::Object::addObject(const char name[], SkJSON::Object* value) {
    this->addSlot(new Slot(name, kObject))->fValue.fObject = value;
}

void SkJSON::Object::addArray(const char name[], SkJSON::Array* value) {
    this->addSlot(new Slot(name, kArray))->fValue.fArray = value;
}

void SkJSON::Object::addString(const char name[], const char value[]) {
    this->addSlot(new Slot(name, kString))->fValue.fString = dup_string(value);
}

void SkJSON::Object::addInt(const char name[], int32_t value) {
    this->addSlot(new Slot(name, kInt))->fValue.fInt = value;
}

void SkJSON::Object::addFloat(const char name[], float value) {
    this->addSlot(new Slot(name, kFloat))->fValue.fFloat = value;
}

void SkJSON::Object::addBool(const char name[], bool value) {
    this->addSlot(new Slot(name, kBool))->fValue.fBool = value;
}

///////////////////////////////////////////////////////////////////////////////

const SkJSON::Object::Slot* SkJSON::Object::findSlot(const char name[],
                                                     Type t) const {
    for (const Slot* slot = fHead; slot; slot = slot->fNext) {
        if (t == slot->type() && !strcmp(slot->name(), name)) {
            return slot;
        }
    }
    return NULL;
}

bool SkJSON::Object::find(const char name[], Type t) const {
    return this->findSlot(name, t) != NULL;
}

bool SkJSON::Object::findObject(const char name[], SkJSON::Object** value) const {
    const Slot* slot = this->findSlot(name, kObject);
    if (slot) {
        if (value) {
            *value = slot->fValue.fObject;
        }
        return true;
    }
    return false;
}

bool SkJSON::Object::findArray(const char name[], SkJSON::Array** value) const {
    const Slot* slot = this->findSlot(name, kArray);
    if (slot) {
        if (value) {
            *value = slot->fValue.fArray;
        }
        return true;
    }
    return false;
}

bool SkJSON::Object::findString(const char name[], SkString* value) const {
    const Slot* slot = this->findSlot(name, kString);
    if (slot) {
        if (value) {
            value->set(slot->fValue.fString);
        }
        return true;
    }
    return false;
}

bool SkJSON::Object::findInt(const char name[], int32_t* value) const {
    const Slot* slot = this->findSlot(name, kInt);
    if (slot) {
        if (value) {
            *value = slot->fValue.fInt;
        }
        return true;
    }
    return false;
}

bool SkJSON::Object::findFloat(const char name[], float* value) const {
    const Slot* slot = this->findSlot(name, kFloat);
    if (slot) {
        if (value) {
            *value = slot->fValue.fFloat;
        }
        return true;
    }
    return false;
}

bool SkJSON::Object::findBool(const char name[], bool* value) const {
    const Slot* slot = this->findSlot(name, kBool);
    if (slot) {
        if (value) {
            *value = slot->fValue.fBool;
        }
        return true;
    }
    return false;
}

bool SkJSON::Object::remove(const char name[], Type t) {
    SkDEBUGCODE(int count = this->count();)
    Slot* prev = NULL;
    Slot* slot = fHead;
    while (slot) {
        Slot* next = slot->fNext;
        if (t == slot->type() && !strcmp(slot->name(), name)) {
            if (prev) {
                SkASSERT(fHead != slot);
                prev->fNext = next;
            } else {
                SkASSERT(fHead == slot);
                fHead = next;
            }
            if (fTail == slot) {
                fTail = prev;
            }
            delete slot;
            SkASSERT(count - 1 == this->count());
            return true;
        }
        prev = slot;
        slot = next;
    }
    SkASSERT(count == this->count());
    return false;
}

///////////////////////////////////////////////////////////////////////////////

static void tabForLevel(int level) {
    for (int i = 0; i < level; ++i) {
        SkDebugf("    ");
    }
}

void SkJSON::Object::toDebugf() const {
    SkDebugf("{\n");
    this->dumpLevel(0);
    SkDebugf("}\n");
}

void SkJSON::Object::dumpLevel(int level) const {
    for (Slot* slot = fHead; slot; slot = slot->fNext) {
        Type t = slot->type();        
        tabForLevel(level + 1);
        SkDebugf("\"%s\" : ", slot->name());
        switch (slot->type()) {
            case kObject:
                if (slot->fValue.fObject) {
                    SkDebugf("{\n");
                    slot->fValue.fObject->dumpLevel(level + 1);
                    tabForLevel(level + 1);
                    SkDebugf("}");
                } else {
                    SkDebugf("null");
                }
                break;
            case kArray:
                if (slot->fValue.fArray) {
                    SkDebugf("[");
                    slot->fValue.fArray->dumpLevel(level + 1);
                    SkDebugf("]");
                } else {
                    SkDebugf("null");
                }
                break;
            case kString:
                SkDebugf("\"%s\"", slot->fValue.fString);
                break;
            case kInt:
                SkDebugf("%d", slot->fValue.fInt);
                break;
            case kFloat:
                SkDebugf("%g", slot->fValue.fFloat);
                break;
            case kBool:
                SkDebugf("%s", slot->fValue.fBool ? "true" : "false");
                break;
            default:
                SkASSERT(!"how did I get here");
                break;
        }
        if (slot->fNext) {
            SkDebugf(",");
        }
        SkDebugf("\n");
    }
}

void SkJSON::Array::dumpLevel(int level) const {
    if (0 == fCount) {
        return;
    }
    int last = fCount - 1;

    switch (this->type()) {
        case kObject: {
            SkDebugf("\n");
            for (int i = 0; i <= last; ++i) {
                Object* obj = fArray.fObjects[i];
                tabForLevel(level + 1);
                if (obj) {
                    SkDebugf("{\n");
                    obj->dumpLevel(level + 1);
                    tabForLevel(level + 1);
                    SkDebugf(i < last ? "}," : "}");
                } else {
                    SkDebugf(i < last ? "null," : "null");
                }
                SkDebugf("\n");
            }
        } break;
        case kArray: {
            SkDebugf("\n");
            for (int i = 0; i <= last; ++i) {
                Array* array = fArray.fArrays[i];
                tabForLevel(level + 1);
                if (array) {
                    SkDebugf("[");
                    array->dumpLevel(level + 1);
                    tabForLevel(level + 1);
                    SkDebugf(i < last ? "]," : "]");
                } else {
                    SkDebugf(i < last ? "null," : "null");
                }
                SkDebugf("\n");
            }
        } break;
        case kString: {
            for (int i = 0; i < last; ++i) {
                const char* str = fArray.fStrings[i];
                SkDebugf(str ? " \"%s\"," : " null,", str);
            }
            const char* str = fArray.fStrings[last];
            SkDebugf(str ? " \"%s\" " : " null ", str);
        } break;
        case kInt: {
            for (int i = 0; i < last; ++i) {
                SkDebugf(" %d,", fArray.fInts[i]);
            }
            SkDebugf(" %d ", fArray.fInts[last]);
        } break;
        case kFloat: {
            for (int i = 0; i < last; ++i) {
                SkDebugf(" %g,", fArray.fFloats[i]);
            }
            SkDebugf(" %g ", fArray.fFloats[last]);
        } break;
        case kBool: {
            for (int i = 0; i < last; ++i) {
                SkDebugf(" %s,", fArray.fBools[i] ? "true" : "false");
            }
            SkDebugf(" %s ", fArray.fInts[last] ? "true" : "false");
        } break;
        default:
            SkASSERT(!"unsupported array type");
            break;
    }
}

///////////////////////////////////////////////////////////////////////////////

static const uint8_t gBytesPerType[] = {
    sizeof(SkJSON::Object*),
    sizeof(SkJSON::Array*),
    sizeof(char*),
    sizeof(int32_t),
    sizeof(float),
    sizeof(bool)
};

typedef void* (*DupProc)(const void*);
             
static void* dup_object(const void* src) {
    return SkNEW_ARGS(SkJSON::Object, (*(SkJSON::Object*)src));
}
                      
static void* dup_array(const void* src) {
    return SkNEW_ARGS(SkJSON::Array, (*(SkJSON::Array*)src));
}

static const DupProc gDupProcs[] = {
    dup_object,             // Object
    dup_array,              // Array
    (DupProc)dup_string,    // String
    NULL,                   // int
    NULL,                   // float
    NULL,                   // bool
};

void SkJSON::Array::init(Type type, int count, const void* src) {
    LEAK_CODE(SkDebugf(" array[%d]\n", gArrayCount++);)

    SkASSERT((unsigned)type < SK_ARRAY_COUNT(gBytesPerType));

    if (count < 0) {
        count = 0;
    }
    size_t size = count * gBytesPerType[type];

    fCount = count;
    fType = type;
    fArray.fVoids = sk_malloc_throw(size);
    if (src) {
        DupProc proc = gDupProcs[fType];
        if (!proc) {
            memcpy(fArray.fVoids, src, size);
        } else {
            void** srcPtr = (void**)src;
            void** dstPtr = (void**)fArray.fVoids;
            for (int i = 0; i < fCount; ++i) {
                dstPtr[i] = proc(srcPtr[i]);
            }
        }
    } else {
        sk_bzero(fArray.fVoids, size);
    }
}

SkJSON::Array::Array(Type type, int count) {
    this->init(type, count, NULL);
}

SkJSON::Array::Array(const int32_t values[], int count) {
    this->init(kInt, count, values);
}
    
SkJSON::Array::Array(const float values[], int count) {
    this->init(kFloat, count, values);
}

SkJSON::Array::Array(const bool values[], int count) {
    this->init(kBool, count, values);
}

SkJSON::Array::Array(const Array& other) {
    this->init(other.type(), other.count(), other.fArray.fVoids);
}

typedef void (*FreeProc)(void*);

static void free_object(void* obj) {
    delete (SkJSON::Object*)obj;
}

static void free_array(void* array) {
    delete (SkJSON::Array*)array;
}

static const FreeProc gFreeProcs[] = {
    free_object,            // Object
    free_array,             // Array
    (FreeProc)free_string,  // String
    NULL,                   // int
    NULL,                   // float
    NULL,                   // bool
};

SkJSON::Array::~Array() {
    FreeProc proc = gFreeProcs[fType];
    if (proc) {
        void** ptr = (void**)fArray.fVoids;
        for (int i = 0; i < fCount; ++i) {
            proc(ptr[i]);
        }
    }
    sk_free(fArray.fVoids);

    LEAK_CODE(SkASSERT(gArrayCount > 0); SkDebugf("~array[%d]\n", --gArrayCount);)
}

void SkJSON::Array::setObject(int index, Object* object) {
    SkASSERT((unsigned)index < (unsigned)fCount);
    Object*& prev = fArray.fObjects[index];
    if (prev != object) {
        delete prev;
        prev = object;
    }
}

void SkJSON::Array::setArray(int index, Array* array) {
    SkASSERT((unsigned)index < (unsigned)fCount);
    Array*& prev = fArray.fArrays[index];
    if (prev != array) {
        delete prev;
        prev = array;
    }
}

void SkJSON::Array::setString(int index, const char str[]) {
    SkASSERT((unsigned)index < (unsigned)fCount);
    char*& prev = fArray.fStrings[index];
    if (prev != str) {
        free_string(prev);
        prev = dup_string(str);
    }
}