package party.bami.eqfix.Equalizer;

// Basic biquad based IIR filter class with multiple possible filter types
public class Filter {

    // filter parameters for biquad filter
    private float a0, a1, a2;
    private float b1, b2;

    // constants used for calculating the coefficients
    private float cosW0, sinW0, w0, alpha;

    // center frequency
    private float f0;

    // sample rate
    private int fs;

    // bandwidth of the filter in octaves
    private float bw;

    // gain of the frequency band in decibels (only used in peaking mode)
    private float gain;

    // stores the type of filter
    private filterType type;

    // available filter types
    public enum filterType {
        HIGHPASS, LOWPASS, PEAKING
    }

    public Filter(int sampleRate, float centerFreq, float bandwidth, filterType type) {
        this.fs = sampleRate;
        this.f0 = centerFreq;
        this.type = type;
        this.bw = bandwidth;
        this.gain = 0.0f;
        prepare();
    }

    // check for what type of filter to prepare the coefficients
    private void prepare() {

        this.w0 = 2 * (float)Math.PI * (this.f0/(float)this.fs);

        // precalculate values as they are used more than once
        this.cosW0 = (float)Math.cos(w0);
        this.sinW0 = (float)Math.sin(w0);
        this.alpha = sinW0 * (float)Math.sinh((Math.log(2)/2) * this.bw * (w0/sinW0));

        switch (this.type) {
            case LOWPASS:
                prepareLowpass();
                break;
            case HIGHPASS:
                prepareHighpass();
                break;
            case PEAKING:
                preparePeaking();
                break;
        }
    }

    // create coefficients for a lowpass filter
    private void prepareLowpass() {
        // calculate coefficients
        float b0 = (1.0f - cosW0)/2.0f;
        float b1 = 1.0f - cosW0;
        float b2 = (1 - cosW0)/2;
        float a0 = 1 + alpha;
        float a1 = -2.0f * cosW0;
        float a2 = 1 - alpha;

        // normalise coefficients
        this.a0 = b0/a0;
        this.a1 = b1/a0;
        this.a2 = b2/a0;
        this.b1 = a1/a0;
        this.b2 = a2/a0;
    }

    // prepare coefficients for highpass filter
    private void prepareHighpass() {
        // calculate base coefficients
        float b0 = (1.0f + cosW0)/2.0f;
        float b1 = -(1.0f + cosW0);
        float b2 = (1.0f + cosW0)/2;
        float a0 = 1.0f + alpha;
        float a1 = -2.0f * cosW0;
        float a2 = 1.0f - alpha;

        // normalise coefficients
        this.a0 = b0/a0;
        this.a1 = b1/a0;
        this.a2 = b2/a0;
        this.b1 = a1/a0;
        this.b2 = a2/a0;
    }

    // prepare coefficients for parametric equalizer
    private void preparePeaking() {
        float amplitude = (float)Math.pow(10.0, (gain/40));

        float b0 = 1.0f + alpha * amplitude;
        float b1 = -2.0f * cosW0;
        float b2 = 1.0f - alpha * amplitude;
        float a0 = 1.0f + alpha/amplitude;
        float a1 = -2.0f * cosW0;
        float a2 = 1.0f - alpha/amplitude;

        // normalise coefficients
        this.a0 = b0/a0;
        this.a1 = b1/a0;
        this.a2 = b2/a0;
        this.b1 = a1/a0;
        this.b2 = a2/a0;

    }

    // filters the data given in the data buffer and returns a new data buffer
    public float[] filter(float[] data) {

        float[] output = new float[data.length];

        for (int n = 0; n < data.length; n++) {

            float x;
            float x1;
            float x2;

            float y1;
            float y2;

            if (n == 0) {
                x1 = 0;
                x2 = 0;
                y1 = 0;
                y2 = 0;
            } else if (n == 1) {
                x1 = data[n-1];
                x2 = 0;
                y1 = output[n-1];
                y2 = 0;
            } else {
                x1 = data[n-1];
                x2 = data[n-2];
                y1 = output[n-1];
                y2 = output[n-2];
            }

            x = data[n];

            float s1 = this.a0 * x;
            float s2 = this.a1 * x1;
            float s3 = this.a2 * x2;
            float s4 = this.b1 * -y1;
            float s5 = this.b2 * -y2;

            output[n] = (s1 + s2 + s3 + s4 + s5);

        }

        return output;
    }

    /********** Getters and Setters ***********/
    // return the center frequency
    public float getFrequency() {
        return this.f0;
    }

    // set the center frequency of the current filter
    public void setFrequency(float frequency) {
        this.f0 = frequency;
        prepare();
    }

    // get the current active samplerate of the filter
    public int getSampleRate() {
        return this.fs;
    }

    // set the samplerate of the filter
    public void setSampleRate(int sampleRate) {
        this.fs = sampleRate;
        prepare();
    }

    // returns the quality factor of the current filter
    public float getBandwidth() {
        return this.bw;
    }

    // set bandwidth of the filter, bw is in octaves
    public void setBandwidth(float bw) {
        this.bw = bw;
        prepare();
    }

    // return the current dB value
    public float getGain() {
        return this.gain;
    }

    // set the cut/boost in dB
    public void setGain(float value) {
        this.gain = value;
        prepare();
    }

}
