/*
 * Copyright (c) 2026 YimRakhee
 * Licensed under the GNU General Public License v3.0 (or later).
 * See the LICENSE file in the project root for more information.
 */

use std::f32::consts::PI;
use wide::{f32x4, CmpLt, CmpGt};

#[derive(Clone, Default)]
pub struct Biquad {
    b0: f32, b1: f32, b2: f32,
    a1: f32, a2: f32,
    x1: f32, x2: f32,
    y1: f32, y2: f32,
}

impl Biquad {
    pub fn process(&mut self, x: f32) -> f32 {
        let y = self.b0 * x + self.b1 * self.x1 + self.b2 * self.x2 - self.a1 * self.y1 - self.a2 * self.y2;
        self.x2 = self.x1; self.x1 = x;
        self.y2 = self.y1; self.y1 = y;
        y
    }

    // pub fn make_low_shelf(&mut self, sample_rate: f32, freq: f32, q: f32, gain_linear: f32) {
    //     let a = gain_linear.sqrt();
    //     let w0 = 2.0 * PI * freq / sample_rate;
    //     let alpha = w0.sin() / (2.0 * q);
    //     let cos_w0 = w0.cos();
    //     let a_plus_1 = a + 1.0; let a_minus_1 = a - 1.0;
    //     let two_sqrt_a_alpha = 2.0 * a.sqrt() * alpha;
    //
    //     let b0 = a * (a_plus_1 - a_minus_1 * cos_w0 + two_sqrt_a_alpha);
    //     let b1 = 2.0 * a * (a_minus_1 - a_plus_1 * cos_w0);
    //     let b2 = a * (a_plus_1 - a_minus_1 * cos_w0 - two_sqrt_a_alpha);
    //     let a0 = a_plus_1 + a_minus_1 * cos_w0 + two_sqrt_a_alpha;
    //     let a1 = -2.0 * (a_minus_1 + a_plus_1 * cos_w0);
    //     let a2 = a_plus_1 + a_minus_1 * cos_w0 - two_sqrt_a_alpha;
    //
    //     self.set_coeffs(b0, b1, b2, a0, a1, a2);
    // }

    // pub fn make_high_shelf(&mut self, sample_rate: f32, freq: f32, q: f32, gain_linear: f32) {
    //     let a = gain_linear.sqrt();
    //     let w0 = 2.0 * PI * freq / sample_rate;
    //     let alpha = w0.sin() / (2.0 * q);
    //     let cos_w0 = w0.cos();
    //     let a_plus_1 = a + 1.0; let a_minus_1 = a - 1.0;
    //     let two_sqrt_a_alpha = 2.0 * a.sqrt() * alpha;
    //
    //     let b0 = a * (a_plus_1 + a_minus_1 * cos_w0 + two_sqrt_a_alpha);
    //     let b1 = -2.0 * a * (a_minus_1 + a_plus_1 * cos_w0);
    //     let b2 = a * (a_plus_1 + a_minus_1 * cos_w0 - two_sqrt_a_alpha);
    //     let a0 = a_plus_1 - a_minus_1 * cos_w0 + two_sqrt_a_alpha;
    //     let a1 = 2.0 * (a_minus_1 - a_plus_1 * cos_w0);
    //     let a2 = a_plus_1 - a_minus_1 * cos_w0 - two_sqrt_a_alpha;
    //
    //     self.set_coeffs(b0, b1, b2, a0, a1, a2);
    // }

    // pub fn make_peak(&mut self, sample_rate: f32, freq: f32, q: f32, gain_db: f32) {
    //     let a = 10.0f32.powf(gain_db / 40.0);
    //     let w0 = 2.0 * PI * freq / sample_rate;
    //     let alpha = w0.sin() / (2.0 * q);
    //
    //     let b0 = 1.0 + alpha * a;
    //     let b1 = -2.0 * w0.cos();
    //     let b2 = 1.0 - alpha * a;
    //     let a0 = 1.0 + alpha / a;
    //     let a1 = -2.0 * w0.cos();
    //     let a2 = 1.0 - alpha / a;
    //
    //     self.set_coeffs(b0, b1, b2, a0, a1, a2);
    // }

    pub fn make_high_pass(&mut self, sample_rate: f32, freq: f32, q: f32) {
        let w0 = 2.0 * PI * freq / sample_rate;
        let alpha = w0.sin() / (2.0 * q);
        let cos_w0 = w0.cos();

        let b0 = (1.0 + cos_w0) / 2.0;
        let b1 = -(1.0 + cos_w0);
        let b2 = (1.0 + cos_w0) / 2.0;
        let a0 = 1.0 + alpha;
        let a1 = -2.0 * cos_w0;
        let a2 = 1.0 - alpha;

        self.set_coeffs(b0, b1, b2, a0, a1, a2);
    }

    // pub fn make_low_pass(&mut self, sample_rate: f32, freq: f32, q: f32) {
    //     let w0 = 2.0 * PI * freq / sample_rate;
    //     let alpha = w0.sin() / (2.0 * q);
    //     let cos_w0 = w0.cos();
    //
    //     let b0 = (1.0 - cos_w0) / 2.0;
    //     let b1 = 1.0 - cos_w0;
    //     let b2 = (1.0 - cos_w0) / 2.0;
    //     let a0 = 1.0 + alpha;
    //     let a1 = -2.0 * cos_w0;
    //     let a2 = 1.0 - alpha;
    //
    //     self.set_coeffs(b0, b1, b2, a0, a1, a2);
    // }

    fn set_coeffs(&mut self, b0: f32, b1: f32, b2: f32, a0: f32, a1: f32, a2: f32) {
        self.b0 = b0 / a0; self.b1 = b1 / a0; self.b2 = b2 / a0;
        self.a1 = a1 / a0; self.a2 = a2 / a0;
    }
}

// pub fn db_to_gain(db: f32) -> f32 {
//     10.0f32.powf(db / 20.0)
// }
//
// pub fn gain_to_db(gain: f32) -> f32 {
//     20.0 * gain.log10()
// }

#[derive(Clone, Default)]
pub struct Allpass {
    coeff: f32,
    z: f32,
}

impl Allpass {
    pub const fn new(coeff: f32) -> Self {
        Self { coeff, z: 0.0 }
    }

    #[inline(always)]
    pub fn process(&mut self, input: f32) -> f32 {
        let out = input * self.coeff + self.z;
        self.z = input - out * self.coeff;
        out
    }
}

pub struct Oversampler2x {
    up_ap0: [Allpass; 2],
    up_ap1: [Allpass; 2],
    down_ap0: [Allpass; 2],
    down_ap1: [Allpass; 2],
    down_prev_odd: f32,
}

impl Oversampler2x {
    pub fn new() -> Self {
        let c0 = 0.079866426;
        let c1 = 0.315750839;
        let c2 = 0.655866166;
        let c3 = 0.902927237;

        Self {
            up_ap0: [Allpass::new(c0), Allpass::new(c2)],
            up_ap1: [Allpass::new(c1), Allpass::new(c3)],
            down_ap0: [Allpass::new(c0), Allpass::new(c2)],
            down_ap1: [Allpass::new(c1), Allpass::new(c3)],
            down_prev_odd: 0.0,
        }
    }

    pub fn reset(&mut self, _sample_rate: f32) {
        *self = Self::new();
    }

    #[inline(always)]
    pub fn upsample(&mut self, sample: f32) -> [f32; 2] {
        let mut y0 = self.up_ap0[0].process(sample);
        y0 = self.up_ap0[1].process(y0);
        let mut y1 = self.up_ap1[0].process(sample);
        y1 = self.up_ap1[1].process(y1);

        [y0, y1]
    }

    #[inline(always)]
    pub fn downsample(&mut self, samples: [f32; 2]) -> f32 {
        let mut y0 = self.down_ap0[0].process(samples[0]);
        y0 = self.down_ap0[1].process(y0);
        let mut y1 = self.down_ap1[0].process(self.down_prev_odd);
        y1 = self.down_ap1[1].process(y1);
        self.down_prev_odd = samples[1];
        (y0 + y1) * 0.5
    }
}

#[inline(always)]
pub fn soft_clip_simd(samples: [f32; 2], drive: f32) -> [f32; 2] {
    let x = f32x4::new([samples[0] * drive, samples[1] * drive, 0.0, 0.0]);
    let x_sq = x * x;
    let den = f32x4::splat(1.0) + f32x4::splat(0.28) * x_sq;
    let result = x / den;

    let arr = result.to_array();
    [arr[0], arr[1]]
}

#[inline(always)]
pub fn branchless_clamp_simd(val: f32x4, min_val: f32, max_val: f32) -> f32x4 {
    let min_vec = f32x4::splat(min_val);
    let max_vec = f32x4::splat(max_val);

    let under = val.cmp_lt(min_vec);
    let clamped_min = under.blend(min_vec, val);

    let over = clamped_min.cmp_gt(max_vec);
    over.blend(max_vec, clamped_min)
}
