//-*****************************************************************************
//
// Copyright (c) 2009-2014,
//  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 <Alembic/AbcGeom/All.h>

using namespace Alembic::AbcGeom;
using namespace Alembic::Abc;
using namespace Alembic::AbcCoreAbstract;

void TimeAndSamplesMap::add(TimeSamplingPtr iTime, size_t iNumSamples)
{

    if (iNumSamples == 0)
    {
        iNumSamples = 1;
    }

    for (size_t i = 0; i < mTimeSampling.size(); ++i)
    {
        if (mTimeSampling[i]->getTimeSamplingType() ==
            iTime->getTimeSamplingType())
        {
            chrono_t curLastTime =
                mTimeSampling[i]->getSampleTime(mExpectedSamples[i]);

            chrono_t lastTime = iTime->getSampleTime(iNumSamples);
            if (lastTime < curLastTime)
            {
                lastTime = curLastTime;
            }

            if (mTimeSampling[i]->getSampleTime(0) > iTime->getSampleTime(0))
            {
                mTimeSampling[i] = iTime;
            }

            mExpectedSamples[i] = mTimeSampling[i]->getNearIndex(lastTime,
                std::numeric_limits< index_t >::max()).first;

            return;
        }
    }

    mTimeSampling.push_back(iTime);
    mExpectedSamples.push_back(iNumSamples);
}

TimeSamplingPtr TimeAndSamplesMap::get(TimeSamplingPtr iTime,
                                       std::size_t & oNumSamples) const
{
    for (size_t i = 0; i < mTimeSampling.size(); ++i)
    {
        if (mTimeSampling[i]->getTimeSamplingType() ==
            iTime->getTimeSamplingType())
        {
            oNumSamples = mExpectedSamples[i];
            return mTimeSampling[i];
        }
    }

    oNumSamples = 0;
    return TimeSamplingPtr();
}

index_t getIndexSample(index_t iCurOutIndex, TimeSamplingPtr iOutTime,
    index_t iInNumSamples, TimeSamplingPtr iInTime, index_t & oNumEmpty)
{

    // see if we are missing any samples for oNumEmpty
    chrono_t curTime = iOutTime->getSampleTime(iCurOutIndex);
    chrono_t inChrono = iInTime->getSampleTime(0);
    if (curTime < inChrono)
    {
        index_t emptyEnd = iOutTime->getNearIndex(inChrono,
            std::numeric_limits<index_t>::max()).first;
        if (emptyEnd > iCurOutIndex)
        {
            oNumEmpty = emptyEnd - iCurOutIndex;
        }
        else
        {
            oNumEmpty = 0;
        }
    }
    else
    {
        oNumEmpty = 0;
    }

    for (index_t i = 0; i < iInNumSamples; ++i)
    {
        inChrono = iInTime->getSampleTime(i);
        if (curTime <= inChrono ||
            Imath::equalWithAbsError(curTime, inChrono, 1e-5))
        {
            return i;
        }
    }

    return iInNumSamples;
}

void checkAcyclic(const TimeSamplingType & tsType,
                  const std::string & fullNodeName)
{
    if (tsType.isAcyclic())
    {
        std::cerr << "No support for stitching acyclic sampling node "
            << fullNodeName << std::endl;
        exit(1);
    }
}


void stitchArrayProp(const PropertyHeader & propHeader,
                     const ICompoundPropertyVec & iCompoundProps,
                     OCompoundProperty & oCompoundProp,
                     const TimeAndSamplesMap & iTimeMap)
{

    size_t totalSamples = 0;
    TimeSamplingPtr timePtr =
        iTimeMap.get(propHeader.getTimeSampling(), totalSamples);

    const DataType & dataType = propHeader.getDataType();
    const MetaData & metaData = propHeader.getMetaData();
    const std::string & propName = propHeader.getName();

    Dimensions emptyDims(0);
    ArraySample emptySample(NULL, dataType, emptyDims);

    OArrayProperty writer(oCompoundProp, propName, dataType, metaData, timePtr);

    size_t numInputs = iCompoundProps.size();
    for (size_t iCpIndex = 0; iCpIndex < numInputs; iCpIndex++)
    {

        if (!iCompoundProps[iCpIndex].valid())
        {
            continue;
        }

        const PropertyHeader * childHeader =
            iCompoundProps[iCpIndex].getPropertyHeader(propName);

        if (!childHeader || dataType != childHeader->getDataType())
        {
            continue;
        }

        IArrayProperty reader(iCompoundProps[iCpIndex], propName);
        index_t numSamples = reader.getNumSamples();

        ArraySamplePtr dataPtr;
        index_t numEmpty;
        index_t k = getIndexSample(writer.getNumSamples(),
            writer.getTimeSampling(), numSamples,
            reader.getTimeSampling(), numEmpty);


        for (index_t j = 0; j < numEmpty; ++j)
        {
            if (!iTimeMap.isHold())
            {
                writer.set(emptySample);
            }
            else if (writer.getNumSamples() == 0)
            {
                reader.get(dataPtr, 0);
                writer.set(*dataPtr);
            }
            else
            {
                writer.setFromPrevious();
            }
        }

        for (; k < numSamples; k++)
        {
            reader.get(dataPtr, k);
            writer.set(*dataPtr);
        }
    }

    // fill in any other empties
    for (size_t i = writer.getNumSamples(); i < totalSamples; ++i)
    {
        if (!iTimeMap.isHold())
        {
            writer.set(emptySample);
        }
        else
        {
            writer.setFromPrevious();
        }
    }
}

// return true if we needed to stitch the geom param
bool stitchArbGeomParam(const PropertyHeader & propHeader,
                        const ICompoundPropertyVec & iCompoundProps,
                        OCompoundProperty & oCompoundProp,
                        const TimeAndSamplesMap & iTimeMap)
{
    // go through all the inputs to see if all the property types are the same
    size_t numInputs = iCompoundProps.size();
    const std::string & propName = propHeader.getName();
    PropertyType ptype = propHeader.getPropertyType();
    bool diffProp = false;

    for (size_t iCpIndex = 1; iCpIndex < numInputs && diffProp == false;
         iCpIndex++)
    {
        if (!iCompoundProps[iCpIndex].valid())
        {
            continue;
        }

        const PropertyHeader * childHeader =
            iCompoundProps[iCpIndex].getPropertyHeader(propName);

        if (childHeader && childHeader->getPropertyType() != ptype)
        {
            diffProp = true;
        }
    }

    // all of the props are the same, lets stitch them like normal
    if (!diffProp)
    {
        return false;
    }


    // we have a mismatch of indexed and non-index geom params, lets stitch them
    // together AS indexed
    std::vector< IArrayProperty > valsProp(numInputs);
    std::vector< IArrayProperty > indicesProp(numInputs);

    bool firstVals = true;

    DataType dataType;
    MetaData metaData;
    TimeSamplingPtr timePtr;

    // first we need to get our attrs
    for (size_t iCpIndex = 0; iCpIndex < numInputs; iCpIndex++)
    {
        if (!iCompoundProps[iCpIndex].valid())
        {
            continue;
        }

        const PropertyHeader * childHeader =
            iCompoundProps[iCpIndex].getPropertyHeader(propName);

        if (childHeader && childHeader->isArray())
        {
            valsProp[iCpIndex] = IArrayProperty(iCompoundProps[iCpIndex],
                                                propName);

            if (firstVals)
            {
                firstVals = false;
                dataType = valsProp[iCpIndex].getDataType();
                metaData = valsProp[iCpIndex].getMetaData();
                timePtr = valsProp[iCpIndex].getTimeSampling();
            }
        }
        else if (childHeader && childHeader->isCompound())
        {
            ICompoundProperty cprop(iCompoundProps[iCpIndex], propName);
            if (cprop.getPropertyHeader(".vals") != NULL &&
                cprop.getPropertyHeader(".indices") != NULL)
            {
                valsProp[iCpIndex] = IArrayProperty(cprop, ".vals");
                indicesProp[iCpIndex] = IArrayProperty(cprop, ".indices");

                if (firstVals)
                {
                    firstVals = false;
                    dataType = valsProp[iCpIndex].getDataType();
                    metaData = valsProp[iCpIndex].getMetaData();
                    timePtr = valsProp[iCpIndex].getTimeSampling();
                }
            }
        }
    }


    size_t totalSamples = 0;
    timePtr = iTimeMap.get(timePtr, totalSamples);

    DataType indicesType(Alembic::Util::kUint32POD);
    Dimensions emptyDims(0);
    ArraySample emptySample(NULL, dataType, emptyDims);
    ArraySample emptyIndicesSample(NULL, indicesType, emptyDims);

    // we write indices and vals together
    OCompoundProperty ocProp(oCompoundProp, propName, metaData);
    OArrayProperty valsWriter(ocProp, ".vals", dataType, metaData, timePtr);
    OArrayProperty indicesWriter(ocProp, ".indices", indicesType, timePtr);

    for (size_t index = 0; index < numInputs; index++)
    {

        if (!valsProp[index].valid())
        {
            continue;
        }

        index_t numSamples = valsProp[index].getNumSamples();

        ArraySamplePtr dataPtr;
        index_t numEmpty;
        index_t k = getIndexSample(valsWriter.getNumSamples(),
            valsWriter.getTimeSampling(), numSamples,
            valsProp[index].getTimeSampling(), numEmpty);

        for (index_t j = 0; j < numEmpty; ++j)
        {
            if (!iTimeMap.isHold())
            {
                valsWriter.set(emptySample);
                indicesWriter.set(emptyIndicesSample);
            }
            else if (valsWriter.getNumSamples() == 0)
            {
                valsProp[index].get(dataPtr, 0);
                valsWriter.set(*dataPtr);

                //
                if (indicesProp[index].valid())
                {
                    indicesProp[index].get(dataPtr, 0);
                    indicesWriter.set(*dataPtr);
                }
            }
            else
            {
                valsWriter.setFromPrevious();
                indicesWriter.setFromPrevious();
            }

        }

        for (; k < numSamples; k++)
        {
            valsProp[index].get(dataPtr, k);
            valsWriter.set(*dataPtr);

            if (indicesProp[index].valid())
            {
                indicesProp[index].get(dataPtr, k);
                indicesWriter.set(*dataPtr);
            }
            else
            {
                // we need to construct our indices manually
                Dimensions dataDims = dataPtr->getDimensions();
                std::vector<Alembic::Util::uint32_t> indicesVec(
                    dataDims.numPoints());
                for (size_t dataIdx = 0; dataIdx < indicesVec.size(); ++dataIdx)
                {
                    indicesVec[dataIdx] = (Alembic::Util::uint32_t) dataIdx;
                }

                // set the empty sample
                if (indicesVec.empty())
                {
                    indicesWriter.set(emptyIndicesSample);
                }
                else
                {
                    ArraySample indicesSamp(&indicesVec.front(), indicesType,
                                            dataDims);
                    indicesWriter.set(indicesSamp);
                }
            }
        }
    }

    // fill in any other empties
    for (size_t i = valsWriter.getNumSamples(); i < totalSamples; ++i)
    {
        if (!iTimeMap.isHold())
        {
            valsWriter.set(emptySample);
            indicesWriter.set(emptyIndicesSample);
        }
        else
        {
            valsWriter.setFromPrevious();
            indicesWriter.setFromPrevious();
        }
    }
    return true;
}

template< typename T >
void scalarPropIO(IScalarProperty & reader,
                  Alembic::Util::uint8_t extent,
                  OScalarProperty & writer, bool hold)
{
    std::vector< T > data(extent);
    std::vector< T > emptyData(extent);
    void * emptyPtr = static_cast< void* >(&emptyData.front());
    void * vPtr = static_cast< void* >(&data.front());

    index_t numSamples = reader.getNumSamples();
    index_t numEmpty;
    index_t k = getIndexSample(writer.getNumSamples(), writer.getTimeSampling(),
        numSamples, reader.getTimeSampling(), numEmpty);

    // not really empty, but set to a default 0 or empty string value
    for (index_t i = 0; i < numEmpty; ++i)
    {
        if (!hold)
        {
            writer.set(emptyPtr);
        }
        else if (writer.getNumSamples() == 0)
        {
            reader.get(vPtr, 0);
            writer.set(vPtr);
        }
        else
        {
            writer.setFromPrevious();
        }
    }

    for (; k < numSamples; ++k)
    {
        reader.get(vPtr, k);
        writer.set(vPtr);
    }
}

void stitchScalarProp(const PropertyHeader & propHeader,
                      const ICompoundPropertyVec & iCompoundProps,
                      OCompoundProperty & oCompoundProp,
                      const TimeAndSamplesMap & iTimeMap)
{
    size_t totalSamples = 0;
    TimeSamplingPtr timePtr =
        iTimeMap.get(propHeader.getTimeSampling(), totalSamples);

    const DataType & dataType = propHeader.getDataType();
    const MetaData & metaData = propHeader.getMetaData();
    const std::string & propName = propHeader.getName();
    Alembic::Util::PlainOldDataType pod = dataType.getPod();

    OScalarProperty writer(oCompoundProp, propName, dataType, metaData,
                           timePtr);

    size_t numInputs = iCompoundProps.size();
    for (size_t iCpIndex = 0; iCpIndex < numInputs; iCpIndex++)
    {

        if (!iCompoundProps[iCpIndex].valid())
        {
            continue;
        }

        const PropertyHeader * childHeader =
            iCompoundProps[iCpIndex].getPropertyHeader(propName);

        if (!childHeader || dataType != childHeader->getDataType())
        {
            continue;
        }

        IScalarProperty reader(iCompoundProps[iCpIndex], propName);
        Alembic::Util::uint8_t extent = dataType.getExtent();
        switch(pod)
        {
            case Alembic::Util::kBooleanPOD:
                scalarPropIO< Alembic::Util::bool_t >(reader, extent, writer, iTimeMap.isHold());
                break;
            case Alembic::Util::kUint8POD:
                scalarPropIO< Alembic::Util::uint8_t >(reader, extent, writer, iTimeMap.isHold());
                break;
            case Alembic::Util::kInt8POD:
                scalarPropIO< Alembic::Util::int8_t >(reader, extent, writer, iTimeMap.isHold());
                break;
            case Alembic::Util::kUint16POD:
                scalarPropIO< Alembic::Util::uint16_t >(reader, extent, writer, iTimeMap.isHold());
                break;
            case Alembic::Util::kInt16POD:
                scalarPropIO< Alembic::Util::int16_t >(reader, extent, writer, iTimeMap.isHold());
                break;
            case Alembic::Util::kUint32POD:
                scalarPropIO< Alembic::Util::uint32_t >(reader, extent, writer, iTimeMap.isHold());
                break;
            case Alembic::Util::kInt32POD:
                scalarPropIO< Alembic::Util::int32_t >(reader, extent, writer, iTimeMap.isHold());
                break;
            case Alembic::Util::kUint64POD:
                scalarPropIO< Alembic::Util::uint64_t >(reader, extent, writer, iTimeMap.isHold());
                break;
            case Alembic::Util::kInt64POD:
                scalarPropIO< Alembic::Util::int64_t >(reader, extent, writer, iTimeMap.isHold());
                break;
            case Alembic::Util::kFloat16POD:
                scalarPropIO< Alembic::Util::float16_t >(reader, extent, writer, iTimeMap.isHold());
                break;
            case Alembic::Util::kFloat32POD:
                scalarPropIO< Alembic::Util::float32_t >(reader, extent, writer, iTimeMap.isHold());
                break;
            case Alembic::Util::kFloat64POD:
                scalarPropIO< Alembic::Util::float64_t >(reader, extent, writer, iTimeMap.isHold());
                break;
            case Alembic::Util::kStringPOD:
                scalarPropIO< Alembic::Util::string >(reader, extent, writer, iTimeMap.isHold());
                break;
            case Alembic::Util::kWstringPOD:
                scalarPropIO< Alembic::Util::wstring >(reader, extent, writer, iTimeMap.isHold());
                break;
            default:
                break;
        }
    }

    // set any extra empties, or hold
    std::vector< Alembic::Util::string > emptyStr(dataType.getExtent());
    std::vector< Alembic::Util::wstring > emptyWstr(dataType.getExtent());
    std::vector< Alembic::Util::uint8_t > emptyBuffer(dataType.getNumBytes());
    for (size_t i = writer.getNumSamples(); i < totalSamples; ++i)
    {
        if (iTimeMap.isHold())
        {
            writer.setFromPrevious();
        }
        else if (pod == Alembic::Util::kStringPOD)
        {
            writer.set(&emptyStr.front());
        }
        else if (pod == Alembic::Util::kWstringPOD)
        {
            writer.set(&emptyWstr.front());
        }
        else
        {
            writer.set(&emptyBuffer.front());
        }
    }
}

void stitchCompoundProp(ICompoundPropertyVec & iCompoundProps,
                        OCompoundProperty & oCompoundProp,
                        const TimeAndSamplesMap & iTimeMap)
{
    size_t numCompounds = iCompoundProps.size();
    for (size_t i = 0; i < numCompounds; ++i)
    {
        if (!iCompoundProps[i].valid())
        {
            continue;
        }

        size_t numProps = iCompoundProps[i].getNumProperties();
        for (size_t propIndex = 0; propIndex < numProps; propIndex++)
        {
            const PropertyHeader & propHeader =
                iCompoundProps[i].getPropertyHeader(propIndex);

            if (oCompoundProp.getPropertyHeader(propHeader.getName()) != NULL)
            {
                continue;
            }

            if (propHeader.getMetaData().get("isGeomParam") == "true" &&
                stitchArbGeomParam(propHeader, iCompoundProps, oCompoundProp,
                                   iTimeMap))
            {
                continue;
            }
            else if (propHeader.isCompound())
            {
                ICompoundPropertyVec childProps;
                for (size_t j = i; j < numCompounds; ++j)
                {
                    if (!iCompoundProps[j].valid() ||
                        iCompoundProps[j].getPropertyHeader(
                            propHeader.getName()) == NULL)
                    {
                        continue;
                    }

                    childProps.push_back(ICompoundProperty(iCompoundProps[j],
                        propHeader.getName()));
                }
                OCompoundProperty child(oCompoundProp, propHeader.getName(),
                    propHeader.getMetaData());
                stitchCompoundProp(childProps, child, iTimeMap);
            }
            else if (propHeader.isScalar())
            {
                stitchScalarProp(propHeader, iCompoundProps,
                                 oCompoundProp, iTimeMap);
            }
            else if (propHeader.isArray())
            {
                stitchArrayProp(propHeader, iCompoundProps,
                                oCompoundProp, iTimeMap);
            }
        }
    }
}

