/*
 * 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 nih_plug::prelude::*;
use std::sync::Arc;
use std::f32::consts::PI;

mod gui;
mod utils;

#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;

#[derive(Params)]
pub struct LetsDistParams {
    #[id = "distortion"] pub distortion: FloatParam,
    #[id = "filter"] pub filter: FloatParam,
    #[id = "slew"] pub slew: FloatParam,
    #[id = "dry"] pub dry: FloatParam,
    #[id = "wet"] pub wet: FloatParam,
    #[id = "fxbypass"] pub fx_bypass: BoolParam,
}

impl Default for LetsDistParams {
    fn default() -> Self {
        Self {
            distortion: FloatParam::new("Distortion", 60.0, FloatRange::Linear { min: 0.0, max: 100.0 })
            .with_smoother(SmoothingStyle::Linear(10.0)),
            filter: FloatParam::new("Filter", 35.0, FloatRange::Linear { min: 0.0, max: 100.0 })
            .with_smoother(SmoothingStyle::Linear(10.0)),
            slew: FloatParam::new("Slew", 30.0, FloatRange::Linear { min: 0.0, max: 100.0 })
            .with_smoother(SmoothingStyle::Linear(10.0)),
            dry: FloatParam::new("Dry", 30.0, FloatRange::Linear { min: 0.0, max: 100.0 })
            .with_smoother(SmoothingStyle::Linear(10.0)),
            wet: FloatParam::new("Wet", 40.0, FloatRange::Linear { min: 0.0, max: 100.0 })
            .with_smoother(SmoothingStyle::Linear(10.0)),
            fx_bypass: BoolParam::new("Bypass", false),
        }
    }
}

pub struct LetsDist {
    params: Arc<LetsDistParams>,
    sample_rate: f32,

    last_out: [f32; 2],
    filter_state: [f32; 2],
    oversamplers: [utils::Oversampler2x; 2],
    dc_blockers: [utils::Biquad; 2],
}

impl Default for LetsDist {
    fn default() -> Self {
        Self {
            params: Arc::new(LetsDistParams::default()),
            sample_rate: 44100.0,

            last_out: [0.0; 2],
            filter_state: [0.0; 2],
            oversamplers: [utils::Oversampler2x::new(), utils::Oversampler2x::new()],
            dc_blockers: [utils::Biquad::default(), utils::Biquad::default()],
        }
    }
}

impl Plugin for LetsDist {
    const NAME: &'static str = "Letsdist";
    const VENDOR: &'static str = "yimrakhee";
    const URL: &'static str = "https://codeberg.org/yimrakhee/letsdist";
    const EMAIL: &'static str = "yimrakhee@gmail.com";
    const VERSION: &'static str = env!("CARGO_PKG_VERSION");

    type SysExMessage = ();
    type BackgroundTask = ();

    const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
        AudioIOLayout {
            main_input_channels: NonZeroU32::new(1),
            main_output_channels: NonZeroU32::new(1),
            ..AudioIOLayout::const_default()
        },
        AudioIOLayout {
            main_input_channels: NonZeroU32::new(2),
            main_output_channels: NonZeroU32::new(2),
            ..AudioIOLayout::const_default()
        },
    ];

    fn params(&self) -> Arc<dyn Params> { self.params.clone() }

    fn initialize(&mut self, _audio_io_layout: &AudioIOLayout, buffer_config: &BufferConfig, _context: &mut impl InitContext<Self>) -> bool {
        self.sample_rate = buffer_config.sample_rate;

        for ch in 0..2 {
            self.oversamplers[ch].reset(self.sample_rate);

            self.dc_blockers[ch].make_high_pass(self.sample_rate, 10.0, 0.707);

            self.last_out[ch] = 0.0;
            self.filter_state[ch] = 0.0;
        }
        true
    }

    fn editor(&mut self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
        gui::create(self.params.clone())
    }

    fn process(&mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, _context: &mut impl ProcessContext<Self>) -> ProcessStatus {
        if self.params.fx_bypass.value() {
            return ProcessStatus::Normal;
        }

        let internal_sr = self.sample_rate * 2.0;
        let dt = 1.0 / internal_sr;

        for channel_samples in buffer.iter_samples() {

            let dist_val = self.params.distortion.smoothed.next() / 100.0;
            let filter_val = self.params.filter.smoothed.next() / 100.0;
            let slew_val = self.params.slew.smoothed.next() / 100.0;
            let dry_gain = self.params.dry.smoothed.next() / 100.0;
            let wet_gain = self.params.wet.smoothed.next() / 100.0;

            let dist_gain = (dist_val * 6.0).exp();

            let cutoff_hz = 20000.0 * 0.01_f32.powf(filter_val);
            let rc = 1.0 / (2.0 * PI * cutoff_hz);
            let alpha = dt / (rc + dt);

            let slew_limit = 0.8 * 0.005_f32.powf(slew_val);

           for (ch, sample) in channel_samples.into_iter().enumerate() {
                if ch >= 2 { break; }
                let original_clean_sample = *sample;

                let upsampled = self.oversamplers[ch].upsample(*sample);
                let clipped_arr = utils::soft_clip_simd(upsampled, dist_gain);

                let mut processed_upsampled = [0.0; 2];

                for i in 0..2 {
                    let clipped = clipped_arr[i];

                    let diff = clipped - self.last_out[ch];
                    let delta = diff.clamp(-slew_limit, slew_limit);
                    let slewed = self.last_out[ch] + delta;
                    self.last_out[ch] = slewed;

                    self.filter_state[ch] = self.filter_state[ch] + alpha * (slewed - self.filter_state[ch]);
                    processed_upsampled[i] = self.filter_state[ch];
                }

                let wet_simd = wide::f32x4::new([processed_upsampled[0], processed_upsampled[1], 0.0, 0.0]);
                let final_clipped_simd = utils::branchless_clamp_simd(wet_simd, -1.0, 1.0);
                let final_clipped_arr = final_clipped_simd.to_array();

                let mut out_sample = self.oversamplers[ch].downsample([final_clipped_arr[0], final_clipped_arr[1]]);
                out_sample = self.dc_blockers[ch].process(out_sample);
                *sample = (original_clean_sample * dry_gain) + (out_sample * wet_gain);
            }
        }

        ProcessStatus::Normal
    }
}

impl ClapPlugin for LetsDist {
    const CLAP_ID: &'static str = "com.yimrakhee.letsdist";
    const CLAP_DESCRIPTION: Option<&'static str> = Some("Powerful Distortion");
    const CLAP_MANUAL_URL: Option<&'static str> = None;
    const CLAP_SUPPORT_URL: Option<&'static str> = None;
    const CLAP_FEATURES: &'static [ClapFeature] = &[ClapFeature::AudioEffect, ClapFeature::Distortion];
}

impl Vst3Plugin for LetsDist {
    const VST3_CLASS_ID: [u8; 16] = *b"YimRakheeLetDist";
    const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[Vst3SubCategory::Fx, Vst3SubCategory::Distortion];
}

nih_export_clap!(LetsDist);
nih_export_vst3!(LetsDist);
