
#ifndef FRAMELIB_TERNARY_TEMPLATE_H
#define FRAMELIB_TERNARY_TEMPLATE_H

#include "FrameLib_DSP.h"

template <typename Op>
class FrameLib_TernaryOp final : public FrameLib_Processor
{
    enum ParameterList { kMismatchMode };
    enum MismatchModes { kWrap, kShrink, kExtend};
    
    class EnlargedInput
    {
    public:
        
        EnlargedInput(FrameLib_TernaryOp *owner, const double *input, unsigned long size, unsigned long extendedSize, MismatchModes mode)
            : mOwner(owner)
        {
            if (extendedSize > size)
            {
                mAllocated = owner->allocAutoArray<double>(extendedSize);
                
                if ((mPtr = mAllocated))
                {
                    switch (mode)
                    {
                        case kWrap:     copyVectorWrap(mAllocated, input, extendedSize, size);      break;
                        case kShrink:                                                               break;
                        case kExtend:   copyVectorExtend(mAllocated, input, extendedSize, size);    break;
                    }
                }
            }
            else
                mPtr = input;
        }
        
        bool isValid() const { return mPtr; }
        
        const double &operator [] (size_t idx) const { return mPtr[idx]; }

    private:
        
        // Deleted
        
        EnlargedInput(const EnlargedInput&) = delete;
        EnlargedInput& operator=(const EnlargedInput&) = delete;
        
        // Data
        
        FrameLib_TernaryOp *mOwner;
        FrameLib_TernaryOp::AutoArray<double> mAllocated;
        const double *mPtr;
    };
    
    struct ParameterInfo : public FrameLib_Parameters::Info
    {
        ParameterInfo()
        {
            add("Sets the mode used when dealing with mismatched input lengths: "
                "wrap - shorter right inputs are read modulo against longer left input. "
                "shrink - the output length is set to the length of the shorter input. "
                "extend - shorter right inputs are extended by repeating their final value.");
        }
    };
    

public:
    FrameLib_TernaryOp(FrameLib_Context context, const FrameLib_Parameters::Serial *serialisedParameters, FrameLib_Proxy *proxy)
    : FrameLib_Processor(context, proxy, getParameterInfo(), 3, 1)
    {
        mParameters.addEnum(kMismatchMode, "mismatch");
        mParameters.addEnumItem(kWrap, "wrap");
        mParameters.addEnumItem(kShrink, "shrink");
        mParameters.addEnumItem(kExtend, "extend");
        mParameters.setInstantiation();

        setInputMode(1, false, false, false);
        setInputMode(2, false, false, false);
        
        mParameters.set(serialisedParameters);
        
        mMismatchMode = mParameters.getEnum<MismatchModes>(kMismatchMode);
    }
    
    std::string objectInfo(bool verbose) override
    {
        return formatInfo("#: Calculation is performed on triplets of values in turn. "
                          "The output is a frame at least as long as the leftmost frame. "
                          "If inputs mismatch in length the result depends on the mismatch parameter.",
                          "#.", getDescriptionString(), verbose);
    }

    std::string inputInfo(unsigned long idx, bool verbose) override
    {
        switch (idx)
        {
            case 0: return "Input";
            case 1: return "Parameter 1";
            default: return "Parameter 2";
        }
    }

    std::string outputInfo(unsigned long idx, bool verbose) override
    {
        return "Output";
    }
    
private:
    
    void process() override
    {
        MismatchModes mode = mMismatchMode;
        Op op;
        
        unsigned long sizeIn[3];
        unsigned long sizeOut;
        
        const double *input1 = getInput(0, &sizeIn[0]);
        const double *input2 = getInput(1, &sizeIn[1]);
        const double *input3 = getInput(2, &sizeIn[2]);

        unsigned long sizeMax = *std::max_element(sizeIn, sizeIn + 3);
        unsigned long sizeMin = *std::min_element(sizeIn, sizeIn + 3);

        if (sizeMin == 0)
            return;
                
        switch (mode)
        {
            case kShrink:
                sizeOut = sizeMin;
                break;

            case kWrap:
            case kExtend:
                sizeOut = sizeMax;
        }
        
        requestOutputSize(0,sizeOut);
        allocateOutputs();
        double *output = getOutput(0, &sizeOut);

        if (!sizeOut)
            return;
        
        if (mode == kShrink || sizeMin == sizeMax)
        {
            for (unsigned long i = 0; i < sizeMin; i++)
                output[i] = op(input1[i], input2[i], input3[i]);
        }
        else
        {
            EnlargedInput in1(this, input1, sizeIn[0], sizeMax, mode);
            EnlargedInput in2(this, input2, sizeIn[1], sizeMax, mode);
            EnlargedInput in3(this, input3, sizeIn[2], sizeMax, mode);
        
            if (in1.isValid() && in2.isValid() && in3.isValid())
            {
                for (unsigned long i = 0; i < sizeMax; i++)
                    output[i] = op(in1[i], in2[i], in3[i]);
            }
            else
                zeroVector(output, sizeMax);
        }
    }
    
    const char *getDescriptionString() { return "Ternary Operator - No operator info available"; }
    
    ParameterInfo *getParameterInfo()
    {
        static ParameterInfo info;
        return &info;
    }
    
    MismatchModes mMismatchMode;
};

// Ternary Functor

template <double func(double, double, double)>
struct Ternary_Functor
{
    double operator()(double x, double y, double z) { return func(x, y, z); }
};

// Ternary (Function Version)

template <double func(double, double, double)>
using  FrameLib_Ternary = FrameLib_TernaryOp<Ternary_Functor<func>>;

#endif
