#ifndef _gpuCacheMaterial_h_
#define _gpuCacheMaterial_h_
#include <map>
#include <vector>
#include <assert.h>
#include <boost/make_shared.hpp>
#include <boost/ref.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/unordered_map.hpp>
#include <boost/utility.hpp>
#include <boost/weak_ptr.hpp>
#include <maya/MColor.h>
#include <maya/MString.h>
namespace GPUCache {
struct MStringHash : std::unary_function<MString, std::size_t>
{
    std::size_t operator()(
const MString& key)
 const    {
        unsigned int length = key.
length();
 
        const char* begin = key.
asChar();
 
        return boost::hash_range(begin, begin + length);
    }
};
class MaterialNode;
class MaterialProperty : boost::noncopyable
{
public:
    
    typedef boost::shared_ptr<const MaterialProperty> Ptr;
    typedef boost::weak_ptr<const MaterialProperty>   WPtr;
    typedef boost::shared_ptr<MaterialProperty>       MPtr;
    
    
    typedef boost::shared_ptr<const MaterialNode> NodePtr;
    typedef boost::weak_ptr<const MaterialNode>   NodeWPtr;
    
    enum Type {
        kBool,
        kInt32,
        kFloat,
        kFloat2,
        kFloat3,
        kRGB,
        kString,
    };
    
    struct PropertyData;
    typedef boost::shared_ptr<PropertyData> PropertyDataPtr;
    
    typedef std::map<double,PropertyDataPtr> SampleMap;
    
    static MPtr create(
const MString& name, Type type);
 
    
    MaterialProperty(
const MString& name, Type type);
    ~MaterialProperty();
    
    const MString& name()
 const { 
return fName; }
 
    Type           type() const { return fType; }
    
    bool asBool(double seconds) const;
    void setBool(double seconds, bool value);
    int asInt32(double seconds) const;
    void setInt32(double seconds, int value);
    float asFloat(double seconds) const;
    void setFloat(double seconds, float value);
    void asFloat2(double seconds, float& x, float& y) const;
    void setFloat2(double seconds, float x, float y);
    void asFloat3(double seconds, float& x, float& y, float& z) const;
    void setFloat3(double seconds, float x, float y, float z);
    const MColor& asColor(
double seconds) 
const;
 
    void setColor(
double seconds, 
const MColor& value);
 
    const MString& asString(
double seconds) 
const;
 
    void setString(
double seconds, 
const MString& value);
 
    
    void setDefault(bool value);
    void setDefault(int value);
    void setDefault(float value);
    void setDefault(float x, float y);
    void setDefault(float x, float y, float z);
    void setDefault(
const MColor& value);
 
    void setDefault(
const MString& value);
 
    bool           getDefaultAsBool() const;
    int            getDefaultAsInt32() const;
    float          getDefaultAsFloat() const;
    void           getDefaultAsFloat2(float& x, float& y) const;
    void           getDefaultAsFloat3(float& x, float& y, float& z) const;
    const MColor&  getDefaultAsColor() 
const;
 
    const MString& getDefaultAsString() 
const;
 
    bool isDefault() const { return fValues.empty(); }
    
    bool isAnimated() const { return fValues.size() > 1; }
    const SampleMap& getSamples() const { return fValues; }
    
    void connect(const NodePtr& node, const Ptr& prop)
    { assert(node && prop); fSourceNode = node; fSourceProp = prop; }
    const NodePtr srcNode() const
    { return fSourceNode.lock(); }
    const Ptr srcProp() const
    { return fSourceProp.lock(); }
private:
    struct BoolPropertyData;
    struct Int32PropertyData;
    struct FloatPropertyData;
    struct Float2PropertyData;
    struct Float3PropertyData;
    struct ColorPropertyData;
    struct StringPropertyData;
    
    static PropertyDataPtr createData(Type type);
    
    template<typename T>
    const T* findValue(double seconds) const
    {
        if (isAnimated()) {
            
            SampleMap::const_iterator it = fValues.upper_bound(seconds);
            if (it != fValues.begin()) --it;
            return static_cast<const T*>((*it).second.get());
        }
        else if (fValues.size() == 1) {
            return static_cast<const T*>((*fValues.begin()).second.get());
        }
        else {
            return static_cast<const T*>(fDefaultValue.get());
        }
    }
    
    void setValue(double seconds, const PropertyDataPtr& data)
    {
        assert(fValues.find(seconds) == fValues.end());
        fValues.insert(std::make_pair(seconds, data));
    }
    const Type      fType;
    PropertyDataPtr fDefaultValue;
    SampleMap       fValues;
    NodeWPtr        fSourceNode;
    WPtr            fSourceProp;
};
 
 
class MaterialPropertyRef : boost::noncopyable
{
public:
    MaterialPropertyRef()  {}
    ~MaterialPropertyRef() {}
    const MaterialProperty::Ptr operator->() const
    { assert(fProp); return fProp; }
    
    MaterialProperty::MPtr operator->()
    { assert(fProp); return fProp; }
    operator MaterialProperty::MPtr& ()
    { assert(fProp); return fProp;}
    operator const MaterialProperty::Ptr () const
    { assert(fProp); return fProp;}
    bool operator== (const MaterialProperty::Ptr& rv) const
    { assert(fProp); return fProp == rv; }
private:
    friend class MaterialNode;
    void initialize(MaterialProperty::MPtr& prop)
    { fProp = prop; }
    MaterialProperty::MPtr fProp;
};
class MaterialNodeVisitor : boost::noncopyable
{
public:
    virtual ~MaterialNodeVisitor() {}
};
class MaterialNode : boost::noncopyable
{
public:
    
    typedef boost::shared_ptr<const MaterialNode> Ptr;
    typedef boost::weak_ptr<const MaterialNode>   WPtr;
    typedef boost::shared_ptr<MaterialNode>       MPtr;
    
    typedef boost::unordered_map<MString,MaterialProperty::Ptr,MStringHash> PropertyMap;
    static MaterialNode::MPtr create(
const MString& name, 
const MString& nodeType);
 
    
        : fName(name), fType(type)
    {}
    virtual ~MaterialNode() {}
    
    const MString& name()
 const { 
return fName; }
 
    const MString& type()
 const { 
return fType; }
 
    
    MaterialProperty::MPtr createProperty(
const MString& name, MaterialProperty::Type type);
    MaterialProperty::MPtr findProperty(
const MString& name);
    MaterialProperty::Ptr findProperty(
const MString& name) 
const;
    const PropertyMap& properties() const { return fProperties; }
    
    virtual void accept(MaterialNodeVisitor& visitor) const = 0;
protected:
    
    
    void createProperty(
const MString& name, MaterialProperty::Type type, MaterialPropertyRef& ref);
 
private:
    PropertyMap   fProperties;
};
class MaterialGraph : boost::noncopyable
{
public:
    
    typedef boost::shared_ptr<const MaterialGraph> Ptr;
    typedef boost::shared_ptr<MaterialGraph>       MPtr;
    typedef boost::weak_ptr<const MaterialGraph>   WPtr;
    
    typedef boost::unordered_map<MString, MaterialNode::Ptr, MStringHash > NamedMap;
    
        : fName(name)
    {}
    virtual ~MaterialGraph() {}
    
    { return fName; }
    
    void addNode(const MaterialNode::Ptr& node)
    { assert(node); fMaterialNodeMap.insert(std::make_pair(node->name(), node)); }
    const NamedMap& getNodes() const
    { return fMaterialNodeMap; }
    
    void setRootNode(const MaterialNode::Ptr& node)
    { assert(node); fRootNode = node; }
    const MaterialNode::Ptr& rootNode() const
    { return fRootNode; }
    bool isAnimated() const;
private:
    NamedMap          fMaterialNodeMap;
    MaterialNode::Ptr fRootNode;
};
class MaterialGraphMap : boost::noncopyable
{
public:
    
    typedef boost::shared_ptr<const MaterialGraphMap> Ptr;
    typedef boost::shared_ptr<MaterialGraphMap>       MPtr;
    
    typedef boost::unordered_map<MString, MaterialGraph::Ptr, MStringHash > NamedMap;
    
    MaterialGraphMap() {}
    virtual ~MaterialGraphMap() {}
    
    void addMaterialGraph(const MaterialGraph::Ptr& graph);
    
    const NamedMap& getGraphs() const
    { return fMaterialGraphMap; }
    
    const MaterialGraph::Ptr find(
const MString name) 
const;
 
private:
    NamedMap fMaterialGraphMap;
};
} 
#endif