#include "gpuCacheMaterialBakers.h"
#include "gpuCacheMaterialNodes.h"
#include "gpuCacheShapeNode.h"
#include "gpuCacheUtil.h"
#include <boost/foreach.hpp>
#include <set>
#include <maya/MFnDagNode.h>
#include <maya/MFnNumericData.h>
#include <maya/MPlugArray.h>
namespace GPUCache {
namespace MaterialBakers {
    
    class BaseMaterialNodeBaker : boost::noncopyable
    {
    public:
        typedef boost::shared_ptr<BaseMaterialNodeBaker> Ptr;
        
        static BaseMaterialNodeBaker::Ptr create(
            std::set<std::string>* traversedNodes);
        BaseMaterialNodeBaker(
const MObject& node)
            : fNode(node), fTraversedNodes(NULL)
        {}
        
        virtual ~BaseMaterialNodeBaker()
        {}
        
        
        void setupNetwork()
        {
            
            fBakedNode = createNode(fNode.name());
            assert(fBakedNode);
            collectPlugsAndProperties();
        }
        
        void sample(
const MTime& time)
 
        {
            assert(fBakedNode); 
            
            BOOST_FOREACH (Channel& channel, fChannels) {
                
                switch (channel.prop()->type()) {
                case MaterialProperty::kBool:
                    sampleBoolPlug(time, channel.plug(), channel.prop());
                    break;
                case MaterialProperty::kInt32:
                    sampleInt32Plug(time, channel.plug(), channel.prop());
                    break;
                case MaterialProperty::kFloat:
                    sampleFloatPlug(time, channel.plug(), channel.prop());
                    break;
                case MaterialProperty::kFloat2:
                    sampleFloat2Plug(time, channel.plug(), channel.prop());
                    break;
                case MaterialProperty::kFloat3:
                    sampleFloat3Plug(time, channel.plug(), channel.prop());
                    break;
                case MaterialProperty::kRGB:
                    sampleFloat3PlugAsColor(time, channel.plug(), channel.prop());
                    break;
                case MaterialProperty::kString:
                    sampleStringPlug(time, channel.plug(), channel.prop());
                    break;
                default:
                    assert(0); 
                    break;
                }
                
                BaseMaterialNodeBaker::Ptr& srcBaker = channel.srcBaker();
                if (srcBaker) {
                    srcBaker->sample(time);
                }
            }
        }
        
        
        void addToGraph(MaterialGraph::MPtr& graph)
        {
            assert(fBakedNode);  
            if (fBakedNode) {
                graph->addNode(fBakedNode);
            }
            
            BOOST_FOREACH (Channel& channel, fChannels) {
                BaseMaterialNodeBaker::Ptr& srcBaker = channel.srcBaker();
                if (srcBaker) {
                    srcBaker->addToGraph(graph);
                }
            }
        }
        
        void connect()
        {
            
            BOOST_FOREACH (Channel& channel, fChannels) {
                MaterialProperty::MPtr     dstProp  = channel.prop();
                BaseMaterialNodeBaker::Ptr srcBaker = channel.srcBaker();
                MaterialProperty::MPtr     srcProp  = channel.srcProp();
                
                if (dstProp && srcBaker && srcProp) {
                    MaterialNode::Ptr srcNode = srcBaker->bakedNode();
                    if (srcNode) {
                        dstProp->connect(srcNode, srcProp);
                    }
                }
            }
        }
        
        MaterialNode::MPtr bakedNode()
        { assert(fBakedNode); return fBakedNode; }
    protected:
        
        virtual MaterialNode::MPtr createNode(
const MString& name) = 0;
 
        virtual void               collectPlugsAndProperties() = 0;
        
        void sampleBoolPlug(
const MTime& time, 
const MPlug& plug, MaterialProperty::MPtr& prop)
 
        {
            if (prop->isDefault() || prop->asBool(timeInSeconds) != value) {
                prop->setBool(timeInSeconds, value);
            }
        }
        
        void sampleInt32Plug(
const MTime& time, 
const MPlug& plug, MaterialProperty::MPtr& prop)
 
        {
            int value = plug.
asInt();
 
            if (prop->isDefault() || prop->asInt32(timeInSeconds) != value) {
                prop->setInt32(timeInSeconds, value);
            }
        }
        
        void sampleFloatPlug(
const MTime& time, 
const MPlug& plug, MaterialProperty::MPtr& prop)
 
        {
            if (prop->isDefault() || prop->asFloat(timeInSeconds) != value) {
                prop->setFloat(timeInSeconds, value);
            }
        }
        
        void sampleFloat2Plug(
const MTime& time, 
const MPlug& plug, MaterialProperty::MPtr& prop)
 
        {
            float value[2], prev[2];
            prop->asFloat2(timeInSeconds, prev[0], prev[1]);
            if (prop->isDefault() || value[0] != prev[0] || value[1] != prev[1]) {
                prop->setFloat2(timeInSeconds, value[0], value[1]);
            }
        }
        
        void sampleFloat3Plug(
const MTime& time, 
const MPlug& plug, MaterialProperty::MPtr& prop)
 
        {
            float value[3], prev[3];
            prop->asFloat3(timeInSeconds, prev[0], prev[1], prev[2]);
            if (prop->isDefault() || value[0] != prev[0] || value[1] != prev[1] || value[2] != prev[2]) {
                prop->setFloat3(timeInSeconds, value[0], value[1], value[2]);
            }
        }
        
        void sampleFloat3PlugAsColor(
const MTime& time, 
const MPlug& plug, MaterialProperty::MPtr prop)
 
        {
            if (prop->isDefault() || value != prop->asColor(timeInSeconds)) {
                prop->setColor(timeInSeconds, value);
            }
        }
        
        void sampleStringPlug(
const MTime& time, 
const MPlug& plug, MaterialProperty::MPtr& prop)
 
        {
            if (prop->isDefault() || value != prop->asString(timeInSeconds)) {
                prop->setString(timeInSeconds, value);
            }
        }
        
        void sampleChannel(
const MString& name, MaterialProperty::MPtr& prop)
 
        {
            
            MPlug plug = fNode.findPlug(name, 
false);
 
            assert(prop);
                
                BaseMaterialNodeBaker::Ptr srcBaker;
                MaterialProperty::MPtr     srcProp;
                    
                    assert(plugArray.
length() == 1);
                    
                        MPlug srcPlug = plugArray[0];
 
                        assert(!srcNode.isNull());
                        
                        
                        
                        if (!isTraversed(srcNode)) {
                            
                            srcBaker = BaseMaterialNodeBaker::create(srcNode, fTraversedNodes);
                            if (srcBaker) {
                                
                                BOOST_FOREACH (Channel& channel, srcBaker->fChannels) {
                                    if (channel.plug() == srcPlug) {
                                        srcProp = channel.prop();
                                        break;
                                    }
                                }
                                
                                if (!srcProp) {
                                    srcBaker.reset();
                                }
                            }
                        }
                    }
                }
                
                fChannels.push_back(Channel(plug, prop, srcBaker, srcProp));
            }
        }
        
        void setTraversedNodes(std::set<std::string>* traversedNodes)
        {
            fTraversedNodes = traversedNodes;
        }
        
        bool isTraversed(
const MObject& node)
 
        {
            assert(!name.empty());
            if (fTraversedNodes && (*fTraversedNodes).find(name) != (*fTraversedNodes).end()) {
                return true;
            }
            return false;
        }
        
        void setTraversed(
const MObject& node)
 
        {
            assert(!name.empty());
            if (fTraversedNodes) {
                (*fTraversedNodes).insert(name);
            }
        }
    private:
        class Channel
        {
        public:
            Channel(
const MPlug&                plug, 
                    MaterialProperty::MPtr&     prop,
                    BaseMaterialNodeBaker::Ptr& srcBaker,
                    MaterialProperty::MPtr&     srcProp)
                : fPlug(plug), 
                  fProp(prop), 
                  fSrcBaker(srcBaker), 
                  fSrcProp(srcProp)
            {}
            ~Channel() {}
            const MPlug&                plug()     { 
return fPlug; }
 
            MaterialProperty::MPtr&     prop()     { return fProp; }
            BaseMaterialNodeBaker::Ptr& srcBaker() { return fSrcBaker; }
            MaterialProperty::MPtr&     srcProp()  { return fSrcProp; }
        private:
            MaterialProperty::MPtr     fProp;
            BaseMaterialNodeBaker::Ptr fSrcBaker;
            MaterialProperty::MPtr     fSrcProp;
        };
        std::vector<Channel> fChannels;
    private:
        MaterialNode::MPtr     fBakedNode;
        std::set<std::string>* fTraversedNodes;
    };
    class SurfaceMaterialBaker : public BaseMaterialNodeBaker
    {
    public:
        SurfaceMaterialBaker(
const MObject& node)
            : BaseMaterialNodeBaker(node) {}
        virtual MaterialNode::MPtr createNode(
const MString& name)
 
        {
            return boost::make_shared<SurfaceMaterial>(name);
        }
        virtual void collectPlugsAndProperties()
        {
            boost::shared_ptr<SurfaceMaterial> surfaceMaterial = 
                boost::dynamic_pointer_cast<SurfaceMaterial>(bakedNode());
            sampleChannel("outColor",        surfaceMaterial->OutColor);
            sampleChannel("outTransparency", surfaceMaterial->OutTransparency);
        }
    };
    class LambertBaker : public SurfaceMaterialBaker
    {
    public:
            : SurfaceMaterialBaker(node) {}
        virtual MaterialNode::MPtr createNode(
const MString& name)
 
        {
            return boost::make_shared<LambertMaterial>(name);
        }
        virtual void collectPlugsAndProperties()
        {
            SurfaceMaterialBaker::collectPlugsAndProperties();
            boost::shared_ptr<LambertMaterial> lambert = 
                boost::dynamic_pointer_cast<LambertMaterial>(bakedNode());
            sampleChannel("color",             lambert->Color);
            sampleChannel("transparency",      lambert->Transparency);
            sampleChannel("ambientColor",      lambert->AmbientColor);
            sampleChannel("incandescence",     lambert->Incandescence);
            sampleChannel("diffuse",           lambert->Diffuse);
            sampleChannel("translucence",      lambert->Translucence);
            sampleChannel("translucenceDepth", lambert->TranslucenceDepth);
            sampleChannel("translucenceFocus", lambert->TranslucenceFocus);
            sampleChannel("hideSource",        lambert->HideSource);
            sampleChannel("glowIntensity",     lambert->GlowIntensity);
        }
    };
    class PhongBaker : public LambertBaker
    {
    public:
            : LambertBaker(node) {}
        virtual MaterialNode::MPtr createNode(
const MString& name)
 
        {
            return boost::make_shared<PhongMaterial>(name);
        }
        virtual void collectPlugsAndProperties()
        {
            LambertBaker::collectPlugsAndProperties();
            boost::shared_ptr<PhongMaterial> phong = 
                boost::dynamic_pointer_cast<PhongMaterial>(bakedNode());
            sampleChannel("cosinePower",    phong->CosinePower);
            sampleChannel("specularColor",  phong->SpecularColor);
            sampleChannel("reflectivity",   phong->Reflectivity);
            sampleChannel("reflectedColor", phong->ReflectedColor);
        }
    };
    class BlinnBaker : public LambertBaker
    {
    public:
            : LambertBaker(node) {}
        virtual MaterialNode::MPtr createNode(
const MString& name)
 
        {
            return boost::make_shared<BlinnMaterial>(name);
        }
        virtual void collectPlugsAndProperties()
        {
            LambertBaker::collectPlugsAndProperties();
            boost::shared_ptr<BlinnMaterial> phong = 
                boost::dynamic_pointer_cast<BlinnMaterial>(bakedNode());
            sampleChannel("eccentricity",   phong->Eccentricity);
            sampleChannel("specularRollOff",phong->SpecularRollOff);
            sampleChannel("specularColor",  phong->SpecularColor);
            sampleChannel("reflectivity",   phong->Reflectivity);
            sampleChannel("reflectedColor", phong->ReflectedColor);
        }
    };
    class Texture2dBaker : public BaseMaterialNodeBaker
    {
    public:
        Texture2dBaker(
const MObject& node)
            : BaseMaterialNodeBaker(node) {}
        virtual MaterialNode::MPtr createNode(
const MString& name) = 0;
 
        virtual void collectPlugsAndProperties()
        {
            boost::shared_ptr<Texture2d> texture2d = 
                boost::dynamic_pointer_cast<Texture2d>(bakedNode());
            sampleChannel("defaultColor", texture2d->DefaultColor);
            sampleChannel("outColor", texture2d->OutColor);
            sampleChannel("outAlpha", texture2d->OutAlpha);
        }
    };
    class FileTextureBaker : public Texture2dBaker
    {
    public:
        FileTextureBaker(
const MObject& node)
            : Texture2dBaker(node) 
        {
        }
        virtual MaterialNode::MPtr createNode(
const MString& name)
 
        {
            return boost::make_shared<FileTexture>(name);
        }
        virtual void collectPlugsAndProperties()
        {
            Texture2dBaker::collectPlugsAndProperties();
            boost::shared_ptr<FileTexture> file = 
                boost::dynamic_pointer_cast<FileTexture>(bakedNode());
            sampleChannel("outTransparency", file->OutTransparency);
            sampleChannel("fileTextureName", file->FileTextureName);
        }
    };
    class UnknownTexture2dBaker : public Texture2dBaker
    {
    public:
        UnknownTexture2dBaker(
const MObject& node)
            : Texture2dBaker(node) {}
        virtual MaterialNode::MPtr createNode(
const MString& name)
 
        {
            return boost::make_shared<UnknownTexture2d>(name);
        }
        virtual void collectPlugsAndProperties()
        {
            Texture2dBaker::collectPlugsAndProperties();
        }
    };
    BaseMaterialNodeBaker::Ptr BaseMaterialNodeBaker::create(
        std::set<std::string>* traversedNodes)
    {
        BaseMaterialNodeBaker::Ptr baker;
            baker = boost::make_shared<PhongBaker>(boost::ref(node));
        }
            baker = boost::make_shared<BlinnBaker>(boost::ref(node));
        }
            baker = boost::make_shared<LambertBaker>(boost::ref(node));
        }
            baker = boost::make_shared<FileTextureBaker>(boost::ref(node));
        }
            baker = boost::make_shared<UnknownTexture2dBaker>(boost::ref(node));
        }
        
        if (baker) {
            baker->setTraversedNodes(traversedNodes);
            baker->setTraversed(node);
            baker->setupNetwork();
        }
        return baker;
    }
}
using namespace MaterialBakers;
class MaterialBaker::MaterialGraphBaker : boost::noncopyable
{
public:
    MaterialGraphBaker(
const MObject& node)
    {
        fRootBaker = BaseMaterialNodeBaker::create(node, &fTraversedNodes);
    }
    ~MaterialGraphBaker() {}
    void sample(
const MTime& time)
 
    {
        if (fRootBaker) {
            fRootBaker->sample(time);
        }
    }
    void buildGraph()
    {
        if (fRootBaker) {
            MaterialNode::Ptr rootNode = fRootBaker->bakedNode();
            if (rootNode) {
                
                MaterialGraph::MPtr graph = boost::make_shared<MaterialGraph>(rootNode->name());
                
                fRootBaker->addToGraph(graph);
                
                fRootBaker->connect();
                
                graph->setRootNode(rootNode);
                fGraph = graph;
            }
            
            fRootBaker.reset();
        }
    }
    MaterialGraph::Ptr get() const
    {
        return fGraph;
    }
private:
    BaseMaterialNodeBaker::Ptr fRootBaker;
    MaterialGraph::MPtr        fGraph;
    std::set<std::string>      fTraversedNodes;
};
MaterialBaker::MaterialBaker()
{}
MaterialBaker::~MaterialBaker()
{}
{
    
    }
    
    if (dagNode.
typeId() == ShapeNode::id) {
 
        const ShapeNode* node = (
const ShapeNode*)dagNode.
userNode();
 
        if (node) {
            const MaterialGraphMap::Ptr materials = node->getCachedMaterial();
            if (materials) {
                
                const MaterialGraphMap::NamedMap& graphs = materials->getGraphs();
                if (!graphs.empty()) {
                    fExistingGraphs.insert(graphs.cbegin(), graphs.cend());
                }
            }
        }
    }
    
    InstanceMaterialLookup lookup(dagPath);
    if (lookup.hasWholeObjectMaterial()) {
        
        MObject surfaceMaterial = lookup.findWholeObjectSurfaceMaterial();
 
        
        if (surfaceMaterial.
isNull()) {
 
        }
        
        
        MaterialGraphBakers::iterator iter = fMaterialGraphBakers.find(name);
        if (iter == fMaterialGraphBakers.end()) {
            MaterialGraphBakerPtr baker = 
                boost::make_shared<MaterialGraphBaker>(surfaceMaterial);
            fMaterialGraphBakers.insert(std::make_pair(name, baker));
        }
    }
    else if (lookup.hasComponentMaterials()) {
        
        std::vector<MObject> surfaceMaterials;
        lookup.findSurfaceMaterials(surfaceMaterials);
        BOOST_FOREACH (
const MObject& surfaceMaterial, surfaceMaterials) {
            if (surfaceMaterial.
isNull()) 
continue;
 
            
            
            MaterialGraphBakers::iterator iter = fMaterialGraphBakers.find(name);
            if (iter == fMaterialGraphBakers.end()) {
                MaterialGraphBakerPtr baker = 
                    boost::make_shared<MaterialGraphBaker>(surfaceMaterial);
                fMaterialGraphBakers.insert(std::make_pair(name, baker));
            }
        }
    }
}
{
    BOOST_FOREACH (MaterialGraphBakers::value_type& val, fMaterialGraphBakers) {
        val.second->sample(time);
    }
}
MStatus MaterialBaker::buildGraph()
 
{
    BOOST_FOREACH (MaterialGraphBakers::value_type& val, fMaterialGraphBakers) {
        val.second->buildGraph();
    }
}
MaterialGraphMap::Ptr MaterialBaker::get()
{
    MaterialGraphMap::MPtr graphMap = boost::make_shared<MaterialGraphMap>();
    
    BOOST_FOREACH (const MaterialGraphBakers::value_type& val, fMaterialGraphBakers) {
        MaterialGraph::Ptr graph = val.second->get();
        if (graph) {
            graphMap->addMaterialGraph(graph);
        }
    }
    
    BOOST_FOREACH (const NamedMaterialGraphs::value_type& val, fExistingGraphs) {
        if (val.second && !graphMap->find(val.first)) {
            graphMap->addMaterialGraph(val.second);
        }
    }
    return !graphMap->getGraphs().empty() ? graphMap : MaterialGraphMap::Ptr();
}
}