// 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::PatternColType;
use super::MAX_COLS;
use super::MAX_PATTERN_LEN;
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],
    generation: usize,
}

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],
            generation: 0,
            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;
        self.generation += 1;
    }

    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 <= out_col.len() {
                    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..out_col.len() {
                    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..out_col.len() {
                    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 => {
                // XXX: We need to iterate over all rows, even if
                //      self.rows < out_col.len(), because empty cells
                //      are not equal to float 0.0. Maybe we should change
                //      this internal format!? But in either case we transmit
                //      a full row to the backend anyways.
                for row in 0..out_col.len() {
                    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;

    fn get_generation(&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);
        self.generation += 1;
    }

    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);
        self.generation += 1;
    }

    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.
        self.generation += 1;
    }

    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);
        self.generation += 1;
    }

    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);
        self.generation += 1;
    }

    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);
        self.generation += 1;
    }

    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);
        self.generation += 1;
    }

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

    fn get_generation(&self) -> usize {
        self.generation
    }
}

#[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);
        }
    }
}
