/*
 * DataElement/DataNode/DataTree -- structured serialization/unserialization system 
 * designed for the CoolMule project :)
 *
 Copyright (C) 2003 by Charles J. Cliffe
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 */

#include "DataTree.h"
#include <fstream>
#include <math.h>
#include <iomanip>
#include <locale>
#include <stdlib.h>
#include <algorithm>


/* DataElement class */

using namespace std;

#define STRINGIFY(A)  #A

DataElement::DataElement() : data_type(DATA_NULL), data_size(0), unit_size(0), data_val(NULL) {
}

DataElement::DataElement(DataElement &cloneFrom) : data_type(cloneFrom.getDataType()), unit_size(cloneFrom.getUnitSize()) {
    data_val = NULL;
    data_init(cloneFrom.getDataSize());
    if (data_size) {
        memcpy(data_val, cloneFrom.getDataPointer(), data_size);
    }
}

DataElement::~DataElement() {
    if (data_val) {
        delete[] data_val;
        data_val = NULL;
    }
}

void DataElement::data_init(size_t data_size_in) {
    if (data_val) {
        delete[] data_val;
        data_val = NULL;
    }
    data_size = data_size_in;
    if (data_size) {
        data_val = new char[data_size];
        //memset to zero
        std::fill_n(data_val, data_size, 0);
    }
}

char * DataElement::getDataPointer() {
    return data_val;
}

int DataElement::getDataType() {
    return data_type;
}

size_t DataElement::getDataSize() {
    return data_size;
}

unsigned int DataElement::getUnitSize() {
    return unit_size;
}

#define DataElementSetNumericDef(enumtype, datatype) void DataElement::set(const datatype& val_in) { \
        data_type = enumtype; \
        unit_size = sizeof(datatype); \
        data_init(unit_size); \
        memcpy(data_val, &val_in, data_size); \
}

DataElementSetNumericDef(DATA_CHAR, char)
DataElementSetNumericDef(DATA_UCHAR, unsigned char)
DataElementSetNumericDef(DATA_INT, int)
DataElementSetNumericDef(DATA_UINT, unsigned int)
DataElementSetNumericDef(DATA_LONG, long)
DataElementSetNumericDef(DATA_ULONG, unsigned long)
DataElementSetNumericDef(DATA_LONGLONG, long long)
DataElementSetNumericDef(DATA_FLOAT, float)
DataElementSetNumericDef(DATA_DOUBLE, double)
DataElementSetNumericDef(DATA_LONGDOUBLE, long double)

void DataElement::set(const char *data_in, long size_in) {
    data_type = DATA_VOID;
    if (!data_size) { return; }
    data_init(size_in);
    memcpy(data_val, data_in, data_size);
}

void DataElement::set(const char *data_in) {
    data_type = DATA_STRING;
    data_init(strlen(data_in) + 1);
    memcpy(data_val, data_in, data_size);
}

void DataElement::set(const string &str_in) {
    data_type = DATA_STRING;
    data_init(str_in.length() + 1);
    memcpy(data_val, str_in.c_str(), data_size);
}

void DataElement::set(const wstring &wstr_in) {
    data_type = DATA_WSTRING;

    //wchar_t is tricky, the terminating zero is actually a (wchar_t)0 !
    //wchar_t is typically 16 bits on windows, and 32 bits on Unix, so use sizeof(wchar_t) everywhere.
    size_t maxLenBytes = (wstr_in.length()+1) * sizeof(wchar_t);

    //be paranoid, zero the buffer
    char *tmp_str = (char *)calloc(maxLenBytes, sizeof(char));

    //if something awful happens, the last sizeof(wchar_t) is at least zero...
    wcstombs(tmp_str, wstr_in.c_str(), maxLenBytes - sizeof(wchar_t));

    data_init(maxLenBytes);

    memcpy(data_val, tmp_str, data_size);
    free(tmp_str);
}

void DataElement::set(vector<string> &strvect_in) {
    vector<string>::iterator i;
    long vectsize;
    long ptr;

    data_type = DATA_STR_VECTOR;

    vectsize = 0;

    for (i = strvect_in.begin(); i != strvect_in.end(); i++) {
        vectsize += (*i).length() + 1;
    }

    data_init(vectsize);

    ptr = 0;

    for (i = strvect_in.begin(); i != strvect_in.end(); i++) {
        int str_length;

        str_length = (*i).length() + 1;

        memcpy(data_val + ptr, (*i).c_str(), str_length);
        ptr += str_length;
    }
}

void DataElement::set(std::set<string> &strset_in) {
    std::set<string>::iterator i;
    vector<string> tmp_vect;

    for (i = strset_in.begin(); i != strset_in.end(); i++) {
        tmp_vect.push_back(*i);
    }

    set(tmp_vect);
}

#define DataElementSetNumericVectorDef(enumtype, datatype) void DataElement::set(vector<datatype>& val_in) { \
        data_type = enumtype; \
        unit_size = sizeof(datatype); \
        data_init(unit_size * val_in.size()); \
        memcpy(data_val, &val_in[0], data_size); \
}

DataElementSetNumericVectorDef(DATA_CHAR_VECTOR, char)
DataElementSetNumericVectorDef(DATA_UCHAR_VECTOR, unsigned char)
DataElementSetNumericVectorDef(DATA_INT_VECTOR, int)
DataElementSetNumericVectorDef(DATA_UINT_VECTOR, unsigned int)
DataElementSetNumericVectorDef(DATA_LONG_VECTOR, long)
DataElementSetNumericVectorDef(DATA_ULONG_VECTOR, unsigned long)
DataElementSetNumericVectorDef(DATA_LONGLONG_VECTOR, long long)
DataElementSetNumericVectorDef(DATA_FLOAT_VECTOR, float)
DataElementSetNumericVectorDef(DATA_DOUBLE_VECTOR, double)
DataElementSetNumericVectorDef(DATA_LONGDOUBLE_VECTOR, long double)


#define DataElementGetNumericDef(enumtype, datatype, ...) void DataElement::get(datatype& val_out) { \
if (!data_type) \
return; \
    int _compat[] = {__VA_ARGS__}; \
    if (data_type != enumtype) { \
        bool compat = false; \
        for (size_t i = 0; i < sizeof(_compat)/sizeof(int); i++) { \
              if (_compat[i] == data_type) { \
                  compat = true; \
                  break; \
              } \
        } \
        if (!compat) { \
            throw(DataTypeMismatchException("Type mismatch, element type " #enumtype " is not compatible with a " #datatype)); \
        } \
        if (sizeof(datatype) < data_size) { \
            std::cout << "Warning, data type mismatch requested size for '" << #datatype << "(" << sizeof(datatype) << ")' < data size '" << data_size << "'; possible loss of data."; \
        } \
        memset(&val_out, 0, sizeof(datatype)); \
        if (sizeof(datatype) > 4 && data_size <= 4) { \
            int v = 0; memcpy(&v,data_val,data_size); \
            val_out = (datatype)v; \
            return; \
        } else { \
            memcpy(&val_out, data_val, (sizeof(datatype) < data_size) ? sizeof(datatype) : data_size); \
        } \
        return; \
    } \
    memcpy(&val_out, data_val, data_size); \
}

DataElementGetNumericDef(DATA_CHAR, char, DATA_UCHAR, DATA_UINT, DATA_ULONG, DATA_LONGLONG, DATA_LONGDOUBLE, DATA_INT, DATA_LONG)
DataElementGetNumericDef(DATA_UCHAR, unsigned char, DATA_CHAR, DATA_UINT, DATA_ULONG, DATA_LONGLONG, DATA_LONGDOUBLE, DATA_INT, DATA_LONG)
DataElementGetNumericDef(DATA_UINT, unsigned int, DATA_CHAR, DATA_UCHAR, DATA_ULONG, DATA_LONGLONG, DATA_LONGDOUBLE, DATA_INT, DATA_LONG)
DataElementGetNumericDef(DATA_ULONG, unsigned long, DATA_CHAR, DATA_UCHAR, DATA_UINT, DATA_LONGLONG, DATA_LONGDOUBLE, DATA_INT, DATA_LONG)
DataElementGetNumericDef(DATA_LONGLONG, long long, DATA_CHAR, DATA_UCHAR, DATA_UINT, DATA_ULONG, DATA_LONGDOUBLE, DATA_INT, DATA_LONG)
DataElementGetNumericDef(DATA_LONGDOUBLE, long double, DATA_CHAR, DATA_UCHAR, DATA_UINT, DATA_ULONG, DATA_LONGLONG, DATA_INT, DATA_LONG)
DataElementGetNumericDef(DATA_INT, int, DATA_CHAR, DATA_UCHAR, DATA_UINT, DATA_ULONG, DATA_LONGLONG, DATA_LONGDOUBLE, DATA_LONG)
DataElementGetNumericDef(DATA_LONG, long, DATA_CHAR, DATA_UCHAR, DATA_UINT, DATA_ULONG, DATA_LONGLONG, DATA_LONGDOUBLE, DATA_INT)




#define DataElementGetFloatingPointDef(enumtype, datatype, ...) void DataElement::get(datatype& val_out) { \
if (!data_type) \
return; \
    int _compat[] = {__VA_ARGS__}; \
    if (data_type != enumtype) { \
        bool compat = false; \
        for (size_t i = 0; i < sizeof(_compat)/sizeof(int); i++) { \
              if (_compat[i] == data_type) { \
                  compat = true; \
                  break; \
              } \
        } \
        if (!compat) { \
            throw(DataTypeMismatchException("Type mismatch, element type " #enumtype " is not compatible with a " #datatype)); \
        } \
        if (data_type == DATA_FLOAT || data_type == DATA_DOUBLE) { \
            if (sizeof(datatype) < data_size) { \
                std::cout << "Warning, data type mismatch requested size for '" << #datatype << "(" << sizeof(datatype) << ")' < data size '" << data_size << "'; possible loss of data."; \
            } \
            memset(&val_out, 0, sizeof(datatype)); \
            if (sizeof(datatype) > 4 && data_size <= 4) { \
                int v = 0; memcpy(&v,data_val,data_size); \
                val_out = (datatype)v; \
                return; \
            } else { \
                memcpy(&val_out, data_val, (sizeof(datatype) < data_size) ? sizeof(datatype) : data_size); \
            } \
        } else { \
            long long tmp_int; \
            get(tmp_int); \
            datatype tmp_float = (float)tmp_int; \
            memcpy(&val_out, &tmp_float, sizeof(datatype)); \
        } \
        return; \
    } \
    memcpy(&val_out, data_val, data_size); \
}

DataElementGetFloatingPointDef(DATA_FLOAT, float, DATA_DOUBLE, DATA_CHAR, DATA_UCHAR, DATA_UINT, DATA_ULONG, DATA_LONGLONG, DATA_LONGDOUBLE, DATA_INT,
        DATA_LONG)
DataElementGetFloatingPointDef(DATA_DOUBLE, double, DATA_FLOAT, DATA_CHAR, DATA_UCHAR, DATA_UINT, DATA_ULONG, DATA_LONGLONG, DATA_LONGDOUBLE, DATA_INT,
        DATA_LONG)

void DataElement::get(char **data_in) {
    if (data_type != DATA_VOID)
        throw(DataTypeMismatchException("Type mismatch, not a CHAR*"));
    *data_in = new char[data_size];
    memcpy(*data_in, data_val, data_size);
}

void DataElement::get(string &str_in) {
    if (!data_type)
        return;

    if (data_type != DATA_STRING)
        throw(DataTypeMismatchException("Type mismatch, not a STRING"));

    if (!str_in.empty())	// flush the string
    {
        str_in.erase(str_in.begin(), str_in.end());
    }

    if (data_val) {
        str_in.append(data_val);
    }
}

void DataElement::get(wstring &wstr_in) {
    if (!data_type)
        return;
    
    if (data_type != DATA_WSTRING)
        throw(DataTypeMismatchException("Type mismatch, not a WSTRING"));

    // flush the string
    wstr_in.clear();
       
    if (data_val) {

        //data_val is an array of bytes holding wchar_t characters, plus a terminating (wchar_t)0
        //wchar_t is typically 16 bits on windows, and 32 bits on Unix, so use sizeof(wchar_t) everywhere.
        int maxNbWchars = (data_size - sizeof(wchar_t)) / sizeof(wchar_t);

        //be paranoid, zero the buffer
        wchar_t *tmp_wstr = (wchar_t *)calloc(maxNbWchars + 1, sizeof(wchar_t));

        //the last wchar_t is actually zero if anything goes wrong...
        mbstowcs(tmp_wstr, data_val, maxNbWchars);

        wstr_in.assign(tmp_wstr);

        free(tmp_wstr);
    }
}

void DataElement::get(vector<string> &strvect_in) {
    size_t ptr;
    if (!data_type)
        return;

    if (data_type != DATA_STR_VECTOR)
        throw(DataTypeMismatchException("Type mismatch, not a STRING VECTOR"));

    ptr = 0;

    while (ptr != data_size) {
        strvect_in.push_back(string(data_val + ptr));
        ptr += strlen(data_val + ptr) + 1;
    }

}

void DataElement::get(std::set<string> &strset_in) {
    if (!data_type)
        return;

    if (data_type != DATA_STR_VECTOR)
        throw(DataTypeMismatchException("Type mismatch, not a STRING VECTOR/SET"));

    std::vector<string> tmp_vect;
    std::vector<string>::iterator i;

    get(tmp_vect);

    for (i = tmp_vect.begin(); i != tmp_vect.end(); i++) {
        strset_in.insert(*i);
    }
}

#define DataElementGetNumericVectorDef(enumtype, datatype, ...) void DataElement::get(vector<datatype>& val_out) { \
if (!data_type || !unit_size) return; \
if (data_type != enumtype) { \
       int _compat[] = {__VA_ARGS__}; \
       bool compat = false; \
       for (size_t i = 0; i < sizeof(_compat)/sizeof(int); i++) { \
             if (_compat[i] == data_type) { \
                 compat = true; \
                 break; \
             } \
       } \
       if (!compat) { \
           throw(DataTypeMismatchException("Type mismatch, element type is not compatible with a " #datatype)); \
       } \
       if (sizeof(datatype) < unit_size) { \
           std::cout << "Warning, data type mismatch for vector<" #datatype ">; " #datatype " size " << sizeof(datatype) << " is less than unit size " << unit_size << "; possible loss of data."; \
       } \
       datatype temp_val; \
       size_t ptr = 0; \
           while (ptr < data_size) { \
               temp_val = 0; \
               memcpy(&temp_val, data_val + ptr, (unit_size > sizeof(datatype))?sizeof(datatype):unit_size); \
               val_out.push_back(temp_val); \
               ptr += unit_size; \
           } \
           return; \
    } \
    val_out.assign(data_val, data_val + (data_size / sizeof(datatype))); \
}

DataElementGetNumericVectorDef(DATA_CHAR_VECTOR, char, DATA_UCHAR_VECTOR, DATA_INT_VECTOR, DATA_UINT_VECTOR, DATA_LONG_VECTOR, DATA_ULONG_VECTOR,
        DATA_LONGLONG_VECTOR);
DataElementGetNumericVectorDef(DATA_UCHAR_VECTOR, unsigned char, DATA_CHAR_VECTOR, DATA_INT_VECTOR, DATA_UINT_VECTOR, DATA_LONG_VECTOR,
        DATA_ULONG_VECTOR, DATA_LONGLONG_VECTOR);
DataElementGetNumericVectorDef(DATA_INT_VECTOR, int, DATA_CHAR_VECTOR, DATA_UCHAR_VECTOR, DATA_UINT_VECTOR, DATA_LONG_VECTOR, DATA_ULONG_VECTOR,
        DATA_LONGLONG_VECTOR);
DataElementGetNumericVectorDef(DATA_UINT_VECTOR, unsigned int, DATA_CHAR_VECTOR, DATA_UCHAR_VECTOR, DATA_INT_VECTOR, DATA_LONG_VECTOR,
        DATA_ULONG_VECTOR, DATA_LONGLONG_VECTOR);
DataElementGetNumericVectorDef(DATA_LONG_VECTOR, long, DATA_CHAR_VECTOR, DATA_UCHAR_VECTOR, DATA_INT_VECTOR, DATA_UINT_VECTOR, DATA_ULONG_VECTOR,
        DATA_LONGLONG_VECTOR);
DataElementGetNumericVectorDef(DATA_ULONG_VECTOR, unsigned long, DATA_CHAR_VECTOR, DATA_UCHAR_VECTOR, DATA_INT_VECTOR, DATA_UINT_VECTOR,
        DATA_LONG_VECTOR, DATA_LONGLONG_VECTOR);
DataElementGetNumericVectorDef(DATA_LONGLONG_VECTOR, long long, DATA_CHAR_VECTOR, DATA_UCHAR_VECTOR, DATA_INT_VECTOR, DATA_UINT_VECTOR,
        DATA_LONG_VECTOR, DATA_ULONG_VECTOR);
DataElementGetNumericVectorDef(DATA_FLOAT_VECTOR, float, DATA_DOUBLE_VECTOR, DATA_LONGDOUBLE_VECTOR);
DataElementGetNumericVectorDef(DATA_DOUBLE_VECTOR, double, DATA_FLOAT_VECTOR, DATA_LONGDOUBLE_VECTOR);
DataElementGetNumericVectorDef(DATA_LONGDOUBLE_VECTOR, long double, DATA_DOUBLE_VECTOR, DATA_FLOAT_VECTOR);

std::string DataElement::toString() {
    int dataType = getDataType();
    std::string strValue = "";
    
    try {
        if (dataType == DATA_STRING) {
            get(strValue);
        } else if (dataType == DATA_INT || dataType == DATA_LONG || dataType == DATA_LONGLONG) {
            long long intSettingValue;
            get(intSettingValue);
            strValue = std::to_string(intSettingValue);
        } else if (dataType == DATA_FLOAT || dataType == DATA_DOUBLE) {
            double floatSettingValue;
            get(floatSettingValue);
            strValue = std::to_string(floatSettingValue);
        } else if (dataType == DATA_NULL) {
            strValue = "";
        } else if (dataType == DATA_WSTRING) {
            std::wstring wstr;
            get(wstr);
            strValue = *wstr.c_str();
        }
        else {
            std::cout << "Unhandled DataElement toString for type: " << dataType  << std::endl;
        }
    } catch (DataTypeMismatchException e) {
        std::cout << "toString() DataTypeMismatch: " << dataType  << std::endl;
    }
    
    return strValue;
}

long DataElement::getSerializedSize() {
    return sizeof(int) + sizeof(long) + data_size;
}

long DataElement::getSerialized(char **ser_str) {
    long ser_size = getSerializedSize();

    *ser_str = new char[ser_size];

    char *ser_pointer;

    ser_pointer = *ser_str;

    memcpy(ser_pointer, &data_type, sizeof(int));
    ser_pointer += sizeof(int);
    memcpy(ser_pointer, &data_size, sizeof(long));
    ser_pointer += sizeof(long);
    memcpy(ser_pointer, data_val, data_size);

    return ser_size;
}

void DataElement::setSerialized(char *ser_str) {
    char *ser_pointer = ser_str;

    memcpy(&data_type, ser_pointer, sizeof(unsigned char));
    ser_pointer += sizeof(unsigned char);
    memcpy(&data_size, ser_pointer, sizeof(unsigned int));
    ser_pointer += sizeof(unsigned int);

    data_init(data_size);
    memcpy(data_val, ser_pointer, data_size);
}

/* DataNode class */

DataNode::DataNode(): parentNode(NULL), ptr(0) {
    data_elem = new DataElement();
}

DataNode::DataNode(const char *name_in): parentNode(NULL), ptr(0) {
    node_name = name_in;
    data_elem = new DataElement();
}

DataNode::DataNode(const char *name_in, DataNode &cloneFrom): parentNode(NULL), ptr(0) {
    node_name = name_in;
    data_elem = new DataElement(*cloneFrom.element());
    
    // TODO: stack recursion optimization
    while (cloneFrom.hasAnother()) {
        DataNode *cNode = cloneFrom.getNext();
        newChildCloneFrom(cNode->getName().c_str(), cNode);
    }
}

DataNode::DataNode(const char *name_in, DataElement &cloneFrom): parentNode(NULL), ptr(0) {
    node_name = name_in;
    data_elem = new DataElement(cloneFrom);
}

DataNode::~DataNode() {
    while (children.size()) {
        DataNode *del = children.back();
        children.pop_back();
        delete del;
    }
    if (data_elem) {
        delete data_elem;
    }
}

void DataNode::setName(const char *name_in) {
    node_name = name_in;
}

DataElement *DataNode::element() {
    return data_elem;
}

DataNode *DataNode::newChild(const char *name_in) {
    children.push_back(new DataNode(name_in));
    childmap[name_in].push_back(children.back());

    children.back()->setParentNode(*this);

    return children.back();
}

DataNode *DataNode::newChild(const char *name_in, DataNode *otherNode) {
    children.push_back(otherNode);
    childmap[name_in].push_back(children.back());
    
    children.back()->setParentNode(*this);
    
    return children.back();
}

DataNode *DataNode::newChildCloneFrom(const char *name_in, DataNode *cloneFrom) {
    DataNode *cloneNode = new DataNode(name_in, *cloneFrom->element());
    
    children.push_back(cloneNode);
    childmap[name_in].push_back(children.back());
    children.back()->setParentNode(*this);
    
    // TODO: stack recursion optimization
    while (cloneFrom->hasAnother()) {
        DataNode *cNode = cloneFrom->getNext();
        cloneNode->newChildCloneFrom(cNode->getName().c_str(), cNode);
    }
    
    cloneFrom->rewind();
    
    return children.back();
}


DataNode *DataNode::child(const char *name_in, int index) {
    DataNode *child_ret;

    child_ret = childmap[name_in][index];

    if (!child_ret) {
        stringstream error_str;
        error_str << "no child '" << index << "' in DataNode '" << node_name << "'";
        throw(DataInvalidChildException(error_str.str().c_str()));
    }

    return child_ret;
}

DataNode *DataNode::child(int index) {

    DataNode *child_ret;

    child_ret = children[index];

    if (!child_ret) {
        stringstream error_str;
        error_str << "no child '" << index << "' in DataNode '" << node_name << "'";
        throw(DataInvalidChildException(error_str.str().c_str()));
    }

    return child_ret;
}

int DataNode::numChildren() {
    return children.size();
}

int DataNode::numChildren(const char *name_in) {
    return childmap[name_in].size();
}

bool DataNode::hasAnother() {
    return children.size() != ptr;
}

bool DataNode::hasAnother(const char *name_in) {
    return childmap[name_in].size() != childmap_ptr[name_in];
}

DataNode *DataNode::getNext() {
    return child(ptr++);
}

DataNode *DataNode::getNext(const char *name_in) {
    return child(name_in, childmap_ptr[name_in]++);
}

void DataNode::rewind() {
    ptr = 0;
    childmap_ptr.erase(childmap_ptr.begin(),childmap_ptr.end());
}

void DataNode::rewind(const char *name_in) {
    childmap_ptr[name_in] = 0;
}

/* DataTree class */

DataTree::DataTree(const char *name_in) {
    dn_root.setName(name_in);
}

DataTree::DataTree() {

}

DataTree::~DataTree() {
}
;

DataNode *DataTree::rootNode() {
    return &dn_root;
}

std::string trim(std::string& s, const std::string& drop = " ") {
    std::string r = s.erase(s.find_last_not_of(drop) + 1);
    return r.erase(0, r.find_first_not_of(drop));
}

string DataTree::wsEncode(const wstring& wstr) {
    stringstream encStream;
    
    //wchar_t is typically 16 bits on windows, and 32 bits on Unix, so use sizeof(wchar_t) everywhere.
    int bufSizeBytes = (wstr.length()+1) * sizeof(wchar_t);

    char *data_str = (char *)calloc(bufSizeBytes, sizeof(char));
    
    wcstombs(data_str, wstr.c_str(), bufSizeBytes - sizeof(wchar_t));
    
    std::string byte_str(data_str);

    free(data_str);
    
    encStream << std::hex;

    for(auto i = byte_str.begin(); i != byte_str.end(); i++) {
        encStream << '%' << setfill('0') << (unsigned int)((unsigned char)(*i));
    }
    
    return encStream.str();
}

wstring DataTree::wsDecode(const string& str) {
    
    std::stringstream decStream;
    std::stringstream mbstr;
    unsigned int x;
    
    string decStr = str;
    std::replace( decStr.begin(), decStr.end(), '%', ' ');
    decStream << trim(decStr);
  
    string sResult;

    //this actually assume we will get as many char as wchar_t from the decodes string,
    //who cares ?
    int maxLen = decStr.length();

    //wchar_t is typically 16 bits on windows, and 32 bits on Unix, so use sizeof(wchar_t) everywhere.
    wchar_t *wc_str = (wchar_t *) calloc(maxLen  + 1, sizeof(wchar_t));

    while (!decStream.eof()) {
        decStream >> std::hex >> x;
        //extract actually 2 hex-chars by 2 hex-chars to form a char value.
        mbstr << (unsigned char) x;
    }

    mbstowcs(wc_str, mbstr.str().c_str(), maxLen);
    
    wstring result(wc_str);

    free(wc_str);
    
    return result;
}

void DataTree::decodeXMLText(DataNode *elem, const char *src_text, DT_FloatingPointPolicy fpp) {

    int tmp_char;
    int tmp_int;
    long tmp_long;
    long long tmp_llong;
    double tmp_double;
    float tmp_float;
    string tmp_str;
    string tmp_str2;
    std::stringstream tmp_stream;
    std::stringstream tmp_stream2;

    vector<char> tmp_charvect;
    vector<int> tmp_intvect;
    vector<long> tmp_longvect;
    vector<long> tmp_llongvect;
    vector<long>::iterator tmp_llongvect_i;
    vector<double> tmp_doublevect;
    vector<double>::iterator tmp_doublevect_i;
    vector<float> tmp_floatvect;

    bool vChars = false;
    bool vInts = false;
    bool vLongs = false;

    string in_text = src_text;

    trim(in_text);
    trim(in_text, "\r\n");
    tmp_stream.str("");
    tmp_stream2.str("");

    if (in_text.find_first_not_of("0123456789-") == string::npos) {
        tmp_stream << in_text;
        tmp_stream >> tmp_llong;

        tmp_int = (int)tmp_llong;
        tmp_long = (long)tmp_llong;

        if (tmp_int == tmp_llong) {
            elem->element()->set(tmp_int);
        } else if (tmp_long == tmp_llong) {
            elem->element()->set(tmp_long);
        } else {
            elem->element()->set(tmp_llong);
        }
    } else if (in_text.find_first_not_of("0123456789.e+-") == string::npos) {
        tmp_stream << in_text;

        if (fpp == USE_FLOAT) {
            tmp_stream >> tmp_float;

            elem->element()->set((float) tmp_float);
        } else {
            tmp_stream >> tmp_double;

            elem->element()->set((double) tmp_double);
        }
    } else if (in_text.find_first_not_of("0123456789- ") == string::npos) {
        tmp_stream << in_text;

        vChars = true;
        vInts = true;
        vLongs = true;

        while (!tmp_stream.eof()) {
            tmp_stream >> tmp_llong;
            tmp_char = tmp_llong;
            tmp_int = tmp_llong;
            tmp_long = tmp_llong;
            if (tmp_char != tmp_llong) {
                vChars = false;
            }
            if (tmp_int != tmp_llong) {
                vInts = false;
            }
            if (tmp_long != tmp_llong) {
                vLongs = false;
            }
            tmp_llongvect.push_back((long) tmp_long);
        }

        if (vChars) {
            for (tmp_llongvect_i = tmp_llongvect.begin(); tmp_llongvect_i != tmp_llongvect.end(); tmp_llongvect_i++) {
                tmp_charvect.push_back(*tmp_llongvect_i);
            }
            tmp_llongvect.clear();
            elem->element()->set(tmp_charvect);
            tmp_charvect.clear();

        } else if (vInts) {
            for (tmp_llongvect_i = tmp_llongvect.begin(); tmp_llongvect_i != tmp_llongvect.end(); tmp_llongvect_i++) {
                tmp_intvect.push_back(*tmp_llongvect_i);
            }
            tmp_llongvect.clear();
            elem->element()->set(tmp_intvect);
            tmp_intvect.clear();
        } else if (vLongs) {
            for (tmp_llongvect_i = tmp_llongvect.begin(); tmp_llongvect_i != tmp_llongvect.end(); tmp_llongvect_i++) {
                tmp_longvect.push_back(*tmp_llongvect_i);
            }
            tmp_llongvect.clear();
            elem->element()->set(tmp_longvect);
            tmp_longvect.clear();
        } else {
            elem->element()->set(tmp_llongvect);
        }
    } else if (in_text.find_first_not_of("0123456789.e-+ ") == string::npos) {
        tmp_stream << in_text;

        if (fpp == USE_FLOAT) {
            tmp_floatvect.clear();
        } else {
            tmp_doublevect.clear();
        }

        while (!tmp_stream.eof()) {

            if (fpp == USE_FLOAT) {
                tmp_stream >> tmp_float;
                tmp_floatvect.push_back(tmp_float);
            } else {
                tmp_stream >> tmp_double;
                tmp_doublevect.push_back(tmp_double);
            }
        }

        if (fpp == USE_FLOAT) {
            elem->element()->set(tmp_floatvect);
        } else {
            elem->element()->set(tmp_doublevect);
        }
    } else if (in_text.find_first_not_of("0123456789abcdef%") == string::npos) {
        elem->element()->set(wsDecode(src_text));
    } else {
        elem->element()->set(src_text);
        //					printf( "Unhandled DataTree XML Field: [%s]", tmp_str.c_str() );
    }

}

void DataTree::setFromXML(DataNode *elem, TiXmlNode *elxml, bool root_node, DT_FloatingPointPolicy fpp) {
    TiXmlText *pText;
    int t = elxml->Type();
    string tmp_str;

    switch (t) {
    case TiXmlNode::TINYXML_DOCUMENT:
        //				printf( "Document" );
        break;

    case TiXmlNode::TINYXML_ELEMENT:
        if (!root_node)
            elem = elem->newChild(elxml->Value());

        const TiXmlAttribute *attribs;
        attribs = elxml->ToElement()->FirstAttribute();

        while (attribs) {

// following badgerfish xml->json and xml->ruby convention for attributes..
            string attrName("@");
            attrName.append(attribs->Name());

            decodeXMLText(elem->newChild(attrName.c_str()), attribs->Value(), fpp);

            attribs = attribs->Next();
        }

        //				printf( "Element \"%s\"", elxml->Value());
        break;

    case TiXmlNode::TINYXML_COMMENT:
//				printf( "Comment: \"%s\"", elxml->Value());
        break;

    case TiXmlNode::TINYXML_UNKNOWN:
//				printf( "Unknown" );
        break;

    case TiXmlNode::TINYXML_TEXT:
        pText = elxml->ToText();

        decodeXMLText(elem, pText->Value(), fpp);

//				pText = elxml->ToText();
//				printf( "Text: [%s]", pText->Value() );
        break;

    case TiXmlNode::TINYXML_DECLARATION:
//				printf( "Declaration" );
        break;
    default:
        break;
    }

//	printf( "\n" );

    TiXmlNode * pChild;

    if (!elxml->NoChildren()) {
        if (elxml->FirstChild()->Type() == TiXmlNode::TINYXML_ELEMENT) {
            if (elxml->FirstChild()->Value() == TIXML_STRING("str")) {
                std::vector<std::string> tmp_strvect;

                for (pChild = elxml->FirstChild(); pChild != 0; pChild = pChild->NextSibling()) {
                    if (pChild->Value() == TIXML_STRING("str")) {
                        if (!pChild->FirstChild()) {
                            tmp_strvect.push_back("");
                            continue;
                        }

                        pText = pChild->FirstChild()->ToText();

                        if (pText) {
                            tmp_str = pText->Value();
                            tmp_strvect.push_back(tmp_str);
                        }
                    }
                }

                elem->element()->set(tmp_strvect);

                return;
            }
        }
    }

    for (pChild = elxml->FirstChild(); pChild != 0; pChild = pChild->NextSibling()) {
        setFromXML(elem, pChild, false, fpp);
    }

}

void DataTree::nodeToXML(DataNode *elem, TiXmlElement *elxml) {
    DataNode *child;

    elem->rewind();

    while (elem->hasAnother()) {
        child = elem->getNext();

        std::string nodeName = child->getName();

        TiXmlElement *element;

        element = new TiXmlElement(nodeName.length() ? nodeName.c_str() : "node");
        std::string tmp;
        std::wstring wtmp;
        std::stringstream tmp_stream;
        TiXmlText *text;
        std::vector<float> tmp_floatvect;
        std::vector<float>::iterator tmp_floatvect_i;
        std::vector<double> tmp_doublevect;
        std::vector<double>::iterator tmp_doublevect_i;
        std::vector<int> tmp_intvect;
        std::vector<int>::iterator tmp_intvect_i;
        std::vector<char> tmp_charvect;
        std::vector<char>::iterator tmp_charvect_i;
        std::vector<unsigned char> tmp_ucharvect;
        std::vector<unsigned char>::iterator tmp_ucharvect_i;
        std::vector<unsigned int> tmp_uintvect;
        std::vector<unsigned int>::iterator tmp_uintvect_i;
        std::vector<long> tmp_longvect;
        std::vector<long>::iterator tmp_longvect_i;
        std::vector<unsigned long> tmp_ulongvect;
        std::vector<unsigned long>::iterator tmp_ulongvect_i;
        std::vector<long long> tmp_llongvect;
        std::vector<long long>::iterator tmp_llongvect_i;
        std::vector<unsigned long long> tmp_ullongvect;
        std::vector<unsigned long long>::iterator tmp_ullongvect_i;
        std::vector<string> tmp_stringvect;
        std::vector<string>::iterator tmp_stringvect_i;
        TiXmlElement *tmp_node;
        char *tmp_pstr;
        double tmp_double;
        long double tmp_ldouble;
        float tmp_float;
        char tmp_char;
        unsigned char tmp_uchar;
        int tmp_int;
        unsigned int tmp_uint;
        long tmp_long;
        unsigned long tmp_ulong;
        long long tmp_llong;

        switch (child->element()->getDataType()) {
        case DATA_NULL:
            break;
        case DATA_VOID:
            child->element()->get(&tmp_pstr);
// following badgerfish xml->json and xml->ruby convention for attributes..
            if (nodeName.substr(0, 1) == string("@")) {
                elxml->SetAttribute(nodeName.substr(1).c_str(), tmp_pstr);
                delete element;
                element = NULL;
            } else {
                text = new TiXmlText(tmp_pstr);
                element->LinkEndChild(text);
            }
            delete[] tmp_pstr;
            break;
        case DATA_CHAR:
            child->element()->get(tmp_char);

            tmp_stream.str("");

            tmp_stream << tmp_char;

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            break;
        case DATA_UCHAR:
            child->element()->get(tmp_uchar);

            tmp_stream.str("");

            tmp_stream << tmp_uchar;

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            break;
        case DATA_INT:
            child->element()->get(tmp_int);

            tmp_stream.str("");

            tmp_stream << tmp_int;

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            break;
        case DATA_UINT:
            child->element()->get(tmp_uint);

            tmp_stream.str("");

            tmp_stream << tmp_uint;

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            break;
        case DATA_LONG:
            child->element()->get(tmp_long);

            tmp_stream.str("");

            tmp_stream << tmp_long;

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            break;
        case DATA_ULONG:
            child->element()->get(tmp_ulong);

            tmp_stream.str("");

            tmp_stream << tmp_ulong;

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            break;
        case DATA_LONGLONG:
            child->element()->get(tmp_llong);

            tmp_stream.str("");

            tmp_stream << tmp_llong;

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            break;
        case DATA_FLOAT:
            child->element()->get(tmp_float);

            tmp_stream.str("");

            tmp_stream << tmp_float;

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            break;
        case DATA_DOUBLE:
            child->element()->get(tmp_double);

            tmp_stream.str("");

            tmp_stream << tmp_double;

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            break;
        case DATA_LONGDOUBLE:
            child->element()->get(tmp_ldouble);

            tmp_stream.str("");

            tmp_stream << tmp_ldouble;

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            break;
        case DATA_STRING:
            child->element()->get(tmp);
            if (nodeName.substr(0, 1) == string("@")) {
                elxml->SetAttribute(nodeName.substr(1).c_str(), tmp.c_str());
                delete element;
                element = NULL;
            } else {
                text = new TiXmlText(tmp.c_str());
                element->LinkEndChild(text);
            }
            break;
        case DATA_WSTRING:
            child->element()->get(wtmp);
            tmp = wsEncode(wtmp);
            if (nodeName.substr(0, 1) == string("@")) {
                elxml->SetAttribute(nodeName.substr(1).c_str(), tmp.c_str());
                delete element;
                element = NULL;
            } else {
                text = new TiXmlText(tmp.c_str());
                element->LinkEndChild(text);
            }
            break;
        case DATA_STR_VECTOR:
            child->element()->get(tmp_stringvect);

            tmp_stream.str("");

            for (tmp_stringvect_i = tmp_stringvect.begin(); tmp_stringvect_i != tmp_stringvect.end(); tmp_stringvect_i++) {
                tmp_node = new TiXmlElement("str");
                text = new TiXmlText((*tmp_stringvect_i).c_str());
                tmp_node->LinkEndChild(text);
                element->LinkEndChild(tmp_node);
            }

            tmp_stringvect.clear();
            break;
        case DATA_CHAR_VECTOR:
            child->element()->get(tmp_charvect);

            tmp_stream.str("");

            for (tmp_charvect_i = tmp_charvect.begin(); tmp_charvect_i != tmp_charvect.end(); tmp_charvect_i++) {
                tmp_stream << (*tmp_charvect_i);
                if (tmp_charvect_i != tmp_charvect.end() - 1)
                    tmp_stream << " ";
            }

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            tmp_charvect.clear();
            break;
        case DATA_UCHAR_VECTOR:
            child->element()->get(tmp_ucharvect);

            tmp_stream.str("");

            for (tmp_ucharvect_i = tmp_ucharvect.begin(); tmp_ucharvect_i != tmp_ucharvect.end(); tmp_ucharvect_i++) {
                tmp_stream << (*tmp_ucharvect_i);
                if (tmp_ucharvect_i != tmp_ucharvect.end() - 1)
                    tmp_stream << " ";
            }

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            tmp_ucharvect.clear();
            break;
        case DATA_INT_VECTOR:
            child->element()->get(tmp_intvect);

            tmp_stream.str("");

            for (tmp_intvect_i = tmp_intvect.begin(); tmp_intvect_i != tmp_intvect.end(); tmp_intvect_i++) {
                tmp_stream << (*tmp_intvect_i);
                if (tmp_intvect_i != tmp_intvect.end() - 1)
                    tmp_stream << " ";
            }

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            tmp_intvect.clear();
            break;
        case DATA_UINT_VECTOR:
            child->element()->get(tmp_uintvect);

            tmp_stream.str("");

            for (tmp_uintvect_i = tmp_uintvect.begin(); tmp_uintvect_i != tmp_uintvect.end(); tmp_uintvect_i++) {
                tmp_stream << (*tmp_intvect_i);
                if (tmp_uintvect_i != tmp_uintvect.end() - 1)
                    tmp_stream << " ";
            }

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            tmp_uintvect.clear();
            break;
        case DATA_LONG_VECTOR:
            child->element()->get(tmp_longvect);

            tmp_stream.str("");

            for (tmp_longvect_i = tmp_longvect.begin(); tmp_longvect_i != tmp_longvect.end(); tmp_longvect_i++) {
                tmp_stream << (*tmp_longvect_i);
                if (tmp_longvect_i != tmp_longvect.end() - 1)
                    tmp_stream << " ";
            }

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            tmp_longvect.clear();
            break;
        case DATA_ULONG_VECTOR:
            child->element()->get(tmp_ulongvect);

            tmp_stream.str("");

            for (tmp_ulongvect_i = tmp_ulongvect.begin(); tmp_ulongvect_i != tmp_ulongvect.end(); tmp_ulongvect_i++) {
                tmp_stream << (*tmp_ulongvect_i);
                if (tmp_ulongvect_i != tmp_ulongvect.end() - 1)
                    tmp_stream << " ";
            }

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            tmp_ulongvect.clear();
            break;
        case DATA_LONGLONG_VECTOR:
            child->element()->get(tmp_llongvect);

            tmp_stream.str("");

            for (tmp_llongvect_i = tmp_llongvect.begin(); tmp_llongvect_i != tmp_llongvect.end(); tmp_llongvect_i++) {
                tmp_stream << (*tmp_llongvect_i);
                if (tmp_llongvect_i != tmp_llongvect.end() - 1)
                    tmp_stream << " ";
            }

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            tmp_llongvect.clear();
            break;
        case DATA_FLOAT_VECTOR:
            child->element()->get(tmp_floatvect);

            tmp_stream.str("");

            for (tmp_floatvect_i = tmp_floatvect.begin(); tmp_floatvect_i != tmp_floatvect.end(); tmp_floatvect_i++) {
                tmp_stream << (*tmp_floatvect_i);
                if (tmp_floatvect_i != tmp_floatvect.end() - 1)
                    tmp_stream << " ";
            }

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            tmp_floatvect.clear();
            break;
        case DATA_DOUBLE_VECTOR:
            child->element()->get(tmp_doublevect);

            tmp_stream.str("");

            for (tmp_doublevect_i = tmp_doublevect.begin(); tmp_doublevect_i != tmp_doublevect.end(); tmp_doublevect_i++) {
                tmp_stream << (*tmp_doublevect_i);
                if (tmp_doublevect_i != tmp_doublevect.end() - 1)
                    tmp_stream << " ";
            }

            text = new TiXmlText(tmp_stream.str().c_str());
            element->LinkEndChild(text);
            tmp_doublevect.clear();
            break;
        }

        if (element) {
            elxml->LinkEndChild(element);

            if (child->numChildren()) {
                nodeToXML(child, element);
            }
        }
    }

    elem->rewind();
}

void DataTree::printXML() /* get serialized size + return node names header */
{
    TiXmlDocument doc;
    TiXmlDeclaration * decl = new TiXmlDeclaration("1.0", "", "");
    doc.LinkEndChild(decl);

    DataNode *root = rootNode();

    string rootName = root->getName();

    TiXmlElement *element = new TiXmlElement(rootName.empty() ? "root" : rootName.c_str());
    doc.LinkEndChild(element);

    if (!root->numChildren())
        doc.Print();

    nodeToXML(root, element);

    root->rewind();

    doc.Print();
}

long DataTree::getSerializedSize(DataElement &de_node_names, bool debug) /* get serialized size + return node names header */
{
    long total_size = 0;

    stack<DataNode *> dn_stack;
    vector<string> node_names;
    map<string, int, string_less> node_name_index_map;

    DataElement de_name_index;	// just used for sizing purposes
    DataElement de_num_children;

    de_name_index.set((int) 0);
    de_num_children.set((int) 0);

    int de_name_index_size = de_name_index.getSerializedSize();
    int de_num_children_size = de_num_children.getSerializedSize();

    dn_stack.push(&dn_root);

    while (!dn_stack.empty()) {
        int name_index;
        //					int num_children;

        /* build the name list */
        if (dn_stack.top()->getName().empty()) {
            name_index = 0; /* empty string */
        } else if (node_name_index_map[dn_stack.top()->getName().c_str()] == 0) {
            node_names.push_back(string(dn_stack.top()->getName()));
            name_index = node_names.size();
            node_name_index_map[dn_stack.top()->getName().c_str()] = name_index;
        } else {
            name_index = node_name_index_map[dn_stack.top()->getName().c_str()];
        }

        /* add on the size of the name index and number of children */
        total_size += de_name_index_size;
        total_size += de_num_children_size;
        total_size += dn_stack.top()->element()->getSerializedSize();

        /* debug output */
        if (debug) {
            for (unsigned int i = 0; i < dn_stack.size() - 1; i++)
                cout << "--";
            cout << (dn_stack.top()->getName().empty() ? "NULL" : dn_stack.top()->getName()) << "(" << dn_stack.top()->element()->getSerializedSize()
                    << ")";
            cout << " type: " << dn_stack.top()->element()->getDataType() << endl;
//cout << " index: " << name_index << endl;
        }
        /* end debug output */

        /* if it has children, traverse into them */
        if (dn_stack.top()->hasAnother()) {
            dn_stack.push(dn_stack.top()->getNext());
            dn_stack.top()->rewind();
        } else {
            /* no more children, back out until we have children, then add next child to the top */
            while (!dn_stack.empty()) {
                if (!dn_stack.top()->hasAnother()) {
                    dn_stack.top()->rewind();
                    dn_stack.pop();
                } else
                    break;
            }

            if (!dn_stack.empty()) {
                dn_stack.push(dn_stack.top()->getNext());
                dn_stack.top()->rewind();
            }
        }
    }

    /* set the header for use in serialization */
    de_node_names.set(node_names);

    total_size += de_node_names.getSerializedSize();

    return total_size;
}

void DataNode::rewindAll() {
    stack<DataNode *> dn_stack;
    
    /* start at the root */
    dn_stack.push(this);
    
    while (!dn_stack.empty()) {
        dn_stack.top()->rewind();
        
        /* if it has children, traverse into them */
        if (dn_stack.top()->hasAnother()) {
            dn_stack.push(dn_stack.top()->getNext());
            dn_stack.top()->rewind();
        } else {
            /* no more children, back out until we have children, then add next child to the top */
            while (!dn_stack.empty()) {
                if (!dn_stack.top()->hasAnother()) {
                    dn_stack.top()->rewind();
                    dn_stack.pop();
                } else
                    break;
            }
            
            if (!dn_stack.empty()) {
                dn_stack.push(dn_stack.top()->getNext());
                dn_stack.top()->rewind();
            }
        }
    }
    
}

void DataNode::findAll(const char *name_in, vector<DataNode *> &node_list_out) {
    stack<DataNode *> dn_stack;

    /* start at the root */
    dn_stack.push(this);

    if (string(getName()) == string(name_in))
        node_list_out.push_back(this);

    while (!dn_stack.empty()) {
        while (dn_stack.top()->hasAnother(name_in)) {
            node_list_out.push_back(dn_stack.top()->getNext(name_in));
        }

        /* if it has children, traverse into them */
        if (dn_stack.top()->hasAnother()) {
            dn_stack.push(dn_stack.top()->getNext());
            dn_stack.top()->rewind();
        } else {
            /* no more children, back out until we have children, then add next child to the top */
            while (!dn_stack.empty()) {
                if (!dn_stack.top()->hasAnother()) {
                    dn_stack.top()->rewind();
                    dn_stack.pop();
                } else
                    break;
            }

            if (!dn_stack.empty()) {
                dn_stack.push(dn_stack.top()->getNext());
                dn_stack.top()->rewind();
            }
        }
    }

}

long DataTree::getSerialized(char **ser_str, bool debug) {
    long data_ptr = 0;
    long data_size = 0;

    stack<DataNode *> dn_stack;
    vector<string> node_names;
    map<string, int, string_less> node_name_index_map;

    /* header of node names, grabbed from getserializedsize to avoid having to memmove() or realloc() */
    DataElement de_node_names;

    data_size = getSerializedSize(de_node_names, debug);

    *ser_str = (char *) malloc(data_size);

    char *data_out = *ser_str;

    /* name list header */
    char *de_node_names_serialized;
    long de_node_names_serialized_size;

    de_node_names.getSerialized(&de_node_names_serialized);
    de_node_names_serialized_size = de_node_names.getSerializedSize();

    /* copy the header and increase the pointer */
    memcpy(data_out, de_node_names_serialized, de_node_names_serialized_size);
    data_ptr += de_node_names_serialized_size;

    /* start at the root */
    dn_stack.push(&dn_root);

    while (!dn_stack.empty()) {
        int name_index;
        int num_children;

        DataElement de_name_index;
        DataElement de_num_children;

        char *de_name_index_serialized;
        char *de_num_children_serialized;
        char *element_serialized;

        long de_name_index_serialized_size;
        long de_num_children_serialized_size;
        long element_serialized_size;

        /* build the name list */
        if (dn_stack.top()->getName().empty()) {
            name_index = 0; /* empty string */
        } else if (node_name_index_map[dn_stack.top()->getName().c_str()] == 0) {
            node_names.push_back(string(dn_stack.top()->getName()));
            name_index = node_names.size();
            node_name_index_map[dn_stack.top()->getName().c_str()] = name_index;
        } else {
            name_index = node_name_index_map[dn_stack.top()->getName().c_str()];
        }

        num_children = dn_stack.top()->numChildren();

        de_name_index.set(name_index);
        de_num_children.set(num_children);

        de_name_index_serialized_size = de_name_index.getSerializedSize();
        de_num_children_serialized_size = de_num_children.getSerializedSize();
        element_serialized_size = dn_stack.top()->element()->getSerializedSize();

        de_name_index.getSerialized(&de_name_index_serialized);
        de_num_children.getSerialized(&de_num_children_serialized);
        dn_stack.top()->element()->getSerialized(&element_serialized);

        /* add on the name index and number of children */
        memcpy(data_out + data_ptr, de_name_index_serialized, de_name_index_serialized_size);
        data_ptr += de_name_index_serialized_size;

        memcpy(data_out + data_ptr, de_num_children_serialized, de_num_children_serialized_size);
        data_ptr += de_num_children_serialized_size;

        /* add on the data element */
        memcpy(data_out + data_ptr, element_serialized, element_serialized_size);
        data_ptr += element_serialized_size;

        delete[] de_name_index_serialized;
        delete[] de_num_children_serialized;
        delete[] element_serialized;

        /* if it has children, traverse into them */
        if (dn_stack.top()->hasAnother()) {
            dn_stack.push(dn_stack.top()->getNext());
            dn_stack.top()->rewind();
        } else {
            /* no more children, back out until we have children, then add next child to the top */
            while (!dn_stack.empty()) {
                if (!dn_stack.top()->hasAnother()) {
                    dn_stack.top()->rewind();
                    dn_stack.pop();
                } else
                    break;
            }

            if (!dn_stack.empty()) {
                dn_stack.push(dn_stack.top()->getNext());
                dn_stack.top()->rewind();
            }
        }
    }

    return data_size;
}

void DataTree::setSerialized(char *ser_str, bool debug) {
    long data_ptr = 0;
//	long data_size = 0;

    stack<DataNode *> dn_stack;
    stack<int> dn_childcount_stack;
    vector<string> node_names;

    DataElement de_node_names;

    de_node_names.setSerialized(ser_str);
    data_ptr += de_node_names.getSerializedSize();
    de_node_names.get(node_names);

    DataElement de_name_index;
    DataElement de_num_children;
    DataElement de_element;

    dn_stack.push(&dn_root);
    dn_childcount_stack.push(0); /* root (parent null) has no siblings */

    /* unserialization is a little less straightforward since we have to do a countdown of remaining children */
    while (!dn_stack.empty()) {
        int name_index = 0;
        int num_children = 0;

        /* pull the index of the name of this node */
        de_name_index.setSerialized(ser_str + data_ptr);
        data_ptr += de_name_index.getSerializedSize();

        /* pull the number of children this node has */
        de_num_children.setSerialized(ser_str + data_ptr);
        data_ptr += de_num_children.getSerializedSize();

        /* get values from the temp dataelements */
        de_name_index.get(name_index);
        de_num_children.get(num_children);

        /* pull the node's element */
        dn_stack.top()->element()->setSerialized(ser_str + data_ptr);
        data_ptr += dn_stack.top()->element()->getSerializedSize();

        /* debug output */
        if (debug) {
            for (unsigned int i = 0; i < dn_stack.size() - 1; i++)
                cout << "--";
            cout << (name_index ? node_names[name_index - 1] : "NULL") << "(" << dn_stack.top()->element()->getSerializedSize() << ")";
            cout << " index: " << name_index << endl;
        }
        /* end debug output */

        /* name index >= 1 means it has a name */
        if (name_index >= 1) {
            dn_stack.top()->setName(node_names[name_index - 1].c_str());

        } else /* name is nil */
        {
            dn_stack.top()->setName("");
        }

        if (num_children) /* Has children, create first child and push it to the top */
        {
            dn_childcount_stack.push(num_children); /* push the child count onto the stack */

            de_name_index.setSerialized(ser_str + data_ptr); /* peek at the new child name but don't increment pointer */
            de_name_index.get(name_index);
            /* add this child onto the top of the stack */
            dn_stack.push(dn_stack.top()->newChild((name_index ? node_names[name_index - 1] : string("")).c_str()));
            dn_childcount_stack.top()--; /* decrement to count the new child */
            }
        else /* No children, move on to the next sibling */
        {
            if (dn_childcount_stack.top()) /* any siblings remaining? */
            {
                de_name_index.setSerialized(ser_str + data_ptr); /* peek at the new child name but don't increment pointer */
                de_name_index.get(name_index);

                dn_stack.pop();
                dn_stack.push(dn_stack.top()->newChild((name_index ? node_names[name_index - 1] : string("")).c_str())); /* create the next sibling and throw it on the stack */
                dn_childcount_stack.top()--; /* decrement to count the new sibling */
                }
            else /* This is the last sibling, move up the stack and find the next */
            {
                while (!dn_stack.empty()) /* move up the stack until we find the next sibling */
                {
                    if (dn_childcount_stack.top()) {
                        de_name_index.setSerialized(ser_str + data_ptr); /* peek at the new child name but don't increment pointer */
                        de_name_index.get(name_index);

                        dn_stack.pop();
                        dn_stack.push(dn_stack.top()->newChild((name_index ? node_names[name_index - 1] : string("")).c_str())); /* throw it on the stack */
                        dn_childcount_stack.top()--; /* count it */
                        break
;                    }
                    else
                    {
                        dn_childcount_stack.pop();
                        dn_stack.pop(); /* if no more siblings found the stack will empty naturally */
                    }
                }
            }
        }
    }
}

bool DataTree::LoadFromFileXML(const std::string& filename, DT_FloatingPointPolicy fpp) {
    TiXmlDocument doc(filename.c_str());

    bool loadOkay = doc.LoadFile();

    if (!loadOkay) {
        std::cout << "LoadFromFileXML[error loading]: " << filename << std::endl;
        return false;
    }

    TiXmlNode *xml_root_node = doc.RootElement();

    if (!xml_root_node) {
        std::cout << "LoadFromFileXML[error no root]: " << filename << std::endl;
        return false;
    }

    rootNode()->setName(xml_root_node->ToElement()->Value());

    setFromXML(rootNode(), xml_root_node, true, fpp);

    return true;
}

bool DataTree::SaveToFileXML(const std::string& filename) {
    TiXmlDocument doc;
    TiXmlDeclaration * decl = new TiXmlDeclaration("1.0", "", "");
    doc.LinkEndChild(decl);

    string rootName = rootNode()->getName();

    TiXmlElement *element = new TiXmlElement(rootName.empty() ? "root" : rootName.c_str());

    doc.LinkEndChild(element);

    nodeToXML(rootNode(), element);

    doc.SaveFile(filename.c_str());

    return true;
}

/*
 bool DataTree::SaveToFile(const std::string& filename)
 {
 char *serialized;

 long dataSize = getSerialized(&serialized);

 std::ofstream fout(filename.c_str(), ios::binary);

 fout.write(serialized, dataSize);

 fout << flush;
 fout.close();

 delete serialized;

 return true;
 }

 bool DataTree::LoadFromFile(const std::string& filename)
 {
 char *serialized;
 long dataSize;

 ifstream fin(filename.c_str(), ios::binary);

 fin.seekg (0, ios::end);
 dataSize = fin.tellg();
 fin.seekg (0, ios::beg);

 serialized = new char[dataSize];
 fin.read(serialized,dataSize);

 fin.close();

 setSerialized(serialized);

 delete serialized;

 return true;
 }
 */

bool DataTree::SaveToFile(const std::string& filename, bool compress, int /* compress_level */) {
    long dataSize, compressedSize = 0, headerSize;
    char *serialized = nullptr, *hdr_serialized = nullptr, *compressed = nullptr;
    DataTree dtHeader;

    dataSize = getSerialized(&serialized);

#if USE_FASTLZ
    if (compress) {
        compressed = new char[(int) ceil(dataSize * 1.5)];

        compressedSize = fastlz_compress_level(compress_level, serialized, dataSize, compressed);

        compressed = (char *) realloc(compressed, compressedSize);

        delete serialized;
    }
#else
    if (compress) {
        std::cout << "Can't compress, FASTLZ disabled";
        compress = false;
    }
#endif
    DataNode *header = dtHeader.rootNode();

    *header->newChild("version") = 1.0f;
    *header->newChild("compression") = string(compress ? "FastLZ" : "none");
    *header->newChild("uncompressed_size") = dataSize;

    headerSize = dtHeader.getSerialized(&hdr_serialized);

    std::ofstream fout(filename.c_str(), ios::binary);

    fout.write((char *) &headerSize, sizeof(long));
    fout.write((char *) &(compress ? compressedSize : dataSize), sizeof(long));

    fout.write(hdr_serialized, headerSize);
    fout.write(compress ? compressed : serialized, compress ? compressedSize : dataSize);

    fout << flush;
    fout.close();

    free(hdr_serialized);

    if (!compress) {
        free(serialized);
    } else {
        delete[] compressed;
    }

    return true;
}

bool DataTree::LoadFromFile(const std::string& filename) {
#if USE_FASTLZ
    char *compressed;
#endif
    char *serialized, *hdr_serialized;
    long dataSize, headerSize, compressedSize;

    ifstream fin(filename.c_str(), ios::binary);

    fin.read((char *) &headerSize, sizeof(long));
    fin.read((char *) &compressedSize, sizeof(long));

    hdr_serialized = new char[headerSize];
    fin.read(hdr_serialized, headerSize);

    DataTree dtHeader;
    dtHeader.setSerialized(hdr_serialized);
    DataNode *header = dtHeader.rootNode();

    string compressionType(*header->getNext("compression"));
    dataSize = *header->getNext("uncompressed_size");

#if USE_FASTLZ
    bool uncompress = false;
    if (compressionType == "FastLZ") {
        uncompress = true;
    }

    if (uncompress) {
        compressed = new char[compressedSize];
        fin.read(compressed, compressedSize);

        serialized = new char[dataSize];
        fastlz_decompress(compressed, compressedSize, serialized, dataSize);

        delete[] compressed;
    } else {
        serialized = new char[dataSize];
        fin.read(serialized, dataSize);
    }
#else
    if (compressionType == "FastLZ") {
        std::cout << "DataTree Unable to load FastLZ compressed file -- FastLZ is disabled";
        return false;
    }

    serialized = new char[dataSize];
    fin.read(serialized, dataSize);
#endif

    fin.close();

    setSerialized(serialized);

    delete[] serialized;
    delete[] hdr_serialized;

    return true;
}

