/*
 * Copyright 2006 Sony Computer Entertainment Inc.
 *
 * Licensed under the SCEA Shared Source License, Version 1.0 (the "License"); you may not use this
 * file except in compliance with the License. You may obtain a copy of the License at:
 * http://research.scea.com/scea_shared_source_license.html
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing permissions and limitations under the
 * License.
 */

#include "daeWriter.h"

#include <dom/domCOLLADA.h>

#include <dom/domNode.h>
#include <dom/domConstants.h>
#include <dom/domLibrary_cameras.h>
#include <dom/domLibrary_lights.h>
#include <dae/domAny.h>
//#include <dom/domVisual_scene.h>
//#include <dom/domLibrary_visual_scenes.h>

#include <osgSim/MultiSwitch>
#include <osgAnimation/AnimationManagerBase>
#include <osgAnimation/UpdateMatrixTransform>
#include <osg/Sequence>
#include <osg/Billboard>
#include <osg/CameraView>
#include <osgDB/ConvertUTF>

using namespace osgDAE;


void daeWriter::writeAnimations( osg::Node &node )
{
    const std::string nodeNameUTF( _pluginOptions.namesUseCodepage ? osgDB::convertStringFromCurrentCodePageToUTF8(node.getName()) : node.getName() );
    osg::Callback* ncb = node.getUpdateCallback();
    if (ncb)
    {
        osgAnimation::AnimationManagerBase* am = dynamic_cast<osgAnimation::AnimationManagerBase*>(ncb);
        if (am)
        {
            // Create library of animations if not existing yet
            if (!_domLibraryAnimations)
            {
                _domLibraryAnimations = daeSafeCast<domLibrary_animations>( dom->add( COLLADA_ELEMENT_LIBRARY_ANIMATIONS ) );
            }

            osgAnimation::AnimationList animationList = am->getAnimationList();
            for (size_t i = 0; i < animationList.size(); i++)
            {
                domAnimation* pDomAnimation = daeSafeCast< domAnimation >( _domLibraryAnimations->add( COLLADA_ELEMENT_ANIMATION ) );
                domAnimation* pMainDomAnimation = pDomAnimation;

                osg::ref_ptr<osgAnimation::Animation> animation = animationList[i];
                std::string animationName( animation->getName() );
                if (animationName.empty())
                    animationName = "animation";
                animationName = uniquify( animationName );
                pDomAnimation->setId(animationName.c_str());

                // Handle multiple channels within an animation
                osgAnimation::ChannelList animationChannels = animation->getChannels();
                for (size_t j=0; j < animationChannels.size(); j++)
                {
                    osgAnimation::Channel* channel = animationChannels[j].get();
                    std::string channelName( channel->getName() );
                    std::string channelNameUTF( _pluginOptions.namesUseCodepage ? osgDB::convertStringFromCurrentCodePageToUTF8(channelName) : channelName );

                    // Wrap each animation channel into it's own child <animation> when more than 1 channel
                    if (animationChannels.size() > 1)
                    {
                        pDomAnimation = daeSafeCast< domAnimation >( pMainDomAnimation->add( COLLADA_ELEMENT_ANIMATION ) );

                        if (channelName.empty())
                        {
                            channelName = "channel";
                            channelNameUTF = channelName;
                        }
                        animationName = uniquify( channelName );
                        pDomAnimation->setId(channelNameUTF.c_str());
                    }

                    std::string sourceName( channelNameUTF + "_sampler" );
                    std::string inputSourceName( channelNameUTF + "_input" );
                    std::string outputSourceName( channelNameUTF + "_output" );
                    std::string interpolationSourceName( channelNameUTF + "_interpolation" );

                    // Fill dom sources based on sampler
                    osgAnimation::Sampler* animationSampler = channel->getSampler();
                    osgAnimation::KeyframeContainer* kfc = animationSampler->getKeyframeContainer();
                    int size = kfc->size();

                    float valueStride = 1;

                    domListOfFloats timeValues;
                    domListOfFloats frameValues;
                    domListOfNames interpolationValues;

                    osgAnimation::Vec3KeyframeContainer* v3kc = dynamic_cast<osgAnimation::Vec3KeyframeContainer*>(kfc);
                    if (v3kc)
                    {
                        valueStride = 3;
                        for (size_t i=0; i < v3kc->size(); i++)
                        {
                            timeValues.append((*v3kc)[i].getTime());
                            osg::Vec3 vec = (*v3kc)[i].getValue();

                            // This needs some serious cleanup
                            if (channelNameUTF.find("euler") != std::string::npos)
                            {
                                frameValues.append(osg::RadiansToDegrees(vec.x()));
                                frameValues.append(osg::RadiansToDegrees(vec.y()));
                                frameValues.append(osg::RadiansToDegrees(vec.z()));
                            }
                            else
                            {
                                frameValues.append(vec.x());
                                frameValues.append(vec.y());
                                frameValues.append(vec.z());
                            }
                            interpolationValues.append("LINEAR");
                        }
                    }
                    osgAnimation::FloatKeyframeContainer* fkc = dynamic_cast<osgAnimation::FloatKeyframeContainer*>(kfc);
                    if (fkc)
                    {
                        valueStride = 1;
                        for (size_t i=0; i < fkc->size(); i++)
                        {
                            timeValues.append((*fkc)[i].getTime());
                            frameValues.append((*fkc)[i].getValue());
                            interpolationValues.append("LINEAR");
                        }
                    }

                    // time values
                    domSource* pDomSource = daeSafeCast< domSource >(pDomAnimation->add(COLLADA_ELEMENT_SOURCE));
                    pDomSource->setId(inputSourceName.c_str());

                    domFloat_array* pDomFloatArray = daeSafeCast< domFloat_array >(pDomSource->add(COLLADA_ELEMENT_FLOAT_ARRAY));
                    std::string inputArrayName = inputSourceName + "_array";
                    pDomFloatArray->setId(inputArrayName.c_str());
                    pDomFloatArray->setCount(size);
                    pDomFloatArray->setValue(timeValues);

                    domSource::domTechnique_common* pDomSourceTechniqueCommon = daeSafeCast< domSource::domTechnique_common >(pDomSource->add(COLLADA_ELEMENT_TECHNIQUE_COMMON));

                    domAccessor* pDomAccessor = daeSafeCast< domAccessor >(pDomSourceTechniqueCommon->add(COLLADA_ELEMENT_ACCESSOR));
                    std::string url = "#" + inputArrayName;
                    pDomAccessor->setSource(url.c_str());
                    pDomAccessor->setCount(size);
                    pDomAccessor->setStride(1);

                    domParam* pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
                    pDomParam->setName("TIME");
                    pDomParam->setType(COLLADA_TYPE_FLOAT);

                    // <source> interpolator type
                    pDomSource = daeSafeCast< domSource >(pDomAnimation->add(COLLADA_ELEMENT_SOURCE));
                    pDomSource->setId(interpolationSourceName.c_str());

                    domName_array* pDomNameArray = daeSafeCast< domName_array >(pDomSource->add(COLLADA_ELEMENT_NAME_ARRAY));
                    std::string interpolationArrayName = interpolationSourceName + "_array";
                    pDomNameArray->setId(interpolationArrayName.c_str());
                    pDomNameArray->setCount(size);
                    pDomNameArray->setValue(interpolationValues);

                    // <technique_common>
                    pDomSourceTechniqueCommon = daeSafeCast< domSource::domTechnique_common >(pDomSource->add(COLLADA_ELEMENT_TECHNIQUE_COMMON));

                    // <accessor count=size stride="1">
                    pDomAccessor = daeSafeCast< domAccessor >(pDomSourceTechniqueCommon->add(COLLADA_ELEMENT_ACCESSOR));
                    url = "#" + interpolationArrayName;
                    pDomAccessor->setSource(url.c_str());
                    pDomAccessor->setCount(size);
                    pDomAccessor->setStride(1);

                    // <param name="INTERPOLATION" type="name"/>
                    pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
                    pDomParam->setName("INTERPOLATION");
                    pDomParam->setType(COLLADA_TYPE_NAME);

                    // Split up access to the euler float array into three sources, because we need to target three separate transforms
                    if (channelNameUTF.find("euler") != std::string::npos)
                    {
                        pDomSource = daeSafeCast< domSource >(pDomAnimation->add(COLLADA_ELEMENT_SOURCE));
                        std::string outputSourceNameX = outputSourceName + "_X";
                        pDomSource->setId(outputSourceNameX.c_str());

                        pDomFloatArray = daeSafeCast< domFloat_array >(pDomSource->add(COLLADA_ELEMENT_FLOAT_ARRAY));
                        std::string outputArrayNameX = outputSourceNameX + "_array";
                        pDomFloatArray->setId(outputArrayNameX.c_str());

                        // TODO, flexible handling of different keyframe types, see osg exporter for inspiration
                        pDomFloatArray->setCount(size * valueStride);
                        pDomFloatArray->setValue(frameValues);

                        pDomSourceTechniqueCommon = daeSafeCast< domSource::domTechnique_common >(pDomSource->add(COLLADA_ELEMENT_TECHNIQUE_COMMON));

                        pDomAccessor = daeSafeCast< domAccessor >(pDomSourceTechniqueCommon->add(COLLADA_ELEMENT_ACCESSOR));
                        url = "#" + outputArrayNameX;
                        pDomAccessor->setSource(url.c_str());
                        pDomAccessor->setCount(size);
                        pDomAccessor->setStride(valueStride);
                        pDomAccessor->setOffset(0);
                        // <param name="ANGLE" type="float"/>
                        pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
                        pDomParam->setName("ANGLE");
                        pDomParam->setType(COLLADA_TYPE_FLOAT);

                        pDomSource = daeSafeCast< domSource >(pDomAnimation->add(COLLADA_ELEMENT_SOURCE));
                        std::string outputSourceNameY = outputSourceName + "_Y";
                        pDomSource->setId(outputSourceNameY.c_str());

                        pDomFloatArray = daeSafeCast< domFloat_array >(pDomSource->add(COLLADA_ELEMENT_FLOAT_ARRAY));
                        std::string outputArrayNameY = outputSourceNameY + "_array";
                        pDomFloatArray->setId(outputArrayNameY.c_str());

                        pDomSourceTechniqueCommon = daeSafeCast< domSource::domTechnique_common >(pDomSource->add(COLLADA_ELEMENT_TECHNIQUE_COMMON));

                        pDomAccessor = daeSafeCast< domAccessor >(pDomSourceTechniqueCommon->add(COLLADA_ELEMENT_ACCESSOR));
                        url = "#" + outputArrayNameY;
                        pDomAccessor->setSource(url.c_str());
                        pDomAccessor->setCount(size);
                        pDomAccessor->setStride(valueStride);
                        pDomAccessor->setOffset(1);
                        // <param name="ANGLE" type="float"/>
                        pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
                        pDomParam->setName("ANGLE");
                        pDomParam->setType(COLLADA_TYPE_FLOAT);

                        pDomSource = daeSafeCast< domSource >(pDomAnimation->add(COLLADA_ELEMENT_SOURCE));
                        std::string outputSourceNameZ = outputSourceName + "_Z";
                        pDomSource->setId(outputSourceNameZ.c_str());

                        pDomFloatArray = daeSafeCast< domFloat_array >(pDomSource->add(COLLADA_ELEMENT_FLOAT_ARRAY));
                        std::string outputArrayNameZ = outputSourceNameZ + "_array";
                        pDomFloatArray->setId(outputArrayNameZ.c_str());

                        pDomSourceTechniqueCommon = daeSafeCast< domSource::domTechnique_common >(pDomSource->add(COLLADA_ELEMENT_TECHNIQUE_COMMON));

                        pDomAccessor = daeSafeCast< domAccessor >(pDomSourceTechniqueCommon->add(COLLADA_ELEMENT_ACCESSOR));
                        url = "#" + outputArrayNameZ;
                        pDomAccessor->setSource(url.c_str());
                        pDomAccessor->setCount(size);
                        pDomAccessor->setStride(valueStride);
                        pDomAccessor->setOffset(2);
                        // <param name="ANGLE" type="float"/>
                        pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
                        pDomParam->setName("ANGLE");
                        pDomParam->setType(COLLADA_TYPE_FLOAT);

                        {
                            domSampler* pDomSampler = daeSafeCast< domSampler >(pDomAnimation->add(COLLADA_ELEMENT_SAMPLER));
                            std::string sourceNameX = sourceName + "_X";
                            pDomSampler->setId(sourceNameX.c_str());

                            // Fill dom sampler based on common semantics
                            {
                                domInputLocal* pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
                                pDomInput->setSemantic(COMMON_PROFILE_INPUT_INPUT);
                                std::string url = "#" + inputSourceName;
                                pDomInput->setSource(url.c_str());

                                pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
                                pDomInput->setSemantic(COMMON_PROFILE_INPUT_OUTPUT);
                                url = "#" + outputSourceNameX;
                                pDomInput->setSource(url.c_str());

                                pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
                                pDomInput->setSemantic(COMMON_PROFILE_INPUT_INTERPOLATION);
                                url = "#" + interpolationSourceName;
                                pDomInput->setSource(url.c_str());
                            }

                            // Set sampler as source
                            domChannel* pDomChannel = daeSafeCast< domChannel >(pDomAnimation->add(COLLADA_ELEMENT_CHANNEL));
                            std::string url = "#" + sourceNameX;
                            pDomChannel->setSource(url.c_str());

                            // targetName contains the name of the updateCallback
                            std::string targetName = channel->getTargetName();

                            // based on the type of updateCallback we can determine the transform element to target eg. "nodeName/translation"
                            osg::Node* node = _animatedNodeCollector.getTargetNode(targetName);
                            if (node)
                            {
                                std::string domChannelTargetName = nodeNameUTF + "/rotateX.ANGLE";
                                pDomChannel->setTarget(domChannelTargetName.c_str());
                            }
                            else
                            {
                                OSG_WARN << "Could not find animation target '" << targetName << "'" << std::endl;
                            }
                        }

                        {
                            domSampler* pDomSampler = daeSafeCast< domSampler >(pDomAnimation->add(COLLADA_ELEMENT_SAMPLER));
                            std::string sourceNameY = sourceName + "_Y";
                            pDomSampler->setId(sourceNameY.c_str());

                            // Fill dom sampler based on common semantics
                            {
                                domInputLocal* pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
                                pDomInput->setSemantic(COMMON_PROFILE_INPUT_INPUT);
                                std::string url = "#" + inputSourceName;
                                pDomInput->setSource(url.c_str());

                                pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
                                pDomInput->setSemantic(COMMON_PROFILE_INPUT_OUTPUT);
                                url = "#" + outputSourceNameY;
                                pDomInput->setSource(url.c_str());

                                pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
                                pDomInput->setSemantic(COMMON_PROFILE_INPUT_INTERPOLATION);
                                url = "#" + interpolationSourceName;
                                pDomInput->setSource(url.c_str());
                            }

                            // Set sampler as source
                            domChannel* pDomChannel = daeSafeCast< domChannel >(pDomAnimation->add(COLLADA_ELEMENT_CHANNEL));
                            std::string url = "#" + sourceNameY;
                            pDomChannel->setSource(url.c_str());

                            // targetName contains the name of the updateCallback
                            std::string targetName = channel->getTargetName();

                            // based on the type of updateCallback we can determine the transform element to target eg. "nodeName/translation"
                            osg::Node* node = _animatedNodeCollector.getTargetNode(targetName);
                            if (node)
                            {
                                std::string domChannelTargetName = nodeNameUTF + "/rotateY.ANGLE";
                                pDomChannel->setTarget(domChannelTargetName.c_str());
                            }
                            else
                            {
                                OSG_WARN << "Could not find animation target '" << targetName << "'" << std::endl;
                            }
                        }

                        {
                            domSampler* pDomSampler = daeSafeCast< domSampler >(pDomAnimation->add(COLLADA_ELEMENT_SAMPLER));
                            std::string sourceNameZ = sourceName + "_Z";
                            pDomSampler->setId(sourceNameZ.c_str());

                            // Fill dom sampler based on common semantics
                            {
                                domInputLocal* pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
                                pDomInput->setSemantic(COMMON_PROFILE_INPUT_INPUT);
                                std::string url = "#" + inputSourceName;
                                pDomInput->setSource(url.c_str());

                                pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
                                pDomInput->setSemantic(COMMON_PROFILE_INPUT_OUTPUT);
                                url = "#" + outputSourceNameZ;
                                pDomInput->setSource(url.c_str());

                                pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
                                pDomInput->setSemantic(COMMON_PROFILE_INPUT_INTERPOLATION);
                                url = "#" + interpolationSourceName;
                                pDomInput->setSource(url.c_str());
                            }

                            // Set sampler as source
                            domChannel* pDomChannel = daeSafeCast< domChannel >(pDomAnimation->add(COLLADA_ELEMENT_CHANNEL));
                            std::string url = "#" + sourceNameZ;
                            pDomChannel->setSource(url.c_str());

                            // targetName contains the name of the updateCallback
                            std::string targetName = channel->getTargetName();

                            // based on the type of updateCallback we can determine the transform element to target eg. "nodeName/translation"
                            osg::Node* node = _animatedNodeCollector.getTargetNode(targetName);
                            if (node)
                            {
                                std::string domChannelTargetName = nodeNameUTF + "/rotateZ.ANGLE";
                                pDomChannel->setTarget(domChannelTargetName.c_str());
                            }
                            else
                            {
                                OSG_WARN << "Could not find animation target '" << targetName << "'" << std::endl;
                            }
                        }
                    }
                    else
                    {
                        // values in keyframecontainer
                        pDomSource = daeSafeCast< domSource >(pDomAnimation->add(COLLADA_ELEMENT_SOURCE));
                        pDomSource->setId(outputSourceName.c_str());

                        pDomFloatArray = daeSafeCast< domFloat_array >(pDomSource->add(COLLADA_ELEMENT_FLOAT_ARRAY));
                        std::string outputArrayName = outputSourceName + "_array";
                        pDomFloatArray->setId(outputArrayName.c_str());

                        // TODO, flexible handling of different keyframe types, see osg exporter for inspiration
                        pDomFloatArray->setCount(size * valueStride);
                        pDomFloatArray->setValue(frameValues);

                        pDomSourceTechniqueCommon = daeSafeCast< domSource::domTechnique_common >(pDomSource->add(COLLADA_ELEMENT_TECHNIQUE_COMMON));

                        pDomAccessor = daeSafeCast< domAccessor >(pDomSourceTechniqueCommon->add(COLLADA_ELEMENT_ACCESSOR));
                        url = "#" + outputArrayName;
                        pDomAccessor->setSource(url.c_str());
                        pDomAccessor->setCount(size);
                        pDomAccessor->setStride(valueStride);

                        if (v3kc)
                        {
                            // <param name="X" type="float"/>
                            pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
                            pDomParam->setName("X");
                            pDomParam->setType(COLLADA_TYPE_FLOAT);
                            // <param name="Y" type="float"/>
                            pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
                            pDomParam->setName("Y");
                            pDomParam->setType(COLLADA_TYPE_FLOAT);
                            // <param name="Z" type="float"/>
                            pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
                            pDomParam->setName("Z");
                            pDomParam->setType(COLLADA_TYPE_FLOAT);
                        }
                        if (fkc)
                        {
                            // <param type="float"/>
                            pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
                            pDomParam->setType(COLLADA_TYPE_FLOAT);
                        }

                        domSampler* pDomSampler = daeSafeCast< domSampler >(pDomAnimation->add(COLLADA_ELEMENT_SAMPLER));
                        pDomSampler->setId(sourceName.c_str());

                        // Fill dom sampler based on common semantics
                        {
                            domInputLocal* pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
                            pDomInput->setSemantic(COMMON_PROFILE_INPUT_INPUT);
                            std::string url = "#" + inputSourceName;
                            pDomInput->setSource(url.c_str());

                            pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
                            pDomInput->setSemantic(COMMON_PROFILE_INPUT_OUTPUT);
                            url = "#" + outputSourceName;
                            pDomInput->setSource(url.c_str());

                            pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
                            pDomInput->setSemantic(COMMON_PROFILE_INPUT_INTERPOLATION);
                            url = "#" + interpolationSourceName;
                            pDomInput->setSource(url.c_str());
                        }

                        // Set sampler as source
                        domChannel* pDomChannel = daeSafeCast< domChannel >(pDomAnimation->add(COLLADA_ELEMENT_CHANNEL));
                        std::string url = "#" + sourceName;
                        pDomChannel->setSource(url.c_str());

                        // targetName contains the name of the updateCallback
                        std::string targetName = channel->getTargetName();

                        // based on the type of updateCallback we can determine the transform element to target eg. "nodeName/translation"
                        osg::Node* node = _animatedNodeCollector.getTargetNode(targetName);
                        if (node)
                        {
                            std::string domChannelTargetName = nodeNameUTF;

                            if (channelNameUTF.find("position") != std::string::npos)
                            {
                                domChannelTargetName += "/translate";
                            }
                            else if (channelNameUTF.find("scale") != std::string::npos)
                            {
                                domChannelTargetName += "/scale";
                            }

                            pDomChannel->setTarget(domChannelTargetName.c_str());
                        }
                        else
                        {
                            OSG_WARN << "Could not find animation target '" << targetName << "'" << std::endl;
                        }
                    }
                }
            }
        }
    }
}

