use image::{save_buffer, GenericImageView};
use std::f64::consts::{PI, SQRT_2};
use crate::descriptors::{Descriptor, DCT, print_matrix};
use log::{debug, log_enabled, Level};


impl DCT {
    /// Returns a new DCT instance with a base quality DCT matrix.
    pub fn new() -> DCT {
        let quantization_matrix: [u8; 64] = [
            16, 11, 10, 16, 24, 40, 51, 61,
            12, 12, 14, 19, 26, 58, 60, 55,
            14, 13, 16, 24, 40, 57, 69, 56,
            14, 17, 22, 29, 51, 87, 80, 62,
            18, 22, 37, 56, 6, 10, 103, 77,
            24, 35, 55, 64, 8, 10, 113, 92,
            49, 64, 78, 8, 10, 12, 12, 101,
            72, 92, 95, 9, 11, 10, 103, 99,
        ];
        DCT { quantization_matrix }
    }

    /// Builds DCT with given quality value
    /// quality
    pub fn with_quality(mut self, quality: u8) -> Self {
        let mut quantization_matrix: [u8; 64] = [0; 64];
        let scalar: f32 = match quality {
            1..=49 => 5000.0/quality as f32,
            50..=100 => 200.0 - 2.0*quality as f32,
            _ => 100.0 // Invalid input: set to base quality
        };
        for (_, cell) in quantization_matrix.iter_mut().enumerate() {
            *cell = ((scalar * *cell as f32 + 50.0) / 100.0).floor() as u8;
            if *cell == 0 {
                *cell = 1;
            }
        }
        self.quantization_matrix = quantization_matrix;
        self
    }

    /// 
    pub fn dct(&self, img: &image::DynamicImage) -> [f64; 64] {
        let mut dct_values: [f64; 64] = [0.0; 64];
        for u in 0..8 {
            for v in 0..8 {
                let k = (v*8)+u;
                let mut alpha = 0.25;
                if u == 0 {
                    alpha /= SQRT_2
                }
                if v == 0 {
                    alpha /= SQRT_2
                }
                let v: f64 = v as f64;
                let u: f64 = u as f64;
                let mut sum: f64 = 0.0;
                for (x, y, pix) in img.pixels() {
                    let x: f64 = 1.0 + 2.0 * x as f64;
                    let y: f64 = 1.0 + 2.0 * y as f64;
                    let pixel = (pix[0] as i16 - 127) as f64;
                    sum +=
                        pixel * 
                        (x*u*PI/16.0).cos() *
                        (y*v*PI/16.0).cos()
                }
                dct_values[k] = alpha * sum;
            }
        }
        dct_values
    }

    fn idct(&self, dct_values: [f64; 64]) -> [u8; 64] {
        let mut reconstructed: [u8; 64] = [0; 64];
        //for k in 0..64  {
        for (k, item) in reconstructed.iter_mut().enumerate()  {
            let x = (k%8) as f64;
            let y = (k/8) as f64;
            let mut sum = 0.0;
            for u in 0..8 {
                for v in 0..8 {
                    let mut alpha = 1.0;
                    if u == 0 {
                        alpha /= SQRT_2
                    }
                    if v == 0 {
                        alpha /= SQRT_2
                    }
                    let uv = (v*8)+u;
                    let v = v as f64;
                    let u = u as f64;
                    sum +=
                        alpha *
                        dct_values[uv] *
                        ((2.0 * x + 1.0) * u * PI / 16.0).cos() *
                        ((2.0 * y + 1.0) * v * PI / 16.0).cos();
                }
            }
            sum = 127.0 + (0.25 * sum).round();
            *item = std::cmp::min(255_u8, sum as u8);
        }
		reconstructed
	}
}

impl Default for DCT {
    fn default() -> Self {
        Self::new()
    }
}

impl Descriptor for DCT {
    fn info(&self) -> String {
        "dct".to_string()
    }
    fn describe(&self, img: &image::DynamicImage) -> u64 {
        let resized = self.resize(img);
        let dct_values = self.dct(&resized);
        debug!("DCT-coefficients:\n {}", print_matrix(dct_values));

        // Quantization:
        // debug!("Using quantization matrix:\n{}", print_matrix(self.quantization_matrix));
        // for i in 0..64 {
        //     dct_values[i] = (dct_values[i] / self.quantization_matrix[i] as f64).round();
        //     if dct_values[i] == -0.0 && i % 2 != 0 {
        //         dct_values[i] = 0.0;
        //     }
        // }
        // debug!("DCT-coefficients, quantized:\n {}", print_matrix(dct_values));

        if log_enabled!(Level::Debug) {
            resized.save("resize.png").expect("Error saving file");
            // De-quantization:
            let mut dequant = dct_values;
            for i in 0..64 {
                dequant[i] = dct_values[i] * self.quantization_matrix[i] as f64;
            }
            //debug!("DCT-coefficients, de-quantized:\n {}", print_matrix(dct_values));

            // Reconstruction original pixel values:
            let reconstructed = self.idct(dequant);

            save_buffer(
                "reconstructed.png",
                &reconstructed,
                8, 
                8,
                image::ColorType::L8
            ).expect("Error saving buffer");
        }

        // Calculating descriptor from dct values:

        // Zigzag order for our flattened array,
        // first 28 elements only
        let zigzag: [usize; 28] = [
            0, 
            1, 8,
            16, 9, 2,
            3, 10, 17, 24,
            32, 25, 18, 11, 4,
            5, 12, 19, 26, 33, 40,
            48, 41, 34, 27, 20, 13, 6,
        ];

        // Mask that indicates if a dct coefficient is positive or negative
        // By convention, when sign bit is 1, number is negative
        let mut sign_mask: u64 = 0;
        // If first horizontal AC coefficient is negative
        // This might account for horizontal flips when applied to all horizontal coefficients.
        let sign_mult = dct_values[1].signum();
        //debug!("Sign multiplier of first horizontal component: {}", sign_mult);

        // Mask that indicates if a coefficient is bigger or smaller than previous in order
        let mut pearson_mask: u64 = 0;
        let mut prev = dct_values[49].abs();


        for i in zigzag {
            let cur = dct_values[i].abs();
            if cur > prev {
                pearson_mask += 1;
            }
            prev = cur;

            let mut signum = dct_values[i].signum();
            if i % 2 != 0 {
                signum *= sign_mult;
            }
            //debug!("[{}]: {} has signum {}, and i%2 is {}", i, cur, signum, i%2);
            // Only multiply sign if dct-coefficient contains a horizontal component.
            if 
                signum < 0.0
            {
                sign_mask += 1;
            }

            // Shift masks
            sign_mask <<= 1;
            pearson_mask <<= 1;
            debug!("Sign    mask: {:028b}", sign_mask);
            debug!("Pearson mask: {:028b}", pearson_mask);
        }
        // debug!("Sign    mask: {:028b}", sign_mask);
        // debug!("Pearson mask: {:028b}", pearson_mask);

        let mut mask = sign_mask;
        debug!("Mask: {:064b}", mask);
        mask <<= 28;
        debug!("Mask: {:064b}", mask);
        mask += pearson_mask;
        debug!("Mask: {:064b}", mask);
        mask <<= 8;
        debug!("Mask: {:064b}", mask);
        // TODO: Do something with these last 8 bits.
        mask
    }
}
