/*  -*-c++-*-
 *  Copyright (C) 2010 Cedric Pinson <cedric.pinson@plopbyte.net>
 */

#ifndef JSON_OBJECT
#define JSON_OBJECT

#include <osg/Matrix>
#include <osg/Array>
#include <osg/Light>
#include <osg/Notify>
#include <osg/PrimitiveSet>
#include <osg/Types>

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

#include "json_stream"

class WriteVisitor;

struct JSONObjectBase : public osg::Referenced
{
    static int level;
    static std::string indent();
    virtual void write(json_stream& /*str*/, WriteVisitor& /*visitor*/) {}
};

template <class T> struct JSONValue;

struct JSONArray;
struct JSONObject : public JSONObjectBase
{
    typedef std::map<std::string, osg::ref_ptr<JSONObject> > JSONMap;
    typedef std::vector<std::string> OrderList;

    static unsigned int uniqueID;
    std::string _bufferName;
    JSONMap _maps;

    JSONObject() {}
    JSONObject(const unsigned int id, const std::string& bufferName = "");

    void addUniqueID();
    unsigned int getUniqueID() const;
    JSONObject* getShadowObject() { return new JSONObject(getUniqueID(), _bufferName); }

    virtual void setBufferName(const std::string& name) { _bufferName = name; }
    std::string getBufferName() { return _bufferName; }

    JSONMap& getMaps() { return _maps; }

    void writeOrder(json_stream& str, const OrderList& order, WriteVisitor& visitor);
    virtual void write(json_stream& str, WriteVisitor& visitor);
    void addChild(const std::string& type, JSONObject* child);
    virtual JSONArray* asArray() { return 0; }
    template<class T> JSONValue<T>* asValue() {
        return dynamic_cast<JSONValue<T> * > ( this);
    }

    bool isVarintableIntegerBuffer(osg::Array const*) const;
    void encodeArrayAsVarintBuffer(osg::Array const*, std::vector<uint8_t>&) const;
    template<typename T>
    void dumpVarintVector(std::vector<uint8_t>&, T const*, bool) const;
    template<typename T>
    void dumpVarintValue(std::vector<uint8_t>&, T const*, bool) const;
    std::vector<uint8_t> varintEncoding(unsigned int value) const;

    // see https://developers.google.com/protocol-buffers/docs/encoding?hl=fr&csw=1#types
    inline unsigned int toVarintUnsigned(int const v) const
    { return (v << 1) ^ (v >> ((sizeof(v) << 3) - 1)); }

};

JSONObject* createLight(osg::Light* light);

struct JSONObjectWithUniqueID : public JSONObject
{
    JSONObjectWithUniqueID()
    { addUniqueID(); }
};

typedef JSONObjectWithUniqueID JSONNode;
typedef JSONObjectWithUniqueID JSONStateSet;
typedef JSONObjectWithUniqueID JSONMaterial;
typedef JSONObjectWithUniqueID JSONLight;

struct JSONArray : public JSONObject
{
    typedef std::vector<osg::ref_ptr<JSONObject> > JSONList;
    JSONList _array;
    JSONArray() {}
    virtual void write(json_stream& str, WriteVisitor& visitor);
    JSONList& getArray() { return _array; }
    JSONArray* asArray() { return this; }
};


struct JSONVec3Array : public JSONArray
{
    JSONVec3Array() {}
    JSONVec3Array(const osg::Vec3&);
    virtual void write(json_stream& str, WriteVisitor& visitor);
};

struct JSONVec4Array : public JSONVec3Array
{
    JSONVec4Array(const osg::Vec4&);
};

struct JSONVec2Array : public JSONVec3Array
{
    JSONVec2Array(const osg::Vec2&);
};

template <class T>
struct JSONValue : public JSONObject
{
    T _value;

    JSONValue(const T& v) {
        _value = v;
    }

    T& getValue() { return _value; }
    T getValue() const { return _value; }

    virtual void write(json_stream& str, WriteVisitor& /*visitor*/) {
        str << _value ;
    }

};


template <>
struct JSONValue<std::string> : public JSONObject
{
    std::string _value;

    JSONValue(const std::string& v) {
        _value = escape(v);
    }

    void write(json_stream& str, WriteVisitor& /*visitor*/) {
        str << '"' << _value  << '"';
    }

protected:
    std::string escape(const std::string& input) {
        std::string value = input;
        replace(value, std::string("\\"), std::string("\\\\"));
        replace(value, std::string("\""), std::string("\\\""));
        return value;
    }

    void replace(std::string& str, const std::string& from, const std::string& to) {
        if(from.empty())
            return;
        size_t start_pos = 0;
        while((start_pos = str.find(from, start_pos)) != std::string::npos) {
            str.replace(start_pos, from.length(), to);
            start_pos += to.length();
        }
    }
};


struct JSONMatrix : public JSONArray
{
    JSONMatrix(const osg::Matrix& matrix) {
        for (int i = 0; i < 16; i++) {
            _array.push_back(new JSONValue<double>(matrix.ptr()[i]));
        }
    }
    void write(json_stream& str, WriteVisitor& visitor);
};



struct JSONVertexArray : public JSONArray
{
    osg::ref_ptr<const osg::Array> _arrayData;
    std::string _filename;

    std::pair<unsigned int, unsigned int> writeMergeData(const osg::Array* array,
                                                         WriteVisitor &visitor,
                                                         const std::string& filename,
                                                         std::string& encoding);

    unsigned int writeData(const osg::Array* array, const std::string& filename)
    {
        std::ofstream myfile;
        myfile.open(filename.c_str(), std::ios::binary );
        const char* b = static_cast<const char*>(array->getDataPointer());
        myfile.write(b, array->getTotalDataSize());
        unsigned int fsize = myfile.tellp();
        myfile.close();
        return fsize;
    }

    template <class T> void writeInlineArray(json_stream& str, unsigned int size, const T* array) {
        str << JSONObjectBase::indent() << "\"Elements\": [ " << array[0];
        for (unsigned int i = 1; i < size; i++) {
            T v = array[i];
            str << ", " << v;
        }
        str << " ]," << std::endl;
    }

    template <typename T, typename U> void writeInlineArray(json_stream& str, unsigned int size, const T* array) {
        str << JSONObjectBase::indent() << "\"Elements\": [ " << static_cast<U>(array[0]);
        for (unsigned int i = 1; i < size; i++) {
            str << ", " << static_cast<U>(array[i]);
        }
        str << " ]," << std::endl;
    }

    template <class T> void writeInlineArrayReal(json_stream& str, unsigned int size, const T* array) {
        str << JSONObjectBase::indent() << "\"Elements\": [ " << array[0];
        for (unsigned int i = 1; i < size; i++) {
            float v = array[i];
            if (osg::isNaN(v))
                v = 0;
            str << ", " << v;
        }
        str << " ]," << std::endl;
    }


    void write(json_stream& str, WriteVisitor& visitor);

    JSONVertexArray() {}
    JSONVertexArray(const osg::Array* array) {
        _arrayData = array;
    }
};

struct JSONBufferArray : public JSONObjectWithUniqueID
{
    JSONBufferArray(const osg::Array* array)
    {
        JSONVertexArray* b = new JSONVertexArray(array);
        getMaps()["Array"] = b;
        getMaps()["ItemSize"] = new JSONValue<int>(array->getDataSize());
        getMaps()["Type"] = new JSONValue<std::string>("ARRAY_BUFFER"); //0x8892);
    }

    void setBufferName(const std::string& bufferName) {
        JSONObject::setBufferName(bufferName);
        getMaps()["Array"]->setBufferName(bufferName);
    }
};


JSONObject* getDrawMode(GLenum mode);

struct JSONDrawArray : public JSONObjectWithUniqueID
{
    JSONDrawArray(osg::DrawArrays& array)
    {
        getMaps()["First"] = new JSONValue<int>(array.getFirst());
        getMaps()["Count"] = new JSONValue<int>(array.getCount());
        getMaps()["Mode"] = getDrawMode(array.getMode());
    }
};

struct JSONDrawArrayLengths : public JSONObjectWithUniqueID
{
    JSONDrawArrayLengths(osg::DrawArrayLengths& array)
    {
        getMaps()["First"] = new JSONValue<int>(array.getFirst());
        getMaps()["Mode"] = getDrawMode(array.getMode());

        JSONArray* jsonArray = new JSONArray;
        for (unsigned int i = 0; i < array.size(); i++) {
            jsonArray->getArray().push_back(new JSONValue<int>(array[i]));
        }
        getMaps()["ArrayLengths"] = jsonArray;
    }

    void setBufferName(const std::string& bufferName) {
        JSONObject::setBufferName(bufferName);
        getMaps()["ArrayLengths"]->setBufferName(bufferName);
    }
};


template<typename T>
struct BufferArray
{ typedef osg::UShortArray type; };

#define ADD_INDEX_BUFFER(T, B)\
template<> \
struct BufferArray<T> \
{ typedef B type; }

ADD_INDEX_BUFFER(osg::DrawElementsUInt, osg::UIntArray);
ADD_INDEX_BUFFER(osg::DrawElementsUByte, osg::UByteArray);


template <class T>
struct JSONDrawElements : public JSONObjectWithUniqueID
{
    JSONDrawElements(T& array) {
        typedef typename BufferArray<T>::type array_type;
        typedef typename array_type::ElementDataType element_type;

        JSONBufferArray* buf;

        if (array.getMode() == GL_QUADS) {
            int size = array.getNumIndices();
            osg::ref_ptr<array_type> buffer = new array_type(size);
            unsigned int idx = 0;
            for (int i = 0; i < size/4; ++i) {
                (*buffer)[idx++] = static_cast<element_type>(array.index(i*4 + 0));
                (*buffer)[idx++] = static_cast<element_type>(array.index(i*4 + 1));
                (*buffer)[idx++] = static_cast<element_type>(array.index(i*4 + 3));

                (*buffer)[idx++] = static_cast<element_type>(array.index(i*4 + 1));
                (*buffer)[idx++] = static_cast<element_type>(array.index(i*4 + 2));
                (*buffer)[idx++] = static_cast<element_type>(array.index(i*4 + 3));
            }
            buf = new JSONBufferArray(buffer.get());
            getMaps()["Mode"] = getDrawMode(osg::PrimitiveSet::TRIANGLES);
        }
        else {
            osg::ref_ptr<array_type> buffer = new array_type(array.getNumIndices());
            for(unsigned int i = 0 ; i < array.getNumIndices() ; ++ i)
                (*buffer)[i] = static_cast<element_type>(array.index(i));
            buf = new JSONBufferArray(buffer.get());
            getMaps()["Mode"] = getDrawMode(array.getMode());
        }

        buf->getMaps()["Type"] = new JSONValue<std::string>("ELEMENT_ARRAY_BUFFER");
        getMaps()["Indices"] = buf;
    }

    void setBufferName(const std::string& bufferName) {
        JSONObject::setBufferName(bufferName);
        getMaps()["Indices"]->setBufferName(bufferName);
    }
};


#endif
