/*
 * ************************************************************************************************************
 * Author: Marcin Załęczny
 * Copyright (C)2010 Marcin Załęczny <mzaleczny@hotmail.com>
 * License: The GNU Lesser General Public License, version 3.0 (LGPLv3)
 * Library Name: mzstd
 * Version: 1.07
 * First release date:   2010-11-08
 * Current release date: 2011-03-04
 *
 *
 * This file is part of mzstd library
 * 
 * mzstd 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 3 of the License, or
 * (at your option) any later version.
 * 
 * mzstd 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.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * ************************************************************************************************************
 */

#include "mzStdObject.h"
#include "mzJsonParser.h"
#include "mzFileUtils.h"
#include "mzMessageBox.h"

VOID CStdObjectProperty::_commonInit() {
    this->setRealClassName(L"CStdObjectProperty");
}

CStdObjectProperty::CStdObjectProperty(CString name, CString value) {
    this->_commonInit();
    this->type = STDOBJ_PROPERTY_VALUE;
    this->name = name;
    this->value = value;
    this->aValue = NULL;
    this->oValue = NULL;
	this->extraData = NULL;
}

CStdObjectProperty::CStdObjectProperty(CString name, LONG value) {
    this->_commonInit();
    this->type = STDOBJ_PROPERTY_VALUE_INT;
    this->name = name;
    this->value = "";
	this->iValue = value;
    this->aValue = NULL;
    this->oValue = NULL;
	this->extraData = NULL;
}

CStdObjectProperty::CStdObjectProperty(CString name, double value) {
    this->_commonInit();
    this->type = STDOBJ_PROPERTY_VALUE_DOUBLE;
    this->name = name;
    this->value = "";
	this->fValue = value;
    this->aValue = NULL;
    this->oValue = NULL;
	this->extraData = NULL;
}

CStdObjectProperty::CStdObjectProperty(CString name, CArrayList *value_array) {
    this->_commonInit();
    this->type = STDOBJ_PROPERTY_ARRAY;
    this->name = name;
    this->value = "";
    this->aValue = value_array;
    this->oValue = NULL;
	this->extraData = NULL;
}

CStdObjectProperty::CStdObjectProperty(CString name, CStdObject *value_object) {
    this->_commonInit();
    this->type = STDOBJ_PROPERTY_OBJECT;
    this->name = name;
    this->value = "";
    this->aValue = NULL;
    this->oValue = value_object;
	this->extraData = NULL;
}

CStdObjectProperty::CStdObjectProperty(CStdObjectProperty& copyObj) {
    this->_commonInit();
    
    this->type = copyObj.type;
    this->name = copyObj.name;
    this->value = copyObj.value;
    if (copyObj.aValue) {
        ULONG size = copyObj.aValue->size();
        this->aValue = new CArrayList(size);
        LONG* copyItems = copyObj.aValue->getItemsBuf();
        CStdObjectProperty* copyItem;
        for (ULONG i = 0; i < size; i++) {
            copyItem = (CStdObjectProperty*)copyItems[i];
            this->aValue->insert(i, (LONG)copyItem->clone());
        }
    } else {
        this->aValue = NULL;
    }
    if (copyObj.oValue) {
        this->oValue = copyObj.oValue->clone();
    } else {
        this->oValue = NULL;
    }
    if (copyObj.extraData) {
        this->extraData = copyObj.extraData->clone();
    } else {
        this->extraData = NULL;
    }
}

CStdObjectProperty::~CStdObjectProperty() {
    this->clearValue();
}

VOID CStdObjectProperty::clearValue() {
    CStdObjectProperty *p;
    if (this->aValue) {
        for (ULONG i = 0; i < this->aValue->size(); i++) {
            p = (CStdObjectProperty*)this->aValue->getAt(i);
            delete p;
        }
        this->aValue->clear();
        delete this->aValue;
        this->aValue = NULL;
    }
    if (this->oValue) {
        delete this->oValue;
        this->oValue = NULL;
    }
	if (this->extraData) {
        delete this->extraData;
        this->extraData = NULL;
	}
    this->value = "";
    this->type = STDOBJ_PROPERTY_NONE;
}

CStdObjectProperty* CStdObjectProperty::clone() {
    CStdObjectProperty* newValue = new CStdObjectProperty(*this);
    return newValue;
}

CString CStdObjectProperty::getAsString() {
    switch (this->type) {
        case STDOBJ_PROPERTY_VALUE:
            return this->value;
        case STDOBJ_PROPERTY_VALUE_INT:
            return CString(this->iValue);
        case STDOBJ_PROPERTY_VALUE_DOUBLE:
            return CString(this->fValue, 2L);
    }
    return COMMON_EMPTY_STRING;
}

BOOL CStdObjectProperty::hasOnlyScalars() {
    if (this->type == STDOBJ_PROPERTY_VALUE || this->type == STDOBJ_PROPERTY_VALUE_INT || this->type == STDOBJ_PROPERTY_VALUE_DOUBLE) {
        return TRUE;
    }

    if (this->type == STDOBJ_PROPERTY_ARRAY) {
        return this->_arrayHasOnlyScalars();
    }

    if (this->type == STDOBJ_PROPERTY_OBJECT) {
        return this->_objectHasOnlyScalars();
    }

    return FALSE;
}

BOOL CStdObjectProperty::_arrayHasOnlyScalars() {
    BOOL onlyScalars = TRUE;
    if (!this->aValue || this->aValue->size() < 1) {
        return FALSE;
    }

    CStdObjectProperty *item;
    for (ULONG i = 0; i < this->aValue->size(); i++) {
        item = (CStdObjectProperty*)this->aValue->getAt(i);
        if (item->type == STDOBJ_PROPERTY_ARRAY || item->type == STDOBJ_PROPERTY_OBJECT) {
            onlyScalars = FALSE;
            break;
        }
    }

    return onlyScalars;
}

BOOL CStdObjectProperty::_objectHasOnlyScalars() {
    return FALSE;
}








VOID CStdObject::_commonInit() {
    this->setRealClassName(L"CStdObject");
}

CStdObject::CStdObject() {
    this->_commonInit();
    this->_properties = new CArrayList();
}

CStdObject::CStdObject(CStdObject& copyObj) {
    this->_commonInit();
    
    ULONG size = copyObj.itemsCount();
    this->_properties = new CArrayList(size);
    LONG* copyItems = copyObj._properties->getItemsBuf();
    CStdObjectProperty* copyItem;
    for (ULONG i = 0; i < size; i++) {
        copyItem = (CStdObjectProperty*)copyItems[i];
        this->_properties->insert(i, (LONG)copyItem->clone());
    }
}

CStdObject::~CStdObject() {
    CStdObjectProperty *p;
    if (this->_properties) {
        for (ULONG i = 0; i < this->_properties->size(); i++) {
            p = (CStdObjectProperty*)this->_properties->getAt(i);
            delete p;
        }
        this->_properties->clear();
        delete this->_properties;
    }
}

CStdObject* CStdObject::clone() {
    CStdObject* newValue = new CStdObject(*this);
    return newValue;
}

VOID CStdObject::set(WCHAR* name, CString value) {
    CStdObjectProperty *p;

    p = this->get(name);
    if (p) {
        p->clearValue();
        p->type = STDOBJ_PROPERTY_VALUE;
        p->value = value;
    } else {
        p = new CStdObjectProperty(name, value);
        this->_properties->append((LONG)p);
    }
}

VOID CStdObject::set(WCHAR* name, LONG value) {
    CStdObjectProperty *p;

    p = this->get(name);
    if (p) {
        p->clearValue();
        p->type = STDOBJ_PROPERTY_VALUE_INT;
        p->iValue = value;
    } else {
        p = new CStdObjectProperty(name, value);
        this->_properties->append((LONG)p);
    }
}

VOID CStdObject::set(WCHAR* name, double value) {
    CStdObjectProperty *p;

    p = this->get(name);
    if (p) {
        p->clearValue();
        p->type = STDOBJ_PROPERTY_VALUE_DOUBLE;
        p->fValue = value;
    } else {
        p = new CStdObjectProperty(name, value);
        this->_properties->append((LONG)p);
    }
}

VOID CStdObject::set(WCHAR* name, CArrayList *aValue) {
    CStdObjectProperty *p;

    p = this->get(name);
    if (p) {
        p->clearValue();
        p->type = STDOBJ_PROPERTY_ARRAY;
        p->aValue = aValue;
    } else {
        p = new CStdObjectProperty(name, aValue);
        this->_properties->append((LONG)p);
    }
}

VOID CStdObject::set(WCHAR* name, CStdObject *oValue) {
    CStdObjectProperty *p;

    p = this->get(name);
    if (p) {
        p->clearValue();
        p->type = STDOBJ_PROPERTY_OBJECT;
        p->oValue = oValue;
    } else {
        p = new CStdObjectProperty(name, oValue);
        this->_properties->append((LONG)p);
    }
}

CStdObjectProperty* CStdObject::get(WCHAR *name) {
    CStdObjectProperty *p;

    if (!this->_properties) {
        return NULL;
    }
    
    for (ULONG i = 0; i < this->_properties->size(); i++) {
        p = (CStdObjectProperty*)this->_properties->getAt(i);
        if (p->name == name) {
            return p;
        }
    }

    return NULL;
}

CString CStdObject::getAsString(WCHAR *name) {
    CStdObjectProperty* property = this->get(name);
    if (property) {
        switch (property->type) {
            case STDOBJ_PROPERTY_VALUE:
                return property->value;
            case STDOBJ_PROPERTY_VALUE_INT:
                return CString(property->iValue);
            case STDOBJ_PROPERTY_VALUE_DOUBLE:
                return CString(property->fValue);
        }
    }
    return COMMON_EMPTY_STRING;
}

LONG CStdObject::getAsInt(WCHAR *name) {
    CStdObjectProperty* property = this->get(name);
    if (property) {
        switch (property->type) {
            case STDOBJ_PROPERTY_VALUE:
                return property->value.toInt();
            case STDOBJ_PROPERTY_VALUE_INT:
                return property->iValue;
            case STDOBJ_PROPERTY_VALUE_DOUBLE:
                return (LONG)property->fValue;
        }
    }
    return 0;
}

double CStdObject::getAsDouble(WCHAR *name) {
    CStdObjectProperty* property = this->get(name);
    if (property) {
        switch (property->type) {
            case STDOBJ_PROPERTY_VALUE:
                return property->value.toDouble();
            case STDOBJ_PROPERTY_VALUE_DOUBLE:
                return property->fValue;
            case STDOBJ_PROPERTY_VALUE_INT:
                return property->iValue;
        }
    }
    return 0;
}

CArrayList* CStdObject::getAsArray(WCHAR *name) {
    CStdObjectProperty* property = this->get(name);
    if (property && property->type == STDOBJ_PROPERTY_ARRAY) {
        return property->aValue;
    }
    return NULL;
}

CStdObject* CStdObject::getAsObject(WCHAR *name) {
    CStdObjectProperty* property = this->get(name);
    if (property && property->type == STDOBJ_PROPERTY_OBJECT) {
        return property->oValue;
    }
    return NULL;
}

CStdObjectProperty* CStdObject::operator[](WCHAR *name) {
    return this->get(name);
}

CString CStdObject::toJson(CString quote) {
    CString json = "";
    CStdObjectProperty *root;

    this->_quote = quote;
    if (this->_properties && this->_properties->size() > 0) {
        root = this->get(L"root");
        if (root) {
            json += this->_propertyToJson(root, "");
        }
    }

    return json;
}

CString CStdObject::_propertyToJson(CStdObjectProperty *p, CString indent) {
    CString json = "";

    if (p->type == STDOBJ_PROPERTY_VALUE || p->type == STDOBJ_PROPERTY_VALUE_INT || p->type == STDOBJ_PROPERTY_VALUE_DOUBLE) {
        return this->_valueToJson(p, "");
    }

    if (p->type == STDOBJ_PROPERTY_ARRAY) {
        return this->_arrayToJson(p, "");
    }

    if (p->type == STDOBJ_PROPERTY_OBJECT) {
        return this->_objectToJson(p, "");
    }

    return "WEWNĘTRNY BŁĄD. Ta wartość nie powinna zostać zwrócona";
}

CString CStdObject::_valueToJson(CStdObjectProperty *p, CString indent, BOOL withName) {
    if (withName) {
        return indent + p->name + ": " + this->_quote + p->value + this->_quote;
    }

    return indent + this->_quote + p->value + this->_quote;
}

CString CStdObject::_arrayToJson(CStdObjectProperty *p, CString indent, BOOL withName) {
    CStdObjectProperty *item;
    CArrayList *a = p->aValue;
    BOOL onlyScalars;
    CString json;

    if (a && a->size() > 0) {
        // najpierw sprawdzamy czy ta tablica zawiera same wartości
        // skalarne
        onlyScalars = p->hasOnlyScalars();

        if (withName) {
            json = indent + p->name + ": [";
        } else {
            json = indent + "[";
        }
        // jeśli mamy same skalary, to je drukujemy w rządku
        if (onlyScalars) {
            ULONG len = a->size();
            for (ULONG i = 0; i < len; i++) {
                item = (CStdObjectProperty*)a->getAt(i);
                json += this->_valueToJson(item, "");
                if (i < len - 1) {
                    json += ", ";
                }
            }
            json += "]";
        } else {
            ULONG len = a->size();
            for (ULONG i = 0; i < len; i++) {
                item = (CStdObjectProperty*)a->getAt(i);
                if (item->type == STDOBJ_PROPERTY_VALUE) {
                    json += CString("\n") + indent + STDOBJ_DEFAULT_INDENT + this->_valueToJson(item, "");
                } else if (item->type == STDOBJ_PROPERTY_ARRAY) {
                    json += CString("\n") + this->_arrayToJson(item, indent + STDOBJ_DEFAULT_INDENT);
                } else if (item->type == STDOBJ_PROPERTY_OBJECT) {
                    json += CString("\n") + this->_objectToJson(item, indent + STDOBJ_DEFAULT_INDENT);
                }
                if (i < len - 1) {
                    json += ",";
                }
            }
            json += "\n";
            json += indent + "]";
        }
        return json;
    } else {
        if (withName) {
            return indent + p->name + ": []";
        }
        return indent + "[]";
    }
}

CString CStdObject::_objectToJson(CStdObjectProperty *p, CString indent, BOOL withName) {
    CString json = "";

    if (withName) {
        json = indent + p->name + ": {";
    } else {
        json = indent + "{";
    }
    CArrayList *a = p->oValue->_properties;
    ULONG len = a->size();
    if (len == 0) {
        json += "}";
        return json;
    }
    CStdObjectProperty *item;
    for (ULONG i = 0; i < len; i++) {
        item = (CStdObjectProperty*)a->getAt(i);
        if (item->type == STDOBJ_PROPERTY_VALUE) {
            json += CString("\n") + indent + STDOBJ_DEFAULT_INDENT + this->_valueToJson(item, "", TRUE);
        } else if (item->type == STDOBJ_PROPERTY_ARRAY) {
            json += CString("\n") + this->_arrayToJson(item, indent + STDOBJ_DEFAULT_INDENT, TRUE);
        } else if (item->type == STDOBJ_PROPERTY_OBJECT) {
            json += CString("\n") + this->_objectToJson(item, indent + STDOBJ_DEFAULT_INDENT, TRUE);
        }
        if (i < len - 1) {
            json += ",";
        }
    }
    json += "\n";
    json += indent + "}";

    return json;
}

BOOL CStdObject::loadFromFile(CString fname) {
    if (FileExists(fname)) {
        CJsonParser parser;
        this->clean();
        CStdObject* data = parser.parseFile(fname, this);
        if (data) {
            return TRUE;
        } else {
            if (!parser.isError()) {
                AlertBox(CString(L"No json objects found in file [") + fname + L"]");
            } else {
                ErrorBox(parser.getErrorMessage());
            }
        }
    }
    return FALSE;
}

VOID CStdObject::clean() {
    CStdObjectProperty *p;
    if (this->_properties) {
        for (ULONG i = 0; i < this->_properties->size(); i++) {
            p = (CStdObjectProperty*)this->_properties->getAt(i);
            if (p) {
                delete p;
            }
        }
        this->_properties->clear();
    }
}
