/* -*-c++-*- OpenSceneGraph - Copyright (C) 2012 Cedric Pinson  */
#ifndef WRITE_VISITOR_H
#define WRITE_VISITOR_H

#include <osg/Image>
#include <osg/Notify>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/GL>
#include <osg/Version>
#include <osg/Endian>
#include <osg/Projection>
#include <osg/MatrixTransform>
#include <osg/PagedLOD>
#include <osg/PositionAttitudeTransform>
#include <osgAnimation/BasicAnimationManager>
#include <osgAnimation/Skeleton>
#include <osg/LightSource>
#include <osg/CullFace>
#include <osg/Material>
#include <osg/BlendColor>
#include <osg/BlendFunc>
#include <osg/ValueObject>
#include <osg/Array>
#include <osgDB/FileNameUtils>

#include <osgAnimation/RigGeometry>
#include <osgAnimation/MorphGeometry>
#include <osgAnimation/Bone>
#include <osgAnimation/UpdateBone>

#include <osgSim/ShapeAttribute>

#include <osgText/Text>

#include <fstream>
#include <sstream>
#include <vector>
#include <map>
#include <string>
#include <algorithm>
#include <sstream>

#include "JSON_Objects"
#include "Animation"
#include "json_stream"


#define WRITER_VERSION 9


class WriteVisitor;

osg::Array* getTangentSpaceArray(osg::Geometry& geometry);
void getStringifiedUserValue(osg::Object* o, std::string& name, std::string& value);
template<typename T>
bool getStringifiedUserValue(osg::Object* o, std::string& name, std::string& value);


class WriteVisitor : public osg::NodeVisitor
{
public:
    typedef std::vector<osg::ref_ptr<osg::StateSet> > StateSetStack;
    typedef std::pair<std::string, std::string> KeyValue;
    typedef std::map<osg::ref_ptr<osg::Object>, osg::ref_ptr<JSONObject> > OsgObjectToJSONObject;

    OsgObjectToJSONObject _maps;
    std::vector<osg::ref_ptr<JSONObject> > _parents;
    osg::ref_ptr<JSONObject> _root;
    StateSetStack _stateset;
    std::string _baseName;
    std::string _baseLodURL;
    bool _useExternalBinaryArray;
    bool _mergeAllBinaryFiles;
    bool _inlineImages;
    int _maxTextureDimension;
    bool _varint;
    std::map<KeyValue, std::string> _specificBuffers;
    std::map<std::string, std::ofstream*> _buffers;

    JSONObject* getJSON(osg::Object* object) const {
        OsgObjectToJSONObject::const_iterator lookup = _maps.find(object);
        if(lookup != _maps.end()) {
            return lookup->second->getShadowObject();
        }
        return 0;
    }

    void setJSON(osg::Object* object, JSONObject* json) {
        if(json) {
            _maps[object] = json;
        }
    }

    std::ofstream& getBufferFile(const std::string& name) {
        if(_buffers.find(name) == _buffers.end()) {
            _buffers[name] = new std::ofstream(name.c_str(), std::ios::binary);
        }
        return *_buffers[name];
    }

    WriteVisitor() :
        osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
        _useExternalBinaryArray(false),
        _mergeAllBinaryFiles(false),
        _inlineImages(false),
        _maxTextureDimension(0),
        _varint(false)
    {}

    ~WriteVisitor() {
        for(std::map<std::string, std::ofstream*>::iterator buffer = _buffers.begin() ;
            buffer != _buffers.end() ; ++ buffer) {
            delete buffer->second;
        }
    }

    std::string getBinaryFilename(KeyValue flag=KeyValue()) const {
        std::string suffix,
                    prefix(_baseName);
        std::map<KeyValue, std::string>::const_iterator it_buffer = _specificBuffers.find(flag);
        if(it_buffer != _specificBuffers.end()) {
            if(osgDB::isAbsolutePath(it_buffer->second)) {
                // if output is explicit, do not change it
                return it_buffer->second;
            }
            else {
                suffix = "_" + it_buffer->second;
            }
        }
        return prefix + suffix + ".bin";
    }

    void closeBuffers() {
        for(std::map<std::string, std::ofstream*>::iterator buffer = _buffers.begin() ;
            buffer != _buffers.end() ; ++ buffer) {
            buffer->second->close();
        }
    }

    unsigned int getBuffersSize() const {
        unsigned int size = 0;
        for(std::map<std::string, std::ofstream*>::const_iterator buffer = _buffers.begin() ;
            buffer != _buffers.end() ; ++ buffer) {
            size += buffer->second->tellp();
        }
        return size;
    }

    void write(json_stream& str) {
        osg::ref_ptr<JSONObject> o = new JSONObject();
        o->getMaps()["Version"] = new JSONValue<int>(WRITER_VERSION);
        o->getMaps()["Generator"] = new JSONValue<std::string>("OpenSceneGraph " + std::string(osgGetVersion()) );
        o->getMaps()["osg.Node"] = _root.get();
        o->write(str, *this);
        if (_mergeAllBinaryFiles) {
            closeBuffers();
            unsigned int size = getBuffersSize();
            osg::notify(osg::NOTICE) << "Use a merged binary file ";
            if (size/1024.0 < 1.0) {
                osg::notify(osg::NOTICE) << size << " bytes" << std::endl;
            } else if (size/(1024.0*1024.0) < 1.0) {
                osg::notify(osg::NOTICE) << size/1024.0 << " kb" << std::endl;
            } else {
                osg::notify(osg::NOTICE) << size/(1024.0*1024.0) << " mb" << std::endl;
            }
        }
    }

    void error() {
        throw "Error occur";
    }

    void setBufferName(JSONObject *json, osg::Object* parent=0, osg::Object* object=0) const {
        if(!_mergeAllBinaryFiles || _specificBuffers.empty())
            return;

        // try to fetch buffer name for object
        std::string bufferName = getBufferName(object);
        std::string defaultBufferName = getBinaryFilename();
        std::string jsonBufferName = json->getBufferName();

        if(bufferName == defaultBufferName) {
            // in case none is set, fallback to parent buffer name
            bufferName = getBufferName(parent);
        }
        // if the buffer is shared we will always favor dumping it in the default
        // buffer and otherwise we keep the first buffer name set.
        if(!jsonBufferName.empty()) {
            if(jsonBufferName != defaultBufferName && bufferName == defaultBufferName) {
                json->setBufferName(defaultBufferName);
            }
        }
        else {
            json->setBufferName(bufferName);
        }
    }

    std::string getBufferName(osg::Object* object) const {
        KeyValue flag;
        if(object && object->getUserDataContainer() && object->getUserDataContainer()->getNumUserObjects()) {
            for(std::map<KeyValue, std::string>::const_iterator it_flag = _specificBuffers.begin() ; it_flag != _specificBuffers.end() ; ++ it_flag) {
                std::string key = it_flag->first.first,
                            value = it_flag->first.second;

                // only looking for existence of user value
                if(value.empty()) {
                    if(object->getUserDataContainer()->getUserObject(key)) {
                        flag = it_flag->first;
                        break;
                    }
                }
                else {
                    std::set<std::string> uservalues;

                    bool boolValue;
                    int numberValue;
                    unsigned int uNumberValue;
                    std::string stringValue;

                    if(object->getUserValue(key, boolValue)) {
                        std::ostringstream oss;
                        if(boolValue) {
                            uservalues.insert("1");
                            uservalues.insert("true");
                        }
                        else {
                            uservalues.insert("0");
                            uservalues.insert("false");
                        }
                    }

                    if(object->getUserValue(key, numberValue)) {
                        std::ostringstream oss;
                        oss << numberValue;
                        uservalues.insert(oss.str());
                    }

                    if(object->getUserValue(key, uNumberValue)) {
                        std::ostringstream oss;
                        oss << uNumberValue;
                        uservalues.insert(oss.str());
                    }

                    if(object->getUserValue(key, stringValue)) {
                        uservalues.insert(stringValue);
                    }

                    if(uservalues.find(value) != uservalues.end()) {
                        flag = it_flag->first;
                        break;
                    }
                }
            }
        }
        return getBinaryFilename(flag);
    }

    void translateObject(JSONObject* json, osg::Object* osg);
    JSONObject* createJSONOsgSimUserData(osgSim::ShapeAttributeList*);
    JSONObject* createJSONUserDataContainer(osg::UserDataContainer*);

    JSONObject* createJSONPagedLOD(osg::PagedLOD* plod);
    JSONObject* createJSONStateSet(osg::StateSet* ss);
    JSONObject* createJSONTexture(osg::Texture* sa);
    JSONObject* createJSONText(osgText::Text* text);
    JSONObject* createJSONMaterial(osg::Material* sa);
    JSONObject* createJSONLight(osg::Light* sa);
    JSONObject* createJSONCullFace(osg::CullFace* sa);
    JSONObject* createJSONBlendColor(osg::BlendColor* sa);
    JSONObject* createJSONBlendFunc(osg::BlendFunc* sa);

    JSONObject* createJSONBufferArray(osg::Array* array, osg::Object* parent = 0);
    JSONObject* createJSONDrawElements(osg::DrawArrays* drawArray, osg::Object* parent = 0);

    JSONObject* createJSONDrawElementsUInt(osg::DrawElementsUInt* de, osg::Object* parent = 0);
    JSONObject* createJSONDrawElementsUShort(osg::DrawElementsUShort* de, osg::Object* parent = 0);
    JSONObject* createJSONDrawElementsUByte(osg::DrawElementsUByte* de, osg::Object* parent = 0);

    JSONObject* createJSONDrawArray(osg::DrawArrays* drawArray, osg::Object* parent = 0);
    JSONObject* createJSONDrawArrayLengths(osg::DrawArrayLengths* drawArray, osg::Object* parent = 0);

    JSONObject* createJSONGeometry(osg::Geometry* geometry, osg::Object* parent=0);
    JSONObject* createJSONRigGeometry(osgAnimation::RigGeometry* rigGeometry);
    JSONObject* createJSONMorphGeometry(osgAnimation::MorphGeometry* morphGeom, osg::Object* parent=0);

    JSONObject* getParent() {
        if (_parents.empty()) {
            _root = new JSONObject;
            _parents.push_back(_root.get());
        }
        return _parents.back().get();
    }

    void initJsonObjectFromNode(osg::Node& node, JSONObject& json) {
        translateObject(&json, &node);
    }


    void createJSONStateSet(JSONObject* json, osg::StateSet* ss) {
        JSONObject* json_stateset = createJSONStateSet(ss);
        if (json_stateset) {
            JSONObject* obj = new JSONObject;
            obj->getMaps()["osg.StateSet"] = json_stateset;
            json->getMaps()["StateSet"] = obj;

        }
    }
    void createJSONStateSet(osg::Node& node, JSONObject* json) {
        if (node.getStateSet()) {
            createJSONStateSet(json, node.getStateSet());
        }
    }

    void applyCallback(osg::Node& node, JSONObject* in_json) {
        osg::ref_ptr<JSONArray> updateCallbacks = new JSONArray;
        osg::ref_ptr<osg::Callback> nc = node.getUpdateCallback();
        while (nc) {
            if(osgAnimation::BasicAnimationManager* am = dynamic_cast<osgAnimation::BasicAnimationManager*>(nc.get())) {
                osg::ref_ptr<JSONArray> array = new JSONArray;
                osg::ref_ptr<JSONObject> bam = new JSONObject;
                translateObject(bam.get(), am);
                bam->getMaps()["Animations"] = array;

                osg::ref_ptr<JSONObject> nodeCallbackObject = new JSONObject;
                nodeCallbackObject->getMaps()["osgAnimation.BasicAnimationManager"] = bam;
                updateCallbacks->getArray().push_back(nodeCallbackObject);

                for ( unsigned int i = 0; i < am->getAnimationList().size(); i++) {
                    osgAnimation::Animation* animation = am->getAnimationList()[i].get();
                    osg::ref_ptr<JSONObject> jsonAnim = createJSONAnimation(animation, this);
                    if (jsonAnim) {
                        translateObject(jsonAnim.get(), animation);
                        osg::ref_ptr<JSONObject> obj = new JSONObject;
                        obj->getMaps()["osgAnimation.Animation"] = jsonAnim;
                        array->getArray().push_back(obj);
                    }
                }
            }
            else if(osgAnimation::UpdateBone* upBone = dynamic_cast<osgAnimation::UpdateBone*>(nc.get())) {
                osg::ref_ptr<JSONObject> jsonCallback = createJSONUpdateMatrixTransform(*upBone, this);
                if (jsonCallback.valid()) {
                    osg::ref_ptr<JSONObject> jsonObject = new JSONObject;
                    jsonObject->getMaps()["osgAnimation.UpdateBone"] = jsonCallback;
                    updateCallbacks->getArray().push_back(jsonObject);
                }
            }
            else if(osgAnimation::UpdateMatrixTransform* updateMT = dynamic_cast<osgAnimation::UpdateMatrixTransform*>(nc.get())) {
                osg::ref_ptr<JSONObject> jsonCallback = createJSONUpdateMatrixTransform(*updateMT, this);
                if (jsonCallback.valid()) {
                    osg::ref_ptr<JSONObject> jsonObject = new JSONObject;
                    jsonObject->getMaps()["osgAnimation.UpdateMatrixTransform"] = jsonCallback;
                    updateCallbacks->getArray().push_back(jsonObject);
                }
            }
            else if(dynamic_cast<osgAnimation::Skeleton::UpdateSkeleton*>(nc.get())) {
                osg::ref_ptr<JSONObject> json = new JSONNode;
                osg::ref_ptr<JSONObject> jsonObject = new JSONObject;
                _maps[&node] = json;
                jsonObject->getMaps()["osgAnimation.UpdateSkeleton"] = json;
                updateCallbacks->getArray().push_back(jsonObject);
            }
            else if(osgAnimation::UpdateMorph* updateMorph = dynamic_cast<osgAnimation::UpdateMorph*>(nc.get())) {
                osg::ref_ptr<JSONObject> json = new JSONNode;
                osg::ref_ptr<JSONObject> jsonObject = new JSONObject;
                osg::ref_ptr<JSONObject> jsonTargetName = new JSONObject;
                json->getMaps()["Name"] = new JSONValue<std::string>(updateMorph->getName());

                unsigned int numTarget = updateMorph->getNumTarget();
                for(unsigned int i = 0; i < numTarget; ++i) {
                    std::ostringstream oss;
                    oss << i;
                    jsonTargetName->getMaps()[ oss.str() ] = new JSONValue<std::string>(updateMorph->getTargetName(i));
                }
                json->getMaps()["TargetMap"] = jsonTargetName;
                json->getMaps()["TargetMap"] = jsonTargetName;

                _maps[&node] = json;
                jsonObject->getMaps()["osgAnimation.UpdateMorph"] = json;
                updateCallbacks->getArray().push_back(jsonObject);
            }
            nc = nc->getNestedCallback();
        }

        if (!updateCallbacks->getArray().empty()) {
            in_json->getMaps()["UpdateCallbacks"] = updateCallbacks;
        }
    }

    void apply(osg::Drawable& drawable) {
        if(osgAnimation::RigGeometry * rigGeometry = dynamic_cast<osgAnimation::RigGeometry*>(&drawable)) {
            JSONObject* json = createJSONRigGeometry(rigGeometry);
            translateObject(json, rigGeometry);
            JSONObject* parent = getParent();
            parent->addChild("osgAnimation.RigGeometry", json);
        }
        else if(osgAnimation::MorphGeometry * morphGeometry = dynamic_cast<osgAnimation::MorphGeometry*>(&drawable)) {
            JSONObject* json = createJSONMorphGeometry(morphGeometry);
            JSONObject* parent = getParent();
            parent->addChild("osgAnimation.MorphGeometry", json);
        }
        else if(osg::Geometry* geometry = dynamic_cast<osg::Geometry*>(&drawable)) {
            JSONObject* json = createJSONGeometry(geometry);
            JSONObject* parent = getParent();
            parent->addChild("osg.Geometry", json);
        }
        else if(osgText::Text* text = dynamic_cast<osgText::Text*>(&drawable)) {
            JSONObject* json = createJSONText(text);
            JSONObject* parent = getParent();
            parent->addChild("osgText.Text", json);
        }
    }

    void apply(osg::Geode& node) {

        JSONObject* parent = getParent();
        if (_maps.find(&node) != _maps.end()) {
            parent->addChild("osg.Node", _maps[&node]->getShadowObject());
            return;
        }

        osg::ref_ptr<JSONObject> json = new JSONNode;
        _maps[&node] = json;

        applyCallback(node, json.get());
        createJSONStateSet(node, json.get());

        parent->addChild("osg.Node", json.get());
        initJsonObjectFromNode(node, *json);
        _parents.push_back(json);
        for (unsigned int i = 0; i < node.getNumDrawables(); ++i) {
            if (node.getDrawable(i))
                apply(*node.getDrawable(i));
        }
        _parents.pop_back();
    }

    void apply(osg::Group& node) {

        JSONObject* parent = getParent();
        if (_maps.find(&node) != _maps.end()) {
            parent->addChild("osg.Node", _maps[&node]->getShadowObject());
            return;
        }

        osg::ref_ptr<JSONObject> json = new JSONNode;
        _maps[&node] = json;
        parent->addChild("osg.Node", json.get());

        applyCallback(node, json.get());
        createJSONStateSet(node, json.get());

        initJsonObjectFromNode(node, *json);

        _parents.push_back(json);
        traverse(node);
        _parents.pop_back();
    }

    void apply(osg::PagedLOD& node)
    {
        JSONObject* parent = getParent();
        if (_maps.find(&node) != _maps.end()) {
            parent->addChild("osg.PagedLOD", _maps[&node]->getShadowObject());
            return;
        }

        osg::ref_ptr<JSONObject> json = createJSONPagedLOD(&node);
        json->addUniqueID();
        _maps[&node] = json;
        parent->addChild("osg.PagedLOD", json.get());


        applyCallback(node, json.get());


        createJSONStateSet(node, json.get());

        initJsonObjectFromNode(node, *json);
        _parents.push_back(json);
        traverse(node);
        _parents.pop_back();
    }

    void apply(osg::LightSource& node) {

        JSONObject* parent = getParent();
        if (_maps.find(&node) != _maps.end()) {
            parent->addChild("osg.LightSource", _maps[&node]->getShadowObject());
            return;
        }

        osg::ref_ptr<JSONObject> json = new JSONNode;
        _maps[&node] = json;

        applyCallback(node, json.get());
        createJSONStateSet(node, json.get());

        parent->addChild("osg.LightSource", json.get());

        initJsonObjectFromNode(node, *json);

        if (node.getLight()) {
            JSONObject* obj = new JSONObject;
            obj->getMaps()["osg.Light"] = createJSONLight(node.getLight());
            json->getMaps()["Light"] = obj;
        }

        _parents.push_back(json);
        traverse(node);
        _parents.pop_back();
    }

    void apply(osg::Projection& node) {
        JSONObject* parent = getParent();
        if (_maps.find(&node) != _maps.end()) {
            parent->addChild("osg.Projection", _maps[&node]->getShadowObject());
            return;
        }

        osg::ref_ptr<JSONObject> json = new JSONNode;
        _maps[&node] = json;

        applyCallback(node, json.get());
        createJSONStateSet(node, json.get());

        parent->addChild("osg.Projection", json.get());

        initJsonObjectFromNode(node, *json);
        json->getMaps()["Matrix"] = new JSONMatrix(node.getMatrix());
        _parents.push_back(json);
        traverse(node);
        _parents.pop_back();
    }

    void applyCommonMatrixTransform(const char * jsClassName, osg::ref_ptr<JSONObject> &json, osg::MatrixTransform &node, JSONObject* parent) {
        json->addUniqueID();
        _maps[&node] = json;

        applyCallback(node, json.get());
        createJSONStateSet(node, json.get());

        parent->addChild(jsClassName, json.get());

        initJsonObjectFromNode(node, *json);
        json->getMaps()["Matrix"] = new JSONMatrix(node.getMatrix());
    }

    void apply(osgText::Text& node) {
        JSONObject* parent = getParent();
        if (_maps.find(&node) != _maps.end()) {
            parent->addChild("osgText.Text", _maps[&node]->getShadowObject());
            return;
        }

        osg::ref_ptr<JSONObject> json = createJSONText(&node);
        json->addUniqueID();
        _maps[&node] = json.get();
        parent->addChild("osgText.Text", json.get());
        applyCallback(node, json.get());
        createJSONStateSet(node, json.get());
        initJsonObjectFromNode(node, *json);
        _parents.push_back(json);
        traverse(node);
        _parents.pop_back();
    }

    void apply(osg::MatrixTransform& node) {
        if (dynamic_cast<osgAnimation::Skeleton*>(&node)) {
           apply(static_cast<osgAnimation::Skeleton&>(node));
           return;
        }
        if (dynamic_cast<osgAnimation::Bone*>(&node)) {
            apply(static_cast<osgAnimation::Bone&>(node));
            return;
        }

        JSONObject* parent = getParent();
        if (_maps.find(&node) != _maps.end()) {
            parent->addChild("osg.MatrixTransform", _maps[&node]->getShadowObject());
            return;
        }
        osg::ref_ptr<JSONObject> json = new JSONNode;
        _maps[&node] = json;

        applyCallback(node, json.get());
        createJSONStateSet(node, json.get());

        parent->addChild("osg.MatrixTransform", json.get());

        initJsonObjectFromNode(node, *json);
        json->getMaps()["Matrix"] = new JSONMatrix(node.getMatrix());

        _parents.push_back(json);
        traverse(node);
        _parents.pop_back();

    }

    void apply(osgAnimation::Skeleton& node) {
        JSONObject* parent = getParent();
        if (_maps.find(&node) != _maps.end()) {
            parent->addChild("osgAnimation.Skeleton", _maps[&node]->getShadowObject());
            return;
        }
        osg::ref_ptr<JSONObject> json = new JSONNode;
        applyCommonMatrixTransform("osgAnimation.Skeleton", json, node, parent);
        _parents.push_back(json);
        traverse(node);
        _parents.pop_back();
    }

    void apply(osgAnimation::Bone &node) {
        JSONObject* parent = getParent();
        if (_maps.find(&node) != _maps.end()) {
            parent->addChild("osgAnimation.Bone", _maps[&node]->getShadowObject());
            return;
        }

        osg::ref_ptr<JSONObject> json = new JSONNode;

        osg::Vec3 min(0,0,0), max(0,0,0);
        osg::ref_ptr<JSONObject> bboxData = new JSONObject;
        bool hasBoundindBox = ( node.getUserValue("AABBonBone_min", min) && node.getUserValue("AABBonBone_max", max) );

        if (hasBoundindBox) {
            unsigned int idxmin = node.getUserDataContainer()->getUserObjectIndex("AABBonBone_min");
            node.getUserDataContainer()->removeUserObject(idxmin);
            unsigned int idxmax = node.getUserDataContainer()->getUserObjectIndex("AABBonBone_max");
            node.getUserDataContainer()->removeUserObject(idxmax);

            if(node.getUserDataContainer() && node.getUserDataContainer()->getNumUserObjects() == 0)
                node.setUserDataContainer(NULL);

            bboxData->getMaps()["min"] = new JSONVec3Array(min);
            bboxData->getMaps()["max"] = new JSONVec3Array(max);

            json->getMaps()["BoundingBox"] = bboxData;
        }

        json->getMaps()["InvBindMatrixInSkeletonSpace"] = new JSONMatrix(node.getInvBindMatrixInSkeletonSpace());

        applyCommonMatrixTransform("osgAnimation.Bone", json, node, parent);

        _parents.push_back(json);
        traverse(node);
        _parents.pop_back();
    }

    void apply(osg::PositionAttitudeTransform& node)
    {
        JSONObject* parent = getParent();
        if (_maps.find(&node) != _maps.end()) {
            parent->addChild("osg.MatrixTransform", _maps[&node]->getShadowObject());
            return;
        }

        osg::ref_ptr<JSONObject> json = new JSONNode;
        _maps[&node] = json;

        applyCallback(node, json.get());
        createJSONStateSet(node, json.get());

        parent->addChild("osg.MatrixTransform", json.get());

        initJsonObjectFromNode(node, *json);
        osg::Matrix matrix = osg::Matrix::identity();
        node.computeLocalToWorldMatrix(matrix,0);
        json->getMaps()["Matrix"] = new JSONMatrix(matrix);
        _parents.push_back(json);
        traverse(node);
        _parents.pop_back();
    }

    std::string getBaseName() const { return _baseName; }
    bool getInlineImages() const { return _inlineImages; }
    int getMaxTextureDimension() const { return _maxTextureDimension; }

    void setBaseName(const std::string& basename) { _baseName = basename; }
    void useExternalBinaryArray(bool use) { _useExternalBinaryArray = use; }
    void mergeAllBinaryFiles(bool use) { _mergeAllBinaryFiles = use; }
    void setInlineImages(bool use) { _inlineImages = use; }
    void setVarint(bool use) { _varint = use; }
    void setMaxTextureDimension(int use) { _maxTextureDimension = use; }
    void addSpecificBuffer(const std::string& bufferFlag) {
        if(bufferFlag.empty()) {
            return;
        }

        std::string key, value, buffer;
        size_t equal = bufferFlag.find("="),
               colon = bufferFlag.find(":");

        key = bufferFlag.substr(0, std::min(equal, colon));
        if(equal != std::string::npos) {
            if(colon == std::string::npos) {
                value = bufferFlag.substr(equal + 1, std::string::npos);
            }
            else {
                value = bufferFlag.substr(equal + 1, colon - equal - 1);
            }
        }

        if(colon != std::string::npos) {
            buffer = bufferFlag.substr(colon + 1, std::string::npos);
        }
        else {
            buffer = key;
        }

        // buffer name should be lowercase
        std::transform(buffer.begin(), buffer.end(), buffer.begin(), ::tolower);
        _specificBuffers[KeyValue(key, value)] = buffer;
    }
    void setBaseLodURL(const std::string& baseLodURL) { _baseLodURL = baseLodURL; }
};

#endif
