use crossbeam_channel::{self, Receiver, SendError, Sender};
use crossbeam_utils::atomic::AtomicCell;
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::sync::Arc;

use super::AudioSource;
use crate::utils::chrono;

pub const DEFAULT_BPM: u32 = 120;
pub const DEFAULT_METER: Meter = Meter::_4_4;

const CHANNEL_SIZE: usize = 256;

const STRONG_BEAT_VOLUME: f32 = 1.0;
const WEAK_BEAT_VOLUME: f32 = 0.2;

#[derive(Clone, Copy, PartialEq)]
pub enum Meter {
    _2_4,
    _3_4,
    _4_4,
    _6_8,
}

impl Meter {
    pub fn as_tuple(self) -> (u32, u32) {
        match self {
            Meter::_2_4 => (2, 4),
            Meter::_3_4 => (3, 4),
            Meter::_4_4 => (4, 4),
            Meter::_6_8 => (6, 8),
        }
    }
}

impl ToString for Meter {
    fn to_string(&self) -> String {
        let (n, d) = self.as_tuple();
        format!("{}/{}", n, d)
    }
}

pub enum Message {
    SetMeter(Meter),
    SetBPM(u32),
}

#[derive(Clone)]
pub struct Handle {
    messages: Sender<Message>,
    bpm: Arc<AtomicU32>,
    timestamp: Arc<AtomicU64>,
    meter: Arc<AtomicCell<Meter>>,
}

impl Handle {
    pub fn send(&self, msg: Message) -> Result<(), SendError<Message>> {
        self.messages.send(msg)
    }

    pub fn get_bpm(&self) -> u32 {
        self.bpm.load(Ordering::SeqCst)
    }

    pub fn get_meter(&self) -> Meter {
        self.meter.load()
    }

    pub fn get_timestamp(&self) -> u64 {
        self.timestamp.load(Ordering::SeqCst)
    }
}

pub struct Metronome {
    // Settings
    bpm: Arc<AtomicU32>,
    meter: Arc<AtomicCell<Meter>>,
    sample_rate: u32,
    beat_period_strong: u32,
    beat_period_weak: u32,
    // State
    position: u32,
    timestamp: Arc<AtomicU64>,
    // Message queue
    messages: Receiver<Message>,
}

impl Metronome {
    pub fn new(sample_rate: u32) -> (Metronome, Handle) {
        let (tx, rx) = crossbeam_channel::bounded(CHANNEL_SIZE);
        let timestamp = Arc::new(AtomicU64::new(0));
        let bpm = Arc::new(AtomicU32::new(DEFAULT_BPM));
        let meter = Arc::new(AtomicCell::new(DEFAULT_METER));

        let handle = Handle {
            messages: tx,
            bpm: bpm.clone(),
            timestamp: timestamp.clone(),
            meter: meter.clone(),
        };

        let mut metronome = Metronome {
            bpm,
            meter,
            sample_rate,
            beat_period_strong: 0,
            beat_period_weak: 0,
            position: 0,
            timestamp,
            messages: rx,
        };
        metronome.recalculate_beat_periods();
        (metronome, handle)
    }

    pub fn set_meter(&mut self, meter: Meter) {
        self.meter.store(meter);
        self.recalculate_beat_periods();
    }

    pub fn set_bpm(&mut self, bpm: u32) {
        self.bpm.store(bpm, Ordering::SeqCst);
        self.recalculate_beat_periods();
    }

    fn recalculate_beat_periods(&mut self) {
        let (numerator, _) = self.meter.load().as_tuple();
        self.beat_period_weak = self.sample_rate * 60 / self.bpm.load(Ordering::SeqCst);
        self.beat_period_strong = numerator * self.beat_period_weak;
    }
}

impl AudioSource for Metronome {
    fn get_sample(&mut self) -> f32 {
        // Calculate position
        self.position = (self.position + 1) % (self.sample_rate * 60);
        let elapsed_strong = self.position % self.beat_period_strong;
        let elapsed_weak = self.position % self.beat_period_weak;
        // Timestamp
        if elapsed_strong == 0 {
            self.timestamp.store(chrono::elapsed_ms(), Ordering::SeqCst);
        }
        // Sample calculation
        // TODO: Refactor
        let elapsed = std::cmp::min(elapsed_strong, elapsed_weak);
        let volume = if elapsed_strong <= elapsed_weak {
            STRONG_BEAT_VOLUME
        } else {
            WEAK_BEAT_VOLUME
        };
        let fade = 1.0 / (elapsed as f32 * 600.0 / self.beat_period_weak as f32).exp();
        (((self.position as f32 / self.sample_rate as f32) * 4400.0 * std::f32::consts::PI).sin()
            * fade
            * 5.0)
            .min(volume)
            .max(-volume)
    }

    fn process_messages(&mut self) {
        match self.messages.try_recv() {
            Ok(Message::SetMeter(meter)) => self.set_meter(meter),
            Ok(Message::SetBPM(bpm)) => self.set_bpm(bpm),
            _ => {}
        }
    }
}
