// 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.

use super::MAX_COLS;
use super::MAX_PATTERN_LEN;
use synfx_dsp::SplitMix64;

pub struct PatternSequencer {
    rows: usize,
    data: Vec<Vec<(f32, u8)>>,
    rng: SplitMix64,
    rand_vals: [(usize, f64); MAX_COLS],
}

const FRACT_16THS: [f32; 16] = [
    1.0 / 16.0,
    2.0 / 16.0,
    3.0 / 16.0,
    4.0 / 16.0,
    5.0 / 16.0,
    6.0 / 16.0,
    7.0 / 16.0,
    8.0 / 16.0,
    9.0 / 16.0,
    10.0 / 16.0,
    11.0 / 16.0,
    12.0 / 16.0,
    13.0 / 16.0,
    14.0 / 16.0,
    15.0 / 16.0,
    1.0,
];

impl PatternSequencer {
    pub fn new_default_seed(rows: usize) -> Self {
        Self {
            rows,
            data: vec![vec![(0.0, 0); MAX_PATTERN_LEN]; MAX_COLS],
            rng: SplitMix64::new(0x91234),
            rand_vals: [(99999, 0.0); MAX_COLS],
        }
    }

    pub fn new(rows: usize) -> Self {
        use std::time::SystemTime;
        let seed = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
            Ok(n) => n.as_nanos() as i64,
            Err(_) => 1_234_567_890,
        };
        Self {
            rows,
            data: vec![vec![(0.0, 0); MAX_PATTERN_LEN]; MAX_COLS],
            rng: SplitMix64::new_from_i64(seed),
            rand_vals: [(99999, 0.0); MAX_COLS],
        }
    }

    pub fn set_rows(&mut self, rows: usize) {
        self.rows = rows;
    }

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

    pub fn set_col(&mut self, col: usize, col_data: &[(f32, u8)]) {
        for (out_cell, in_cell) in self.data[col].iter_mut().zip(col_data.iter()) {
            *out_cell = *in_cell;
        }
    }

    pub fn col_interpolate_at_phase(
        &self,
        col: usize,
        phase: &[f32],
        out: &mut [f32],
        out_gate: &mut [f32],
    ) {
        let col = &self.data[col][..];

        let last_row_idx: f32 = (self.rows as f32) - 0.00001;
        let rows = self.rows;

        for ((phase, out), out_gate) in phase.iter().zip(out.iter_mut()).zip(out_gate.iter_mut()) {
            let row_phase = phase * last_row_idx;
            let phase_frac = row_phase.fract();
            let line = row_phase.floor() as usize % rows;
            let prev_line = if line == 0 { self.rows - 1 } else { line - 1 };

            let prev = col[prev_line].0;
            let next = col[line].0;
            let gate = col[line].1;

            // println!("INTERP: {}={:9.7}, {}={:9.7} | {:9.7}",
            //          prev_line, prev,
            //          line, next,
            //          phase_frac);

            *out = prev * (1.0 - phase_frac) + next * phase_frac;
            *out_gate = gate as f32;
        }
    }

    pub fn col_get_at_phase(
        &self,
        col: usize,
        phase: &[f32],
        out: &mut [f32],
        out_gate: &mut [f32],
    ) {
        let col = &self.data[col][..];

        let last_row_idx: f32 = (self.rows as f32) - 0.00001;
        let rows = self.rows;

        for ((phase, out), out_gate) in phase.iter().zip(out.iter_mut()).zip(out_gate.iter_mut()) {
            let row_phase = phase * last_row_idx;
            let line = row_phase.floor() as usize % rows;

            let (val, gate) = col[line];
            *out = val;
            *out_gate = gate as f32;
        }
    }

    pub fn col_gate_at_phase(
        &mut self,
        col_idx: usize,
        phase: &[f32],
        out: &mut [f32],
        out_gate: &mut [f32],
    ) {
        let col = &self.data[col_idx][..];

        let last_row_idx: f32 = (self.rows as f32) - 0.00001;
        let rows = self.rows;

        for ((phase, out), out_gate) in phase.iter().zip(out.iter_mut()).zip(out_gate.iter_mut()) {
            let row_phase = phase.clamp(0.0, 1.0) * last_row_idx;
            let line = row_phase.floor() as usize % rows;
            let phase_frac = row_phase.fract();

            let gate: u32 = col[line].0.to_bits();
            let ggate: u8 = col[line].1;

            // pulse_width:
            //      0xF  - Gate is on for full row
            //      0x0  - Gate is on for a very short burst
            let pulse_width: f32 = FRACT_16THS[(gate & 0x00F) as usize];
            // row_div:
            //      0xF  - Row has 1  Gate
            //      0x0  - Row is divided up into 16 Gates
            let row_div: f32 = (16 - ((gate & 0x0F0) >> 4)) as f32;
            // probability:
            //      0xF  - Row is always triggered
            //      0x7  - Row fires only in 50% of the cases
            //      0x0  - Row fires only in ~6% of the cases
            let probability: u8 = ((gate & 0xF00) >> 8) as u8;

            let sub_frac = (phase_frac * row_div).fract();
            //d// println!(
            //d//     "row_div={}, pw={}, phase={} / {}",
            //d//     row_div, pulse_width, sub_frac, phase_frac);

            if probability < 0xF {
                let rand_val = if self.rand_vals[col_idx].0 != line {
                    let new_rand_val = self.rng.next_open01();
                    self.rand_vals[col_idx] = (line, new_rand_val);
                    new_rand_val
                } else {
                    self.rand_vals[col_idx].1
                };
                //d// println!("RANDVAL: {:?} | {:9.7}", self.rand_vals[col_idx], FRACT_16THS[probability as usize]);

                if rand_val > (FRACT_16THS[probability as usize] as f64) {
                    *out = 0.0;
                    if (gate & 0xF000) > 0 {
                        *out_gate = 0.0;
                    } else {
                        *out_gate = ggate as f32;
                    }
                    continue;
                }
            }

            if (gate & 0xF000) > 0 {
                *out = 0.0;
                *out_gate = 0.0;
            } else {
                *out = if sub_frac <= pulse_width { 1.0 } else { 0.0 };
                *out_gate = ggate as f32;
            }
        }
    }
}

#[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_seq_interpolate_1() {
        let mut ps = PatternSequencer::new(2);
        ps.set_col(0, &[(0.0, 0), (1.0, 1)]);

        let mut out = [0.0; 6];
        let mut out_gate = [0.0; 6];
        ps.col_interpolate_at_phase(
            0,
            &[0.0, 0.1, 0.50, 0.51, 0.9, 0.99999],
            &mut out[..],
            &mut out_gate[..],
        );
        assert_float_eq!(out[0], 1.0);
        assert_float_eq!(out[1], 0.8);
        assert_float_eq!(out[2], 0.0);
        assert_float_eq!(out[3], 0.02);
        assert_float_eq!(out[4], 0.8);
        assert_float_eq!(out[5], 0.99999);

        assert_float_eq!(out_gate[0], 0.0);
        assert_float_eq!(out_gate[1], 0.0);
        assert_float_eq!(out_gate[2], 0.0);
        assert_float_eq!(out_gate[3], 1.0);
        assert_float_eq!(out_gate[4], 1.0);
        assert_float_eq!(out_gate[5], 1.0);
    }

    #[test]
    fn check_seq_interpolate_buffer_end() {
        let mut ps = PatternSequencer::new(256);
        ps.set_col(0, &[(f32::from_bits(0xF000), 0); 256]);

        let mut out = [0.0; 1];
        let mut out_gate = [0.0; 1];
        ps.col_gate_at_phase(0, &[0.9999999999], &mut out[..], &mut out_gate[..]);
        assert_float_eq!(out[0], 0.0);
        assert_float_eq!(out_gate[0], 0.0);

        let mut ps = PatternSequencer::new(256);
        ps.set_col(0, &[(0.0, 0); 256]);

        ps.col_get_at_phase(0, &[0.9999999999], &mut out[..], &mut out_gate[..]);
        assert_float_eq!(out[0], 0.0);
        assert_float_eq!(out_gate[0], 0.0);

        ps.col_interpolate_at_phase(0, &[0.9999999999], &mut out[..], &mut out_gate[..]);
        assert_float_eq!(out[0], 0.0);
        assert_float_eq!(out_gate[0], 0.0);
    }

    #[test]
    fn check_seq_step_1() {
        let mut ps = PatternSequencer::new(2);
        ps.set_col(0, &[(0.0, 0), (1.0, 1)]);

        let mut out = [0.0; 3];
        let mut out_gate = [0.0; 3];
        ps.col_get_at_phase(0, &[0.1, 0.51, 0.9], &mut out[..], &mut out_gate[..]);
        assert_float_eq!(out[0], 0.0);
        assert_float_eq!(out[1], 1.0);
        assert_float_eq!(out[2], 1.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_seq_step_2() {
        let mut ps = PatternSequencer::new(3);
        ps.set_col(0, &[(0.0, 0), (0.3, 1), (1.0, 1)]);

        let mut out = [0.0; 6];
        let mut out_gate = [0.0; 6];
        ps.col_get_at_phase(0, &[0.1, 0.5, 0.51, 0.6, 0.9, 0.99], &mut out[..], &mut out_gate[..]);
        assert_float_eq!(out[0], 0.0);
        assert_float_eq!(out[1], 0.3);
        assert_float_eq!(out[2], 0.3);
        assert_float_eq!(out[3], 0.3);
        assert_float_eq!(out[4], 1.0);
        assert_float_eq!(out[5], 1.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);
        assert_float_eq!(out_gate[3], 1.0);
        assert_float_eq!(out_gate[4], 1.0);
        assert_float_eq!(out_gate[5], 1.0);
    }

    #[test]
    fn check_seq_gate_1() {
        let mut ps = PatternSequencer::new(2);
        ps.set_col(0, &[(f32::from_bits(0x0FFF), 1), (f32::from_bits(0xF000), 0)]);

        let mut out = [0.0; 6];
        let mut out_gate = [0.0; 6];
        ps.col_gate_at_phase(
            0,
            &[0.1, 0.5, 0.5001, 0.6, 0.9, 0.99],
            &mut out[..],
            &mut out_gate[..],
        );
        //d// println!("out: {:?}", out);

        assert_float_eq!(out[0], 1.0);
        assert_float_eq!(out[1], 1.0);
        assert_float_eq!(out[2], 0.0);
        assert_float_eq!(out[3], 0.0);
        assert_float_eq!(out[4], 0.0);
        assert_float_eq!(out[5], 0.0);

        assert_float_eq!(out_gate[0], 1.0);
        assert_float_eq!(out_gate[1], 1.0);
        assert_float_eq!(out_gate[2], 0.0);
        assert_float_eq!(out_gate[3], 0.0);
        assert_float_eq!(out_gate[4], 0.0);
        assert_float_eq!(out_gate[5], 0.0);
    }

    fn count_high(slice: &[f32]) -> usize {
        let mut sum = 0;
        for p in slice.iter() {
            if *p > 0.5 {
                sum += 1;
            }
        }
        sum
    }

    fn count_up(slice: &[f32]) -> usize {
        let mut sum = 0;
        let mut cur = 0.0;
        for p in slice.iter() {
            if cur < 0.1 && *p > 0.5 {
                sum += 1;
            }
            cur = *p;
        }
        sum
    }

    #[test]
    fn check_seq_gate_2() {
        let mut ps = PatternSequencer::new(3);
        ps.set_col(
            0,
            &[
                (f32::from_bits(0x0FF0), 1),
                (f32::from_bits(0x0FF7), 0),
                (f32::from_bits(0x0FFF), 1),
            ],
        );

        let mut phase = vec![0.0; 96];
        let inc = 1.0 / (96.0 - 1.0);
        let mut phase_run = 0.0;
        for p in phase.iter_mut() {
            *p = phase_run;
            phase_run += inc;
        }

        //d// println!("PHASE: {:?}", phase);

        let mut out = [0.0; 96];
        let mut out_gate = [0.0; 96];
        ps.col_gate_at_phase(0, &phase[..], &mut out[..], &mut out_gate[..]);
        //d// println!("out: {:?}", &out[0..32]);

        assert_eq!(count_high(&out[0..32]), 2);
        assert_eq!(count_high(&out[32..64]), 16);
        assert_eq!(count_high(&out[64..96]), 32);
        assert_eq!(count_high(&out_gate[0..32]), 32);
        assert_eq!(count_high(&out_gate[32..64]), 0);
        assert_eq!(count_high(&out_gate[64..96]), 32);

        assert_eq!(count_up(&out[0..32]), 1);
        assert_eq!(count_up(&out[32..64]), 1);
        assert_eq!(count_up(&out[64..96]), 1);
        assert_eq!(count_up(&out_gate[0..32]), 1);
        assert_eq!(count_up(&out_gate[32..64]), 0);
        assert_eq!(count_up(&out_gate[64..96]), 1);
    }

    #[test]
    fn check_seq_gate_div_1() {
        let mut ps = PatternSequencer::new(3);
        ps.set_col(
            0,
            &[
                (f32::from_bits(0x0F80), 1),
                (f32::from_bits(0x0F87), 0),
                (f32::from_bits(0x0F8F), 1),
            ],
        );

        let mut phase = vec![0.0; 3 * 64];
        let inc = 1.0 / ((3.0 * 64.0) - 1.0);
        let mut phase_run = 0.0;
        for p in phase.iter_mut() {
            *p = phase_run;
            phase_run += inc;
        }

        //d// println!("PHASE: {:?}", phase);

        let mut out = [0.0; 3 * 64];
        let mut out_gate = [0.0; 3 * 64];
        ps.col_gate_at_phase(0, &phase[..], &mut out[..], &mut out_gate[..]);

        assert_eq!(count_high(&out[0..64]), 8);
        assert_eq!(count_up(&out[0..64]), 8);
        assert_eq!(count_high(&out_gate[0..64]), 64);
        assert_eq!(count_up(&out_gate[0..64]), 1);

        assert_eq!(count_high(&out[64..128]), 32);
        assert_eq!(count_up(&out[64..128]), 8);
        assert_eq!(count_high(&out_gate[64..128]), 0);
        assert_eq!(count_up(&out_gate[64..128]), 0);

        assert_eq!(count_high(&out[128..192]), 64);
        assert_eq!(count_up(&out[128..192]), 1);
        assert_eq!(count_high(&out_gate[128..192]), 64);
        assert_eq!(count_up(&out_gate[128..192]), 1);
    }

    #[test]
    fn check_seq_gate_div_2() {
        let mut ps = PatternSequencer::new(3);
        ps.set_col(
            0,
            &[
                (f32::from_bits(0x0F00), 1),
                (f32::from_bits(0x0F07), 0),
                (f32::from_bits(0x0F0F), 1),
            ],
        );

        let mut phase = vec![0.0; 6 * 64];
        let inc = 1.0 / ((6.0 * 64.0) - 1.0);
        let mut phase_run = 0.0;
        for p in phase.iter_mut() {
            *p = phase_run;
            phase_run += inc;
        }

        //d// println!("PHASE: {:?}", phase);

        let mut out = [0.0; 6 * 64];
        let mut out_gate = [0.0; 6 * 64];
        ps.col_gate_at_phase(0, &phase[..], &mut out[..], &mut out_gate[..]);

        assert_eq!(count_high(&out[0..128]), 16);
        assert_eq!(count_up(&out[0..128]), 16);
        assert_eq!(count_high(&out_gate[0..128]), 128);
        assert_eq!(count_up(&out_gate[0..128]), 1);

        assert_eq!(count_high(&out[128..256]), 64);
        assert_eq!(count_up(&out[128..256]), 16);
        assert_eq!(count_high(&out_gate[128..256]), 0);
        assert_eq!(count_up(&out_gate[128..256]), 0);

        assert_eq!(count_high(&out[256..384]), 128);
        assert_eq!(count_up(&out[256..384]), 1);
        assert_eq!(count_high(&out_gate[256..384]), 128);
        assert_eq!(count_up(&out_gate[256..384]), 1);
    }

    #[test]
    fn check_seq_gate_div_3() {
        let mut ps = PatternSequencer::new(3);
        ps.set_col(
            0,
            &[
                (f32::from_bits(0x0FE0), 1),
                (f32::from_bits(0x0FE7), 0),
                (f32::from_bits(0x0FEF), 1),
            ],
        );

        let mut phase = vec![0.0; 6 * 64];
        let inc = 1.0 / ((6.0 * 64.0) - 1.0);
        let mut phase_run = 0.0;
        for p in phase.iter_mut() {
            *p = phase_run;
            phase_run += inc;
        }

        //d// println!("PHASE: {:?}", phase);

        let mut out = [0.0; 6 * 64];
        let mut out_gate = [0.0; 6 * 64];
        ps.col_gate_at_phase(0, &phase[..], &mut out[..], &mut out_gate[..]);

        assert_eq!(count_high(&out[0..128]), 8);
        assert_eq!(count_up(&out[0..128]), 2);
        assert_eq!(count_high(&out_gate[0..128]), 128);
        assert_eq!(count_up(&out_gate[0..128]), 1);

        assert_eq!(count_high(&out[128..256]), 64);
        assert_eq!(count_up(&out[128..256]), 2);
        assert_eq!(count_high(&out_gate[128..256]), 0);
        assert_eq!(count_up(&out_gate[128..256]), 0);

        assert_eq!(count_high(&out[256..384]), 128);
        assert_eq!(count_up(&out[256..384]), 1);
        assert_eq!(count_high(&out_gate[256..384]), 128);
        assert_eq!(count_up(&out_gate[256..384]), 1);
    }

    fn run_probability_test_for(prob: u32) -> (usize, usize) {
        let rows = 100;

        let mut ps = PatternSequencer::new_default_seed(rows);
        let mut coldata = vec![(0.0, 0); rows];
        for i in 0..coldata.len() {
            coldata[i] = (f32::from_bits(0x00FF | prob), 1);
        }
        ps.set_col(0, &coldata[..]);

        let samples = rows;
        let mut phase = vec![0.0; samples];
        let inc = 1.0 / ((samples as f32) - 1.0);
        let mut phase_run = 0.0;
        for p in phase.iter_mut() {
            *p = phase_run;
            phase_run += inc;
        }

        let mut out = vec![0.0; samples];
        let mut out_gate = vec![0.0; samples];
        ps.col_gate_at_phase(0, &phase[..], &mut out[..], &mut out_gate[..]);

        (count_high(&out[..]), count_up(&out[..]))
    }

    #[test]
    fn check_seq_gate_div_rng() {
        // XXX: The result numbers are highly dependent on the
        //      sampling rate inside run_probability_test_for().
        assert_eq!(run_probability_test_for(0x000), (5, 5));
        assert_eq!(run_probability_test_for(0x100), (12, 11));
        assert_eq!(run_probability_test_for(0x200), (20, 18));
        assert_eq!(run_probability_test_for(0x300), (26, 23));
        assert_eq!(run_probability_test_for(0x400), (32, 26));
        assert_eq!(run_probability_test_for(0x500), (38, 29));
        assert_eq!(run_probability_test_for(0x600), (47, 29));
        assert_eq!(run_probability_test_for(0x700), (56, 26));
        assert_eq!(run_probability_test_for(0x800), (60, 25));
        assert_eq!(run_probability_test_for(0x900), (66, 24));
        assert_eq!(run_probability_test_for(0xA00), (70, 22));
        assert_eq!(run_probability_test_for(0xB00), (79, 18));
        assert_eq!(run_probability_test_for(0xC00), (84, 13));
        assert_eq!(run_probability_test_for(0xD00), (93, 7));
        assert_eq!(run_probability_test_for(0xE00), (96, 5));
        assert_eq!(run_probability_test_for(0xF00), (100, 1));
    }
}
