use av_scenechange::DetectionResults;
use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};

use crate::scenechange::ScMethod;

#[gen_stub_pyclass]
#[pyo3::pyclass]
pub struct DetectResult {
    pub detect_result: DetectionResults,
    #[pyo3(get)]
    pub args:          ArgsResult,
}

#[gen_stub_pyclass]
#[pyo3::pyclass]
#[derive(Clone)]
pub struct ArgsResult {
    #[pyo3(get)]
    pub min_scene_len:      Option<usize>,
    #[pyo3(get)]
    pub max_scene_len:      Option<usize>,
    #[pyo3(get)]
    pub lookahead_distance: usize,
    #[pyo3(get)]
    pub detect_flashes:     bool,
    #[pyo3(get)]
    pub sc_method:          ScMethod,
    #[pyo3(get)]
    pub total_frames:       usize,
}

#[gen_stub_pymethods]
#[pyo3::pymethods]
impl DetectResult {
    /// return a frame list of scene change
    pub fn to_list(&self) -> Vec<usize> {
        self.detect_result.scene_changes.clone()
    }

    /// return a raw json content from av-scenechange
    pub fn to_json(&self) -> String {
        serde_json::to_string_pretty(&self.detect_result).unwrap()
    }
}

#[gen_stub_pyclass]
#[pyo3::pyclass]
pub struct SmartResult {
    pub detect_result: DetectionResults,
    #[pyo3(get)]
    pub args:          ArgsResult,
}

#[gen_stub_pymethods]
#[pyo3::pymethods]
impl SmartResult {
    /// return a list of scene change
    pub fn to_list(&self) -> Vec<usize> {
        use std::{cmp::min, collections::BTreeMap};

        let max_dist = 300;
        let scores: BTreeMap<usize, (f64, f64)> = self
            .detect_result
            .scores
            .iter()
            .map(|(&k, v)| (k, (v.inter_cost, v.threshold)))
            .collect();

        let mut scenes = Vec::new();
        for i in 0..self.detect_result.scene_changes.len() {
            let s = self.detect_result.scene_changes[i];
            let e = self
                .detect_result
                .scene_changes
                .get(i + 1)
                .copied()
                .unwrap_or(self.args.total_frames);
            scenes.push((s, e));
        }

        let mut new_scenes = vec![0];

        for &(s_frame, e_frame) in &scenes {
            let mut current_start = s_frame.max(*new_scenes.last().unwrap());
            let mut distance = e_frame - current_start;
            let split_size = max_dist as usize;

            while distance > split_size {
                let minimum_split_count = distance / split_size;
                let middle_point = distance / (minimum_split_count + 1);
                let min_size = middle_point / 2;
                let max_size = min(split_size, middle_point + min_size);
                let range_size = max_size - min_size;

                let split_point = (min_size..=max_size)
                    .filter_map(|size| {
                        scores.get(&(current_start + size)).map(|(inter_cost, threshold)| {
                            let inter_score = inter_cost / threshold;
                            let distance_from_mid =
                                (middle_point.max(size) - middle_point.min(size)) as f64;
                            let distance_weighting = 1.0 - distance_from_mid / range_size as f64;
                            (size, inter_score * distance_weighting)
                        })
                    })
                    .max_by_key(|(_, score)| (*score * 10000.0).round() as u64)
                    .expect("split scores is not empty")
                    .0;

                current_start += split_point;
                new_scenes.push(current_start);
                distance = e_frame - current_start;
            }
            new_scenes.push(e_frame);
        }

        if new_scenes.last() == Some(&self.args.total_frames) {
            new_scenes.pop();
        }

        new_scenes
    }
}

/// warning check incase something goes wrong after scene detection
pub fn warn_check(detect_result: &DetectionResults, vid_frame_count: usize) {
    if detect_result.frame_count != vid_frame_count {
        println!("Warning: Frame count from scene detect and video is diferrent")
    }
}
