// SPDX-FileCopyrightText: 2026 0064ff
// SPDX-License-Identifier: MIT-0

#pragma once
#include <JuceHeader.h>
#include "MyCompressor.h"

template <typename SampleType>
class MyCompressorChain
{
public:
    //==============================================================================
    /** Constructor. */
    MyCompressorChain()
    {
        // set defaults for variables
        setDriveDB(SampleType(0));
        setThresholds(SampleType(-100), SampleType(0), SampleType(0));
        setSlew(SampleType(0.5));
        setTransientTime(SampleType(0.125));
        setAttackTime(SampleType(10));
        setReleaseTime(SampleType(100));
        setAdaptability(SampleType(2));
        setLowCompPower(SampleType(0.5));
        setHighCompPower(SampleType(4));
        setLowCompOffset(SampleType(1));
        setHighCompOffset(SampleType(0));
        setResponsePower(SampleType(0.5));
        setNegMix(SampleType(0.4));
        setRatio(SampleType(2));
        setVariMuMultiplier(SampleType(1));
        setKneeDB(SampleType(18));
        setSmoothTime(SampleType(3));
        setSagTime(SampleType(512));
        setMakeupDB(SampleType(0));
        setMix(SampleType(1));
    }

    //==============================================================================
    /** Sets how much to drive the compressor in dB. */
    void setDriveDB(SampleType newDriveDB)
    {
        if (driveDB == newDriveDB)
            return;

        driveDB = newDriveDB;
        driveLinear = MyCompressor<SampleType>::dBToLinear(newDriveDB);
        invDriveLinear = SampleType(1) / driveLinear;
    }

    /** Sets the compressor's thresholds in dB. */
    void setThresholds(SampleType newLowerThreshold, SampleType newUpperThreshold1, SampleType newUpperThreshold2)
    {
        compMainBelowStage.setThreshold(newLowerThreshold);
        compMainAboveStage1.setThreshold(newUpperThreshold1);
        compMainAboveStage2.setThreshold(newUpperThreshold2);
        compParallelAboveStage.setThreshold(newUpperThreshold1);
    }

    /** Sets the envelope's transient time in milliseconds. */
    void setTransientTime(SampleType newTransientTime)
    {
        compMainBelowStage.setTransientTime(newTransientTime);
        compMainAboveStage1.setTransientTime(newTransientTime);
        compMainAboveStage2.setTransientTime(newTransientTime);
        compParallelAboveStage.setTransientTime(newTransientTime);
    }

    /** Sets the envelope's attack time in milliseconds. */
    void setAttackTime(SampleType newAttackTime)
    {
        compMainBelowStage.setAttackTime(newAttackTime);
        compMainAboveStage1.setAttackTime(newAttackTime);
        compMainAboveStage2.setAttackTime(newAttackTime);
        compParallelAboveStage.setAttackTime(newAttackTime);
    }

    /** Sets the envelope's release time in milliseconds. */
    void setReleaseTime(SampleType newReleaseTime)
    {
        compMainBelowStage.setReleaseTime(newReleaseTime);
        compMainAboveStage1.setReleaseTime(newReleaseTime);
        compMainAboveStage2.setReleaseTime(newReleaseTime);
        compParallelAboveStage.setReleaseTime(newReleaseTime);
    }

    /** Sets slew as a percentage of the attack and release. Takes a value from 0 to 1. */
    void setSlew(SampleType newSlew)
    {
        compMainBelowStage.setSlew(newSlew);
        compMainAboveStage1.setSlew(newSlew);
        compMainAboveStage2.setSlew(newSlew);
        compParallelAboveStage.setSlew(newSlew);
    }

    /** Sets the program-dependent multiplier for attack and release. */
    void setAdaptability(SampleType newAdaptability)
    {
        compMainBelowStage.setAdaptability(newAdaptability);
        compMainAboveStage1.setAdaptability(newAdaptability);
        compMainAboveStage2.setAdaptability(newAdaptability);
        compParallelAboveStage.setAdaptability(newAdaptability);
    }

    /** Sets the power to raise the signal to for detection when minimal compression is happening. 1 = Peak. 2 = RMS.
        Expects a power that's 2^k, where k is an integer. */
    void setLowCompPower(SampleType newLowCompPower)
    {
        compMainBelowStage.setLowCompPower(newLowCompPower);
        compMainAboveStage1.setLowCompPower(newLowCompPower);
        compMainAboveStage2.setLowCompPower(newLowCompPower);
        compParallelAboveStage.setLowCompPower(newLowCompPower);
    }

    /** Sets the power to raise the signal to for detection when maximal compression is happening. 1 = Peak. 2 = RMS.
        Expects a power that's 2^k, where k is an integer. */
    void setHighCompPower(SampleType newHighCompPower)
    {
        compMainBelowStage.setHighCompPower(newHighCompPower);
        compMainAboveStage1.setHighCompPower(newHighCompPower);
        compMainAboveStage2.setHighCompPower(newHighCompPower);
        compParallelAboveStage.setHighCompPower(newHighCompPower);
    }

    /** Sets the offset for the signal when minimal compression is occurring. 0 = symmetric (odd harmonics). 1 = asymmetric (even harmonics). */
    void setLowCompOffset(SampleType newLowCompOffset)
    {
        compMainBelowStage.setLowCompOffset(newLowCompOffset);
        compMainAboveStage1.setLowCompOffset(newLowCompOffset);
        compMainAboveStage2.setLowCompOffset(newLowCompOffset);
        compParallelAboveStage.setLowCompOffset(newLowCompOffset);
    }

    /** Sets the offset for the signal when maximal compression is occurring. 0 = symmetric (odd harmonics). 1 = asymmetric (even harmonics). */
    void setHighCompOffset(SampleType newHighCompOffset)
    {
        compMainBelowStage.setHighCompOffset(newHighCompOffset);
        compMainAboveStage1.setHighCompOffset(newHighCompOffset);
        compMainAboveStage2.setHighCompOffset(newHighCompOffset);
        compParallelAboveStage.setHighCompOffset(newHighCompOffset);
    }

    /** Sets the power the compression amount multiplier is raised to. Expects a power that's 2^k, where k is an integer. */
    void setResponsePower(SampleType newResponsePower)
    {
        compMainBelowStage.setResponsePower(newResponsePower);
        compMainAboveStage1.setResponsePower(newResponsePower);
        compMainAboveStage2.setResponsePower(newResponsePower);
        compParallelAboveStage.setResponsePower(newResponsePower);
    }

    /** Sets amount of negator to mix in. Range: 0 - 1. */
    void setNegMix(SampleType newNegMix) {negMix=newNegMix;}

    /** Sets the compressor's ratio. */
    void setRatio(SampleType newRatio)
    {
        compMainBelowStage.setRatio(newRatio * SampleType(2)); // upwards compression should be twice the ratio
        compMainAboveStage1.setRatio(newRatio);
        compMainAboveStage2.setRatio(SampleType(-1)); // above threshold, stage 2 should always be a negation stage
        compParallelAboveStage.setRatio(SampleType(-1)); // the parallel stage should always negate
    }

    /** Sets the max compression-amount-scaled multiplier for ratio. */
    void setVariMuMultiplier(SampleType newVariMuMultiplier)
    {
        compMainBelowStage.setVariMuMultiplier(newVariMuMultiplier);
        compMainAboveStage1.setVariMuMultiplier(newVariMuMultiplier);
        compMainAboveStage2.setVariMuMultiplier(newVariMuMultiplier);
        compParallelAboveStage.setVariMuMultiplier(newVariMuMultiplier);
    }

    /** Sets the compressor's knee width in dB. */
    void setKneeDB(SampleType newKneeDB)
    {
        compMainBelowStage.setKneeDB(newKneeDB);
        compMainAboveStage1.setKneeDB(newKneeDB);
        compMainAboveStage2.setKneeDB(newKneeDB);
        compParallelAboveStage.setKneeDB(newKneeDB);
    }

    /** Sets how quickly the compressor smooths gain changes in milliseconds. */
    void setSmoothTime(SampleType newSmoothTime)
    {
        compMainBelowStage.setSmoothTime(newSmoothTime);
        compMainAboveStage1.setSmoothTime(newSmoothTime);
        compMainAboveStage2.setSmoothTime(newSmoothTime);
        compParallelAboveStage.setSmoothTime(newSmoothTime);
    }

    /** Sets the time period for measuring sag amount in milliseconds. */
    void setSagTime(SampleType newSagTime)
    {
        compMainBelowStage.setSagTime(newSagTime);
        compMainAboveStage1.setSagTime(newSagTime);
        compMainAboveStage2.setSagTime(newSagTime);
        compParallelAboveStage.setSagTime(newSagTime);
    }

    /** Sets how much to boost the post-compression signal in dB, for matching levels before the mix knob. */
    void setMakeupDB(SampleType newMakeupDB)
    {
        if (makeupDB == newMakeupDB)
            return;

        makeupDB = newMakeupDB;
        makeupLinear = MyCompressor<SampleType>::dBToLinear(newMakeupDB);
    }

    /** Sets how much of the compressor to mix in. Range: 0  - 1. */
    void setMix(SampleType newMix) {mix=newMix;}

    /** Sets values for testing. */
    void setTestValues(SampleType newTestValue1, SampleType newTestValue2, SampleType newTestValue3)
    {
        testValue1 = newTestValue1;
        testValue2 = newTestValue2;
        testValue3 = newTestValue3;
        compMainBelowStage.setTestValues(newTestValue1, newTestValue2, newTestValue3);
        compMainAboveStage1.setTestValues(newTestValue1, newTestValue2, newTestValue3);
        compMainAboveStage2.setTestValues(newTestValue1, newTestValue2, newTestValue3);
        compParallelAboveStage.setTestValues(newTestValue1, newTestValue2, newTestValue3);
    }

    //==============================================================================
    /** Initialises the processor. */
    void prepare(juce::dsp::ProcessSpec const& spec)
    {
        // prepare all stages
        compMainBelowStage.prepare(spec);
        compMainAboveStage1.prepare(spec);
        compMainAboveStage2.prepare(spec);
        compParallelAboveStage.prepare(spec);

        // set modes for all stages
        compMainBelowStage.setMode(CompressorMode::BelowThreshold);
        compMainAboveStage1.setMode(CompressorMode::AboveThreshold);
        compMainAboveStage2.setMode(CompressorMode::AboveThreshold);
        compParallelAboveStage.setMode(CompressorMode::AboveThreshold);

        // reset all stages
        reset();
    }

    //==============================================================================
    /** Resets the internal state variables of the processor. */
    void reset()
    {
        compMainBelowStage.reset();
        compMainAboveStage1.reset();
        compMainAboveStage2.reset();
        compParallelAboveStage.reset();
    }

    //==============================================================================
    /** Processes a single sample on a given channel. */
    SampleType processSample(int channel, SampleType inputValue) noexcept
        {return processSample(channel, inputValue, inputValue);}

    /** Processes a single sample on a given channel. */
    SampleType processSample(int channel, SampleType inputValue, SampleType detectionValue) noexcept
    {
        // apply drive
        SampleType const drivenInputValue = inputValue * driveLinear;
        SampleType const drivenDetectionValue = detectionValue * driveLinear;

        // process main path
        // upwards compression needs to come first to avoid it affecting a negated signal that
        // falls beneath the threshold
        SampleType compMain;
        compMain = compMainBelowStage.processSample(channel, drivenInputValue, drivenDetectionValue);
        compMain = compMainAboveStage1.processSample(channel, compMain, drivenDetectionValue);
        compMain = compMainAboveStage2.processSample(channel, compMain, drivenDetectionValue);

        // process parallel path
        SampleType compParallel = SampleType(0);
        if (negMix != SampleType(0))
            compParallel = compParallelAboveStage.processSample(channel, drivenInputValue, drivenDetectionValue);

        // mix main and parallel paths
        SampleType compValue = compMain     * (SampleType(1) - negMix) +
                               compParallel *                  negMix;

        // apply inverse drive
        compValue *= invDriveLinear;

        // apply makeup
        compValue *= makeupLinear;

        // return dry/wet mix
        return inputValue * (SampleType(1) - mix) +
               compValue  *                  mix;
    }

    //==============================================================================
private:
    MyCompressor<SampleType> compMainBelowStage;
    MyCompressor<SampleType> compMainAboveStage1;
    MyCompressor<SampleType> compMainAboveStage2;
    MyCompressor<SampleType> compParallelAboveStage;

    static constexpr SampleType qNAN = std::numeric_limits<SampleType>::quiet_NaN();

    SampleType driveDB=qNAN, negMix=qNAN, makeupDB=qNAN, mix=qNAN;
    SampleType driveLinear=qNAN, invDriveLinear=qNAN, makeupLinear=qNAN;

    SampleType testValue1=qNAN, testValue2=qNAN, testValue3=qNAN;
};
