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

use super::PatternColType;
use super::MAX_PATTERN_LEN;
use super::MAX_COLS;
use crate::matrix_repr::PatternRepr;

#[derive(Debug)]
pub struct PatternData {
    col_types:  [PatternColType; MAX_COLS],
    data:       Vec<Vec<Option<u16>>>,
    out_data:   Vec<[(f32, u8); MAX_PATTERN_LEN]>,
    strings:    Vec<Vec<Option<String>>>,
    cursor:     (usize, usize),
    rows:       usize,
    edit_step:  usize,
    dirty_col:  [bool; MAX_COLS],
}

impl PatternData {
    pub fn new(rows: usize) -> Self {
        Self {
            col_types:  [PatternColType::Value; MAX_COLS],
            data:       vec![vec![None; MAX_COLS]; MAX_PATTERN_LEN],
            out_data:   vec![[(0.0, 0); MAX_PATTERN_LEN]; MAX_COLS],
            strings:    vec![vec![None; MAX_COLS]; MAX_PATTERN_LEN],
            cursor:     (2, 2),
            edit_step:  4,
            dirty_col:  [true; MAX_COLS],
            rows,
        }
    }
}

impl PatternData {
    pub fn is_unset(&self) -> bool {
        for ct in self.col_types.iter() {
            if *ct != PatternColType::Value { return false; }
        }

        for rows in self.data.iter() {
            for col in rows.iter() {
                if col.is_some() { return false; }
            }
        }

        true
    }

    pub fn to_repr(&self) -> PatternRepr {
        let mut col_types = [0; MAX_COLS];
        for (i, ct) in self.col_types.iter().enumerate() {
            col_types[i] =
                match ct {
                    PatternColType::Value => 0,
                    PatternColType::Note  => 1,
                    PatternColType::Step  => 2,
                    PatternColType::Gate  => 3,
                };
        }

        let mut data = vec![vec![-1; MAX_COLS]; MAX_PATTERN_LEN];
        for (row_idx, row) in self.data.iter().enumerate() {
            for (col_idx, cell) in row.iter().enumerate() {
                data[row_idx][col_idx] =
                    if let Some(c) = cell { *c as i32 }
                    else                  { -1 };
            }
        }

        PatternRepr {
            col_types,
            data,
            rows:      self.rows,
            edit_step: self.edit_step,
            cursor:    self.cursor,
        }
    }

    pub fn from_repr(&mut self, repr: &PatternRepr) {
        for (i, ct) in repr.col_types.iter().enumerate() {
            self.col_types[i] =
                match *ct {
                    0 => PatternColType::Value,
                    1 => PatternColType::Note,
                    2 => PatternColType::Step,
                    3 => PatternColType::Gate,
                    _ => PatternColType::Value,
                };

            self.modified_col(i);
        }

        for (row_idx, row) in repr.data.iter().enumerate() {
            for (col_idx, cell) in row.iter().enumerate() {
                self.data[row_idx][col_idx] =
                    if *cell < 0 { None }
                    else         { Some(*cell as u16) };
            }
        }

        self.rows      = repr.rows;
        self.edit_step = repr.edit_step;
        self.cursor    = repr.cursor;
    }

    pub fn get_out_data(&self) -> &[[(f32, u8); MAX_PATTERN_LEN]] {
        &self.out_data
    }

    fn modified_col(&mut self, col: usize) {
        if let Some(bit) = self.dirty_col.get_mut(col) {
            *bit = true;
        }
    }

    pub fn col_is_modified_reset(&mut self, col: usize) -> bool {
        if self.dirty_col.get(col).copied().unwrap_or(false) {
            self.dirty_col[col] = false;

            true
        } else {
            false
        }
    }



    pub fn col_type(&self, col: usize) -> PatternColType {
        self.col_types.get(col).copied().unwrap_or(PatternColType::Step)
    }

    pub fn sync_out_data(&mut self, col: usize) {
        let out_col = &mut self.out_data[col];

        if self.rows == 0 {
            return;
        }

        match self.col_types[col] {
            PatternColType::Value => {
                let mut start_value = 0.0;
                let mut start_idx   = 0;
                let mut end_idx     = 0;

                while end_idx <= self.rows {
                    let mut break_after_write = false;
                    let cur_value =
                        if end_idx == self.rows {
                            end_idx -= 1;
                            break_after_write = true;
                            Some(self.data[end_idx][col]
                                .map(|v| ((v & 0xFFF) as f32) / (0xFFF as f32))
                                .unwrap_or(0.0))
                        } else {
                            self.data[end_idx][col].map(|v|
                                ((v & 0xFFF) as f32) / (0xFFF as f32))
                        };

                    if let Some(end_value) = cur_value {
                        out_col[start_idx] = (start_value, 0);
                        out_col[end_idx]   = (end_value, 1);

                        let delta_rows = end_idx - start_idx;

                        if delta_rows > 1 {
                            for idx in (start_idx + 1)..end_idx {
                                let x =
                                      (idx - start_idx) as f32
                                    / (delta_rows as f32);
                                out_col[idx] =
                                    (start_value * (1.0 - x) + end_value * x, 0);
                            }
                        }

                        start_value = end_value;
                        start_idx   = end_idx;
                        end_idx     = end_idx + 1;

                        if break_after_write {
                            break;
                        }

                    } else {
                        end_idx += 1;
                    }
                }
            },
            PatternColType::Note => {
                let mut cur_value = (0.0, 0);

                for row in 0..self.rows {
                    if let Some(new_value) = self.data[row][col] {
                        cur_value = (
                            ((new_value as i32 - 69) as f32 * 0.1) / 12.0,
                            1
                        );
                    } else {
                        cur_value.1 = 0;
                    }

                    out_col[row] =
                        (cur_value.0.clamp(-1.0, 1.0), cur_value.1);
                }
            },
            PatternColType::Step => {
                let mut cur_value = (0.0, 0);

                for row in 0..self.rows {
                    if let Some(new_value) = self.data[row][col] {
                        cur_value = (
                            ((new_value & 0xFFF) as f32) / (0xFFF as f32),
                            1
                        );
                    } else {
                        cur_value.1 = 0;
                    }

                    out_col[row] = cur_value;
                }
            },
            PatternColType::Gate => {
                for row in 0..self.rows {
                    out_col[row] =
                        if let Some(new_value) = self.data[row][col] {
                            (f32::from_bits(new_value as u32), 1)
                        } else {
                            (f32::from_bits(0xF000 as u32), 0)
                        };
                }
            },
        }
    }
}

impl dyn UIPatternModel {
    pub fn change_value(&mut self, row: usize, col: usize, offs: i16) {
        let val = self.get_cell_value(row, col) as i16;
        let val = (val + offs).max(0).min(0xfff);
        self.set_cell_value(row, col, val as u16);
    }
}

pub trait UIPatternModel: std::fmt::Debug {
    fn get_cell(&mut self, row: usize, col: usize) -> Option<&str>;
    fn is_col_note(&self, col: usize) -> bool;
    fn is_col_step(&self, col: usize) -> bool;
    fn is_col_gate(&self, col: usize) -> bool;

    fn rows(&self) -> usize;
    fn cols(&self) -> usize;
    fn set_rows(&mut self, rows: usize);

    fn clear_cell(&mut self, row: usize, col: usize);
    fn set_col_note_type(&mut self, col: usize);
    fn set_col_step_type(&mut self, col: usize);
    fn set_col_value_type(&mut self, col: usize);
    fn set_col_gate_type(&mut self, col: usize);

    fn set_cell_value(&mut self, row: usize, col: usize, val: u16);
    fn get_cell_value(&mut self, row: usize, col: usize) -> u16;

    fn set_cursor(&mut self, row: usize, col: usize);
    fn get_cursor(&self) -> (usize, usize);
    fn set_edit_step(&mut self, es: usize);
    fn get_edit_step(&mut self) -> usize;
}

impl UIPatternModel for PatternData {
    fn get_cell(&mut self, row: usize, col: usize) -> Option<&str> {
        if row >= self.data.len()    { return None; }
        if col >= self.data[0].len() { return None; }

        if self.strings[row][col].is_none() {
            if let Some(v) = self.data[row][col] {
                self.strings[row][col] = Some(format!("{:03x}", v));
            } else {
                return None;
            }
        }

        Some(self.strings[row][col].as_ref().unwrap())
    }

    fn clear_cell(&mut self, row: usize, col: usize) {
        if row >= self.data.len()    { return; }
        if col >= self.data[0].len() { return; }

        self.data[row][col]    = None;
        self.strings[row][col] = None;
        self.modified_col(col);
    }

    fn get_cell_value(&mut self, row: usize, col: usize) -> u16 {
        if row >= self.data.len()    { return 0; }
        if col >= self.data[0].len() { return 0; }

        self.data[row][col].unwrap_or(0)
    }

    fn set_cell_value(&mut self, row: usize, col: usize, val: u16) {
        if row >= self.data.len()    { return; }
        if col >= self.data[0].len() { return; }

        self.data[row][col]    = Some(val);
        self.strings[row][col] = None;
        self.modified_col(col);
    }

    fn is_col_note(&self, col: usize) -> bool {
        if let Some(ct) = self.col_types.get(col) {
            *ct == PatternColType::Note
        } else {
            false
        }
    }

    fn is_col_step(&self, col: usize) -> bool {
        if let Some(ct) = self.col_types.get(col) {
            *ct == PatternColType::Step
        } else {
            false
        }
    }

    fn is_col_gate(&self, col: usize) -> bool {
        if let Some(ct) = self.col_types.get(col) {
            *ct == PatternColType::Gate
        } else {
            false
        }
    }

    fn cols(&self) -> usize { self.data[0].len() }

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

    fn set_rows(&mut self, rows: usize) {
        self.rows = rows.min(self.data.len());
        self.modified_col(0); // modify any col, so we send an update.
    }

    fn set_col_note_type(&mut self, col: usize) {
        if col >= self.col_types.len() { return; }
        self.col_types[col] = PatternColType::Note;
        self.modified_col(col);
    }

    fn set_col_step_type(&mut self, col: usize) {
        if col >= self.col_types.len() { return; }
        self.col_types[col] = PatternColType::Step;
        self.modified_col(col);
    }

    fn set_col_value_type(&mut self, col: usize) {
        if col >= self.col_types.len() { return; }
        self.col_types[col] = PatternColType::Value;
        self.modified_col(col);
    }

    fn set_col_gate_type(&mut self, col: usize) {
        if col >= self.col_types.len() { return; }
        self.col_types[col] = PatternColType::Gate;
        self.modified_col(col);
    }

    fn set_cursor(&mut self, row: usize, col: usize) {
        self.cursor = (row, col);
    }
    fn get_cursor(&self) -> (usize, usize) { self.cursor }
    fn set_edit_step(&mut self, es: usize) { self.edit_step = es; }
    fn get_edit_step(&mut self) -> usize { self.edit_step }
}


#[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_linear_value_corner_case1_0_to_1() {
        let mut pats = PatternData::new(3);

        for col in 0..MAX_COLS {
            pats.set_col_value_type(col);
            pats.set_cell_value(0, col, 0);
            pats.set_cell_value(2, col, 0xFFF);
            pats.sync_out_data(col);

            let out_data = pats.get_out_data();

            let inc = 1.0 / 2.0;
            for i in 1..2 {
                let delta =
                    out_data[col][i].0
                    - out_data[col][i - 1].0;
                assert_float_eq!(delta, inc);
            }
        }
    }

    #[test]
    fn check_linear_value_corner_case2_0_to_1() {
        let mut pats = PatternData::new(4);

        for col in 0..MAX_COLS {
            pats.set_col_value_type(col);
            pats.set_cell_value(0, col, 0);
            pats.set_cell_value(3, col, 0xFFF);
            pats.sync_out_data(col);

            let out_data = pats.get_out_data();

            let inc = 1.0 / 3.0;
            for i in 1..3 {
                let delta =
                    out_data[col][i].0
                    - out_data[col][i - 1].0;
                assert_float_eq!(delta, inc);
            }
        }
    }

    #[test]
    fn check_linear_value_out_0_to_1() {
        let mut pats = PatternData::new(16);

        for col in 0..MAX_COLS {
            pats.set_col_value_type(col);
            pats.set_cell_value(0,  col, 0);
            pats.set_cell_value(15, col, 0xFFF);
            pats.sync_out_data(col);

            let out_data = pats.get_out_data();

            let inc = 1.0 / 15.0;

            //d// println!("out: {:?}", &out_data[col][0..16]);
            for i in 1..16 {
                let delta =
                    out_data[col][i].0
                    - out_data[col][i - 1].0;
                assert_float_eq!(delta, inc);
            }
        }
    }

    #[test]
    fn check_linear_value_out_1_to_0() {
        let mut pats = PatternData::new(16);

        for col in 0..MAX_COLS {
            pats.set_col_value_type(col);
            pats.set_cell_value(0, col, 0xFFF);
            pats.sync_out_data(col);

            let out_data = pats.get_out_data();

            let inc = 1.0 / 15.0;

            for i in 1..16 {
                let delta =
                    out_data[col][i].0
                    - out_data[col][i - 1].0;
                assert_float_eq!(delta.abs(), inc);
            }
        }
    }

    #[test]
    fn check_linear_value_out_cast1_1_to_1() {
        let mut pats = PatternData::new(16);

        for col in 0..MAX_COLS {
            pats.set_col_value_type(col);
            pats.set_cell_value(7, col, 0xFFF);
            pats.set_cell_value(8, col, 0xFFF);
            pats.sync_out_data(col);

            let out_data = pats.get_out_data();

            //d// println!("out: {:?}", &out_data[col][0..16]);
            for i in 0..8 {
                assert_float_eq!(
                    out_data[col][i].0,
                    out_data[col][15 - i].0);
            }
        }
    }

    #[test]
    fn check_linear_value_out_case2_1_to_1() {
        let mut pats = PatternData::new(16);

        for col in 0..MAX_COLS {
            pats.set_col_value_type(col);
            pats.set_cell_value(6, col, 0xFFF);
            pats.set_cell_value(9, col, 0xFFF);
            pats.sync_out_data(col);

            let out_data = pats.get_out_data();

            //d// println!("out: {:?}", &out_data[col][0..16]);
            for i in 0..8 {
                assert_float_eq!(
                    out_data[col][i].0,
                    out_data[col][15 - i].0);
            }
        }
    }

    #[test]
    fn check_linear_value_out_case3_1_to_1() {
        let mut pats = PatternData::new(16);

        for col in 0..MAX_COLS {
            pats.set_col_value_type(col);
            pats.set_cell_value(6, col, 0xFFF);
            pats.set_cell_value(7, col, 0x0);
            pats.set_cell_value(8, col, 0x0);
            pats.set_cell_value(9, col, 0xFFF);
            pats.sync_out_data(col);

            let out_data = pats.get_out_data();

            //d// println!("out: {:?}", &out_data[col][0..16]);
            for i in 0..8 {
                assert_float_eq!(
                    out_data[col][i].0,
                    out_data[col][15 - i].0);
            }
        }
    }

    #[test]
    fn check_linear_value_out_case4_1_to_1() {
        let mut pats = PatternData::new(16);

        for col in 0..MAX_COLS {
            pats.set_col_value_type(col);
            pats.set_cell_value(5, col, 0xFFF);
            pats.set_cell_value(7, col, 0x0);
            pats.set_cell_value(8, col, 0x0);
            pats.set_cell_value(10, col, 0xFFF);
            pats.sync_out_data(col);

            let out_data = pats.get_out_data();

            //d// println!("out: {:?}", &out_data[col][0..16]);

            assert_float_eq!(0.5, out_data[col][6].0);
            assert_float_eq!(0.5, out_data[col][9].0);

            for i in 0..8 {
                assert_float_eq!(
                    out_data[col][i].0,
                    out_data[col][15 - i].0);
            }
        }
    }

    #[test]
    fn check_pattern_step_out() {
        let mut pats = PatternData::new(16);

        for col in 0..MAX_COLS {
            pats.set_col_step_type(col);
            pats.set_cell_value(4,  col, 0x450);
            pats.set_cell_value(5,  col, 0x0);
            pats.set_cell_value(7,  col, 0x7ff);
            pats.set_cell_value(9,  col, 0x800);
            pats.set_cell_value(10, col, 0xfff);
            pats.sync_out_data(col);

            let out_data = pats.get_out_data();
            assert_float_eq!(out_data[col][0].0,  0.0);
            assert_float_eq!(out_data[col][4].0,  0.26959708);
            assert_float_eq!(out_data[col][5].0,  0.0);
            assert_float_eq!(out_data[col][7].0,  0.4998779);
            assert_float_eq!(out_data[col][8].0,  0.4998779);
            assert_float_eq!(out_data[col][9].0,  0.50012213);
            assert_float_eq!(out_data[col][10].0, 1.0);
            assert_float_eq!(out_data[col][15].0, 1.0);
        }
    }

    #[test]
    fn check_pattern_note_out() {
        let mut pats = PatternData::new(16);

        for col in 0..MAX_COLS {
            pats.set_col_note_type(col);
            pats.set_cell_value(4, col, 0x45);
            pats.set_cell_value(5, col, 0x0);
            pats.set_cell_value(7, col, 0x45 - 12);
            pats.set_cell_value(10, col, 0x45 + 12);
            pats.sync_out_data(col);

            let out_data = pats.get_out_data();
            assert_float_eq!(out_data[col][0].0,  0.0);
            assert_float_eq!(out_data[col][4].0,  0.0);
            assert_float_eq!(out_data[col][5].0, -0.575);
            assert_float_eq!(out_data[col][7].0, -0.1);
            assert_float_eq!(out_data[col][9].0, -0.1);
            assert_float_eq!(out_data[col][10].0, 0.1);
            assert_float_eq!(out_data[col][15].0, 0.1);
        }
    }

    #[test]
    fn check_pattern_repr() {
        let mut pat = PatternData::new(MAX_PATTERN_LEN);

        for col in 0..MAX_COLS {
            pat.set_col_note_type(col);
            for v in 1..(MAX_PATTERN_LEN + 1) {
                pat.set_cell_value(v - 1, col, v as u16);
            }

            pat.set_cursor(16, 3);
            pat.set_edit_step(5);
            pat.set_rows(133);
        }

        let repr = pat.to_repr();

        let mut pat2 = PatternData::new(MAX_PATTERN_LEN);
        pat2.from_repr(&repr);

        for col in 0..MAX_COLS {
            assert!(pat.is_col_note(col));
            for v in 1..(MAX_PATTERN_LEN + 1) {
                assert_eq!(pat.get_cell_value(v - 1, col), v as u16);
            }

            assert_eq!(pat.get_cursor(), (16, 3));
            assert_eq!(pat.get_edit_step(), 5);
            assert_eq!(pat.rows(), 133);
        }
    }
}