//-*****************************************************************************
//
// Copyright (c) 2009-2012,
//  Sony Pictures Imageworks, Inc. and
//  Industrial Light & Magic, a division of Lucasfilm Entertainment Company Ltd.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// *       Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// *       Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// *       Neither the name of Sony Pictures Imageworks, nor
// Industrial Light & Magic nor the names of their contributors may be used
// to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//-*****************************************************************************

#include "util.h"

#include <maya/MAnimControl.h>
#include <maya/MDGModifier.h>
#include <maya/MPlugArray.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnTransform.h>
#include <maya/MFnSet.h>
#include <maya/MItDag.h>
#include <maya/MSelectionList.h>
#include <maya/MStringArray.h>
#include <maya/MObjectArray.h>
#include <maya/MTime.h>
#include <maya/MItDependencyGraph.h>

#include <algorithm>

MObject createShadingGroup(const MString& iName)
{
    MStatus status;

    MFnSet fnSet;
    MSelectionList selList;
    MObject shadingGroup = fnSet.create(selList,
                                        MFnSet::kRenderableOnly,
                                        &status);
    if (status != MS::kSuccess)
    {
        MString theError("Could not create shading engine: ");
        theError += iName;
        MGlobal::displayError(theError);

        return shadingGroup;
    }

    fnSet.setName(iName);

    return shadingGroup;
}

MObjectArray getOutConnectedSG( const MDagPath &shapeDPath )
{
    MStatus status;

    // Array of connected Shaging Engines
    MObjectArray connSG;

    // Iterator through the dependency graph to find if there are
    // shading engines connected
    MObject obj(shapeDPath.node()); // non const MObject
    MItDependencyGraph itDG( obj, MFn::kShadingEngine,
                             MItDependencyGraph::kDownstream,
                             MItDependencyGraph::kBreadthFirst,
                             MItDependencyGraph::kNodeLevel, &status );

    if( status == MS::kFailure )
        return connSG;

    // we want to prune the iteration if the node is not a shading engine
    itDG.enablePruningOnFilter();

    // iterate through the output connected shading engines
    for( ; itDG.isDone()!= true; itDG.next() )
        connSG.append( itDG.currentItem() );

    return connSG;
}

MStatus replaceDagObject(MObject & oldObject, MObject & newObject,
            const MString & name)
{
    MStatus status;
    MFnDagNode mFnOld(oldObject, &status);
    if (status == MS::kSuccess)
    {
        unsigned int numChild = mFnOld.childCount();

        std::vector<MObject> children;
        children.reserve(numChild);
        for (unsigned int i = 0; i < numChild; i++)
        {
            MObject child = mFnOld.child(i, &status);
            if (status == MS::kSuccess)
            {
                children.push_back(child);
            }
            else
            {
                MString theError("Failed to get child ");
                theError += i;
                theError += " of ";
                theError += mFnOld.name();
                theError += ", status = ";
                theError += status.errorString();
                MGlobal::displayError(theError);
            }
        }

        MFnDagNode mFnNew(newObject, &status);
        if (status == MS::kSuccess)
        {
            for (unsigned int i = 0; i < numChild; i++)
            {
                status = mFnNew.addChild(children[i]);
                if (status != MS::kSuccess)
                {
                    MString theError("Failed to add child ");
                    theError += i;
                    theError += " of ";
                    theError += mFnOld.name();
                    theError += " to ";
                    theError += name;
                    theError += ", status = ";
                    theError += status.errorString();
                    MGlobal::displayError(theError);
                }
            }
        }
    }
    return status;
}

void disconnectProps(MFnDependencyNode & iNode,
    std::vector<Prop> & iSampledPropList,
    std::size_t iFirstProp)
{
    // get prop names and make sure they are disconnected before
    // trying to connect to them
    std::size_t numProps = iSampledPropList.size();
    for (std::size_t i = iFirstProp; i < numProps; ++i)
    {
        std::string propName;
        if (iSampledPropList[i].mArray.valid())
        {
            propName = iSampledPropList[i].mArray.getName();
        }
        else
        {
            propName = iSampledPropList[i].mScalar.getName();
        }

        // disconnect connections to animated props
        MPlug dstPlug;
        if (propName == Alembic::AbcGeom::kVisibilityPropertyName)
        {
            dstPlug = iNode.findPlug("visibility", true);
        }
        else
        {
            dstPlug = iNode.findPlug(propName.c_str(), true);
        }

        // make sure the long name matches
        if (propName == Alembic::AbcGeom::kVisibilityPropertyName ||
            dstPlug.partialName(false, false, false, false, false, true) == propName.c_str())
        {
            disconnectAllPlugsTo(dstPlug);
        }
    }
}

MStatus disconnectAllPlugsTo(MPlug & dstPlug)
{
    MStatus status = MS::kSuccess;
    MPlugArray array;
    dstPlug.connectedTo(array, true, false, &status);
    unsigned int arrayLength = array.length();

    for (unsigned int i = 0; i < arrayLength; i++)
    {
        MPlug srcPlug = array[i];
        if (status == MS::kSuccess)
        {
            MDGModifier modifier;
            status = modifier.disconnect(srcPlug, dstPlug);
            status = modifier.doIt();
            if (status != MS::kSuccess)
            {
                MString theError("Disconnect ");
                theError += srcPlug.name();
                theError += MString(" -> ");
                theError += dstPlug.name();
                theError += MString(" failed, status = ");
                theError += status.errorString();
                MGlobal::displayError(theError);
                return status;
            }
        }
    }
    return MS::kSuccess;
}

MStatus getDagPathListByName(const MString & objectNames,
    std::vector<MDagPath> & dagPathList)
{
    MStatus status;
    MStringArray theArray;
    status = objectNames.split(' ', theArray);
    if (status == MS::kSuccess)
    {
        unsigned int len = theArray.length();
        for (unsigned int i = 0; i < len; i++)
        {
            MDagPath dagPath;
            status = getDagPathByName(theArray[i], dagPath);
            if (status == MS::kSuccess)
                dagPathList.push_back(dagPath);
            else
            {
                MString theError(theArray[i]);
                theError += MString(" doesn't exist");
                MGlobal::displayError(theError);
            }
        }
    }

    return status;
}

MStatus getObjectByName(const MString & name, MObject & object)
{
    object = MObject::kNullObj;

    MSelectionList sList;
    MStatus status = sList.add(name);
    if (status == MS::kSuccess)
    {
        status = sList.getDependNode(0, object);
    }

    return status;
}

MStatus getDagPathByName(const MString & name, MDagPath & dagPath)
{
    MSelectionList sList;
    MStatus status = sList.add(name);
    if (status == MS::kSuccess)
    {
        status = sList.getDagPath(0, dagPath);
    }

    return status;
}

std::string stripPathAndNamespace(const std::string & iPath)
{
    std::string childName;
    std::size_t lastPath = iPath.rfind('|');
    if (lastPath != std::string::npos)
    {
        childName =  iPath.substr(lastPath + 1);
    }
    else
    {
        childName = iPath;
    }

    std::size_t lastNamespace = childName.rfind(':');
    if (lastNamespace != std::string::npos)
    {
        childName = childName.substr(lastNamespace+1);
    }
    return childName;
}

bool getDagPathByChildName(MDagPath & ioDagPath, const std::string & iChildName)
{
    unsigned int numChildren = ioDagPath.childCount();
    std::string strippedName = stripPathAndNamespace(iChildName);
    MObject closeMatch;

    for (unsigned int i = 0; i < numChildren; ++i)
    {
        MObject child = ioDagPath.child(i);
        MFnDagNode dagChild(child);
        std::string name = dagChild.partialPathName().asChar();
        if (name == iChildName)
        {
            ioDagPath.push(child);
            return true;
        }

        if (closeMatch.isNull())
        {
            if (strippedName == stripPathAndNamespace(name))
            {
                closeMatch = child;
            }
        }
    }

    if (!closeMatch.isNull())
    {
        ioDagPath.push(closeMatch);
        return true;
    }

    return false;
}

MStatus getPlugByName(const MString & objName, const MString & attrName,
            MPlug & plug)
{
    MObject object = MObject::kNullObj;
    MStatus status = getObjectByName(objName, object);
    if (status == MS::kSuccess)
    {
        MFnDependencyNode mFn(object, &status);
        if (status == MS::kSuccess)
            plug = mFn.findPlug(attrName, true, &status);
    }

    return status;
}

MStatus setPlayback(double min, double max)
{
    MStatus status = MS::kSuccess;
    MAnimControl anim;
    MTime minTime, maxTime, curTime;
    minTime.setValue(min);
    maxTime.setValue(max);

    status = anim.setMinTime(minTime);
    status = anim.setAnimationStartTime(minTime);

    if (max > 0)
    {
        status = anim.setMaxTime(maxTime);
        status = anim.setAnimationEndTime(maxTime);
    }

    return status;
}

MStatus setInitialShadingGroup(const MString & dagNodeName)
{
    MObject initShader;
    MDagPath dagPath;

    if (getObjectByName("initialShadingGroup", initShader) == MS::kSuccess &&
        getDagPathByName(dagNodeName, dagPath) == MS::kSuccess)
    {
        MFnSet set(initShader);
        set.addMember(dagPath);
    }
    else
    {
        MString theError("Error getting adding ");
        theError += dagNodeName;
        theError += MString(" to initalShadingGroup.");
        MGlobal::displayError(theError);
        return MS::kFailure;
    }
    return MS::kSuccess;
}

MStatus deleteDagNode(MDagPath & dagPath)
{
    MObject obj = dagPath.node();
    return MGlobal::deleteNode(obj);
}

MStatus deleteCurrentSelection()
{
    MStatus status;

    MDGModifier modifier;
    status = modifier.commandToExecute("\ndelete;\n");
    status = modifier.doIt();

    return status;
}

bool stripFileName(const MString & filePath, MString & fileName)
{
    std::string str(filePath.asChar());
    size_t found;
    found = str.find_last_of("/\\");
    str = str.substr(found+1);

    // str is now in the form of xxx.abc
    found = str.find_first_of(".");
    str = str.substr(0, found);
    fileName = MString(str.c_str());
    return true;
}

double getWeightAndIndex(double iFrame,
    Alembic::AbcCoreAbstract::TimeSamplingPtr iTime, size_t numSamps,
    Alembic::AbcCoreAbstract::index_t & oIndex,
    Alembic::AbcCoreAbstract::index_t & oCeilIndex)
{
    if (numSamps == 0)
        numSamps = 1;

    std::pair<Alembic::AbcCoreAbstract::index_t, double> floorIndex =
        iTime->getFloorIndex(iFrame, numSamps);

    oIndex = floorIndex.first;
    oCeilIndex = oIndex;

    if (fabs(iFrame - floorIndex.second) < 0.0001)
        return 0.0;

    std::pair<Alembic::AbcCoreAbstract::index_t, double> ceilIndex =
        iTime->getCeilIndex(iFrame, numSamps);

    if (oIndex == ceilIndex.first)
        return 0.0;

    oCeilIndex = ceilIndex.first;

    double alpha = (iFrame - floorIndex.second) /
        (ceilIndex.second - floorIndex.second);

    // we so closely match the ceiling so we'll just use it
    if (fabs(1.0 - alpha) < 0.0001)
    {
        oIndex = oCeilIndex;
        return 0.0;
    }

    return alpha;
}

bool isColorSet(const Alembic::AbcCoreAbstract::PropertyHeader & iHeader,
    bool iUnmarkedFaceVaryingColors)
{
    bool isColor = Alembic::AbcGeom::IC3fGeomParam::matches(iHeader) ||
            Alembic::AbcGeom::IC4fGeomParam::matches(iHeader);

    Alembic::AbcGeom::GeometryScope scope =
        Alembic::AbcGeom::GetGeometryScope(iHeader.getMetaData());

    bool isScoped = (scope == Alembic::AbcGeom::kFacevaryingScope ||
                     scope == Alembic::AbcGeom::kVaryingScope ||
                     scope == Alembic::AbcGeom::kVertexScope);
    return (isColor && isScoped &&
            (iUnmarkedFaceVaryingColors ||
            iHeader.getMetaData().get("mayaColorSet") != ""));
}
