/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */

#ifndef OBOE_MULTICHANNEL_RESAMPLER_H
#define OBOE_MULTICHANNEL_RESAMPLER_H

#include <memory>
#include <vector>
#include <sys/types.h>
#include <unistd.h>
#include <cassert>

#ifndef MCR_USE_KAISER
// It appears from the spectrogram that the HyperbolicCosine window leads to fewer artifacts.
// And it is faster to calculate.
#define MCR_USE_KAISER 0
#endif

#if MCR_USE_KAISER
#include "KaiserWindow.h"
#else
#include "HyperbolicCosineWindow.h"
#endif

namespace resampler {

class MultiChannelResampler {

public:

    enum class Quality : int32_t {
        Fastest,
        Low,
        Medium,
        High,
        Best,
    };

    class Builder {
    public:
        /**
         * Construct an optimal resampler based on the specified parameters.
         * @return address of a resampler
         */
        MultiChannelResampler *build();

        /**
         * The number of taps in the resampling filter.
         * More taps gives better quality but uses more CPU time.
         * This typically ranges from 4 to 64. Default is 16.
         *
         * For polyphase filters, numTaps must be a multiple of four for loop unrolling.
         * @param numTaps number of taps for the filter
         * @return address of this builder for chaining calls
         */
        Builder *setNumTaps(int32_t numTaps) {
            mNumTaps = numTaps;
            return this;
        }

        /**
         * Use 1 for mono, 2 for stereo, etc. Default is 1.
         *
         * @param channelCount number of channels
         * @return address of this builder for chaining calls
         */
        Builder *setChannelCount(int32_t channelCount) {
            mChannelCount = channelCount;
            return this;
        }

        /**
         * Default is 48000.
         *
         * @param inputRate sample rate of the input stream
         * @return address of this builder for chaining calls
         */
        Builder *setInputRate(int32_t inputRate) {
            mInputRate = inputRate;
            return this;
        }

        /**
         * Default is 48000.
         *
         * @param outputRate sample rate of the output stream
         * @return address of this builder for chaining calls
         */
        Builder *setOutputRate(int32_t outputRate) {
            mOutputRate = outputRate;
            return this;
        }

        /**
         * Set cutoff frequency relative to the Nyquist rate of the output sample rate.
         * Set to 1.0 to match the Nyquist frequency.
         * Set lower to reduce aliasing.
         * Default is 0.70.
         *
         * @param normalizedCutoff anti-aliasing filter cutoff
         * @return address of this builder for chaining calls
         */
        Builder *setNormalizedCutoff(float normalizedCutoff) {
            mNormalizedCutoff = normalizedCutoff;
            return this;
        }

        int32_t getNumTaps() const {
            return mNumTaps;
        }

        int32_t getChannelCount() const {
            return mChannelCount;
        }

        int32_t getInputRate() const {
            return mInputRate;
        }

        int32_t getOutputRate() const {
            return mOutputRate;
        }

        float getNormalizedCutoff() const {
            return mNormalizedCutoff;
        }

    protected:
        int32_t mChannelCount = 1;
        int32_t mNumTaps = 16;
        int32_t mInputRate = 48000;
        int32_t mOutputRate = 48000;
        float   mNormalizedCutoff = kDefaultNormalizedCutoff;
    };

    virtual ~MultiChannelResampler() = default;

    /**
     * Factory method for making a resampler that is optimal for the given inputs.
     *
     * @param channelCount number of channels, 2 for stereo
     * @param inputRate sample rate of the input stream
     * @param outputRate  sample rate of the output stream
     * @param quality higher quality sounds better but uses more CPU
     * @return an optimal resampler
     */
    static MultiChannelResampler *make(int32_t channelCount,
                                       int32_t inputRate,
                                       int32_t outputRate,
                                       Quality quality);

    bool isWriteNeeded() const {
        return mIntegerPhase >= mDenominator;
    }

    /**
     * Write a frame containing N samples.
     *
     * @param frame pointer to the first sample in a frame
     */
    void writeNextFrame(const float *frame) {
        writeFrame(frame);
        advanceWrite();
    }

    /**
     * Read a frame containing N samples.
     *
     * @param frame pointer to the first sample in a frame
     */
    void readNextFrame(float *frame) {
        readFrame(frame);
        advanceRead();
    }

    int getNumTaps() const {
        return mNumTaps;
    }

    int getChannelCount() const {
        return mChannelCount;
    }

    static float hammingWindow(float radians, float spread);

    static float sinc(float radians);

protected:

    explicit MultiChannelResampler(const MultiChannelResampler::Builder &builder);

    /**
     * Write a frame containing N samples.
     * Call advanceWrite() after calling this.
     * @param frame pointer to the first sample in a frame
     */
    virtual void writeFrame(const float *frame);

    /**
     * Read a frame containing N samples using interpolation.
     * Call advanceRead() after calling this.
     * @param frame pointer to the first sample in a frame
     */
    virtual void readFrame(float *frame) = 0;

    void advanceWrite() {
        mIntegerPhase -= mDenominator;
    }

    void advanceRead() {
        mIntegerPhase += mNumerator;
    }

    /**
     * Generate the filter coefficients in optimal order.
     * @param inputRate sample rate of the input stream
     * @param outputRate  sample rate of the output stream
     * @param numRows number of rows in the array that contain a set of tap coefficients
     * @param phaseIncrement how much to increment the phase between rows
     * @param normalizedCutoff filter cutoff frequency normalized to Nyquist rate of output
     */
    void generateCoefficients(int32_t inputRate,
                              int32_t outputRate,
                              int32_t numRows,
                              double phaseIncrement,
                              float normalizedCutoff);


    int32_t getIntegerPhase() {
        return mIntegerPhase;
    }

    static constexpr int kMaxCoefficients = 8 * 1024;
    std::vector<float>   mCoefficients;

    const int            mNumTaps;
    int                  mCursor = 0;
    std::vector<float>   mX;           // delayed input values for the FIR
    std::vector<float>   mSingleFrame; // one frame for temporary use
    int32_t              mIntegerPhase = 0;
    int32_t              mNumerator = 0;
    int32_t              mDenominator = 0;


private:

#if MCR_USE_KAISER
    KaiserWindow           mKaiserWindow;
#else
    HyperbolicCosineWindow mCoshWindow;
#endif

    static constexpr float kDefaultNormalizedCutoff = 0.70f;

    const int              mChannelCount;
};

}
#endif //OBOE_MULTICHANNEL_RESAMPLER_H
