// Copyright (c) 2021 Weird Constructor <weirdconstructor@gmail.com>
// This file is a part of HexoDSP. Released under GPL-3.0-or-later.
// See README.md and COPYING for details.

mod pattern;
mod sequencer;

use ringbuf::{RingBuffer, Producer, Consumer};

use std::sync::{Arc, Mutex};

pub const MAX_COLS         : usize = 6;
pub const MAX_PATTERN_LEN  : usize = 256;
pub const MAX_RINGBUF_SIZE : usize = 64;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum PatternColType {
    Note,
    Step,
    Value,
    Gate,
}

pub use pattern::{PatternData, UIPatternModel};
pub use sequencer::PatternSequencer;

#[derive(Debug, Clone, Copy)]
pub enum PatternUpdateMsg {
    UpdateColumn {
        col:         usize,
        col_type:    PatternColType,
        pattern_len: usize,
        data:        [(f32, u8); MAX_PATTERN_LEN]
    },
}

pub struct Tracker {
    data:      Arc<Mutex<PatternData>>,
    data_prod: Producer<PatternUpdateMsg>,
    seq:       Option<PatternSequencer>,
    seq_cons:  Option<Consumer<PatternUpdateMsg>>,
}

impl Clone for Tracker {
    fn clone(&self) -> Self { Tracker::new() }
}


pub struct TrackerBackend {
    seq:        PatternSequencer,
    seq_cons:   Consumer<PatternUpdateMsg>,
    col_types:  [PatternColType; MAX_COLS],
}

impl std::fmt::Debug for TrackerBackend {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Tracker")
         .field("col_types", &self.col_types)
         .field("seq",       &"PatternSequencer")
         .field("seq_cons",  &"RingbufConsumer")
         .finish()
    }
}

impl Tracker {
    pub fn new() -> Self {
        let rb = RingBuffer::new(MAX_RINGBUF_SIZE);
        let (prod, con) = rb.split();

        Self {
            data:      Arc::new(Mutex::new(PatternData::new(MAX_PATTERN_LEN))),
            data_prod: prod,
            seq:       Some(PatternSequencer::new(MAX_PATTERN_LEN)),
            seq_cons:  Some(con),
        }
    }

    pub fn data(&self) -> Arc<Mutex<PatternData>> { self.data.clone() }

    pub fn send_one_update(&mut self) -> bool {
        let mut data = self.data.lock().unwrap();

        for col in 0..MAX_COLS {
            if data.col_is_modified_reset(col) {
                data.sync_out_data(col);
                let out_data = data.get_out_data();
                let msg =
                    PatternUpdateMsg::UpdateColumn {
                        col_type:    data.col_type(col),
                        pattern_len: data.rows(),
                        data:        out_data[col],
                        col,
                    };

                let _ = self.data_prod.push(msg);

                return true;
            }
        }

        false
    }

    pub fn get_backend(&mut self) -> TrackerBackend {
        if self.seq.is_none() {
            let rb = RingBuffer::new(MAX_RINGBUF_SIZE);
            let (prod, con) = rb.split();

            self.seq        = Some(PatternSequencer::new(MAX_PATTERN_LEN));
            self.data_prod  = prod;
            self.seq_cons   = Some(con);
        }

        let seq      = self.seq.take().unwrap();
        let seq_cons = self.seq_cons.take().unwrap();

        TrackerBackend {
            seq,
            seq_cons,
            col_types: [PatternColType::Value; MAX_COLS],
        }
    }
}

impl TrackerBackend {
    pub fn check_updates(&mut self) -> bool {
        if let Some(msg) = self.seq_cons.pop() {
            match msg {
                PatternUpdateMsg::UpdateColumn { col, col_type, pattern_len, data } => {
                    self.col_types[col] = col_type;
                    self.seq.set_rows(pattern_len);
                    self.seq.set_col(col, &data);
                },
            }

            true

        } else {
            false
        }
    }

    pub fn pattern_len(&self) -> usize { self.seq.rows() }

    pub fn get_col_at_phase(
        &mut self, col: usize, phase: &[f32],
        out: &mut [f32], out_gate: &mut [f32])
    {
        if self.seq.rows() == 0 {
            return;
        }

        match self.col_types[col] {
            PatternColType::Note | PatternColType::Step =>
                self.seq.col_get_at_phase(        col, phase, out, out_gate),
            PatternColType::Value =>
                self.seq.col_interpolate_at_phase(col, phase, out, out_gate),
            PatternColType::Gate =>
                self.seq.col_gate_at_phase(       col, phase, out, out_gate),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    macro_rules! assert_float_eq {
        ($a:expr, $b:expr) => {
            if ($a - $b).abs() > 0.0001 {
                panic!(r#"assertion failed: `(left == right)`
      left: `{:?}`,
     right: `{:?}`"#, $a, $b)
            }
        }
    }

    #[test]
    fn check_tracker_com_step() {
        let mut t = Tracker::new();
        let mut backend = t.get_backend();

        t.data().lock().unwrap().set_rows(16);
        t.data().lock().unwrap().set_col_step_type(0);
        t.data().lock().unwrap().set_cell_value(0,  0, 0xFFF);
        t.data().lock().unwrap().set_cell_value(7,  0, 0x777);
        t.data().lock().unwrap().set_cell_value(15, 0, 0x000);

        while t.send_one_update() { }
        while backend.check_updates() { }

        let mut out      = [0.0; 16];
        let mut out_gate = [0.0; 16];

        backend.get_col_at_phase(
            0, &[0.2, 0.5, 0.99], &mut out[..], &mut out_gate[..]);

        assert_float_eq!(out[0], 1.0);
        assert_float_eq!(out[1], 0.46666666);
        assert_float_eq!(out[2], 0.0);
        assert_float_eq!(out_gate[0], 0.0);
        assert_float_eq!(out_gate[1], 1.0);
        assert_float_eq!(out_gate[2], 1.0);
    }

    #[test]
    fn check_tracker_com_interp() {
        let mut t = Tracker::new();
        let mut backend = t.get_backend();

        t.data().lock().unwrap().set_rows(16);
        t.data().lock().unwrap().set_col_value_type(0);
        t.data().lock().unwrap().set_cell_value(0,  0, 0xFFF);
        t.data().lock().unwrap().set_cell_value(7,  0, 0x777);
        t.data().lock().unwrap().set_cell_value(15, 0, 0x000);

        while t.send_one_update() { }
        while backend.check_updates() { }

        let mut out      = [0.0; 16];
        let mut out_gate = [0.0; 16];

        backend.get_col_at_phase(
            0, &[0.2, 0.5, 0.999999], &mut out[..], &mut out_gate[..]);
        assert_float_eq!(out[0], 0.83238);
        assert_float_eq!(out[1], 0.46666666);
        assert_float_eq!(out[2], 0.0);
        assert_float_eq!(out_gate[0], 0.0);
        assert_float_eq!(out_gate[1], 0.0);
        assert_float_eq!(out_gate[2], 1.0);
    }

    #[test]
    fn check_tracker_com_gate() {
        let mut t = Tracker::new();
        let mut backend = t.get_backend();

        t.data().lock().unwrap().set_rows(4);
        t.data().lock().unwrap().set_col_gate_type(0);
        t.data().lock().unwrap().set_cell_value(0,  0, 0xFF7);
        t.data().lock().unwrap().clear_cell(1, 0);
        t.data().lock().unwrap().set_cell_value(2,  0, 0xFF0);
        t.data().lock().unwrap().set_cell_value(3,  0, 0xFFF);

        while t.send_one_update() { }
        while backend.check_updates() { }

        let mut out      = [0.0; 64];
        let mut out_gate = [0.0; 64];

        let mut phase = [0.0; 64];
        for (i, p) in phase.iter_mut().enumerate() {
            *p = i as f32 / 63.0;
        }

        //d// println!("----");
        backend.get_col_at_phase(
            0, &phase[..], &mut out[..], &mut out_gate[..]);
        //d// println!("out: {:?}", &out[16..32]);

        assert_eq!(out[0..8],  [1.0; 8]);
        assert_eq!(out[8..16], [0.0; 8]);
        assert_eq!(out[16..32],[0.0; 16]);
        assert_eq!(out_gate[0..8],  [1.0; 8]);
        assert_eq!(out_gate[8..16], [1.0; 8]);
        assert_eq!(out_gate[16..32],[0.0; 16]);

        assert_float_eq!(out[32], 1.0);
        assert_eq!(out[33..48],[0.0; 15]);
        assert_float_eq!(out_gate[32], 1.0);
        assert_eq!(out_gate[33..48],[1.0; 15]);

        assert_eq!(out[48..64],[1.0; 16]);
        assert_eq!(out_gate[48..64],[1.0; 16]);
    }
}