use std::{path::PathBuf, process::exit};

use pyo3::prelude::*;
use pyo3_stub_gen::{define_stub_info_gatherer, derive::{gen_stub_pyclass, gen_stub_pyfunction}};

use crate::{
    result::{DetectResult, SmartResult},
    scenechange::{ScMethod, SceneDetectArgs},
};

mod progressbar;
mod result;
mod scenechange;
mod vid_info;

#[gen_stub_pyclass]
#[pyclass]
pub struct BuildFeatures {
    #[pyo3(get)]
    pub vapoursynth_enabled: bool,
}


#[pymethods]
impl BuildFeatures {
    #[new]
    fn new() -> Self {
        Self {
            vapoursynth_enabled: cfg!(feature = "vapoursynth"),
        }
    }
}

#[allow(clippy::too_many_arguments)]
#[gen_stub_pyfunction]
#[pyfunction]
#[pyo3(signature = (input, min_scene_len=Some(-1), max_scene_len=Some(-1), sc_width=None, sc_height=None, sc_method=ScMethod::Standard, progress_template=None, detect_flashes=Some(true), lookahead_distance=Some(5)))]
/// Detect scene changes using av-scenechange and return DetectResult object to
/// get scene change result call to_list() function.
///
/// Parameters
/// ----------
/// - **input** Video file path or vapoursynth script file (.vpy, .py) if
///   compile with vapoursynth input support.
///
/// - **min_scene_len** Minimum scene length in frames. Defaults is the video
///   framerate. To disable min scene len pass None to this. -1 is internal
///   value means that you will you use default value.
///
/// - **max_scene_len** Maximum scene length in frames. Defaults is 10 x video
///   framerate, capped at 300. To disable max cene len pass None to this. -1 is
///   internal value means that you will you use default value.
///
/// - **sc_width** Downscale width for scene detection. Only applies to video
///   file inputs.
///
/// - **sc_height** Downscale height for scene detection. Only applies to video
///   file inputs.
///
/// - **sc_method** Scene detection method to use.
///
/// - **detect_flashes** Detect flashes option for av-scenechange. Default is
///   true. pass None to this will result in True value
///
/// - **lookahead_distance** lookahead distance option for av-scenechange.
///   Default is 5. pass None to this means that you will you use default value
///
/// - **progress_template** Custom progress bar template. See source code for
///   details. if you want to disable progress bar pass "" (empty str) value to
///   this.
fn scene_detect(
    py: Python<'_>,
    input: String,
    min_scene_len: Option<isize>,
    max_scene_len: Option<isize>,
    sc_width: Option<usize>,
    sc_height: Option<usize>,
    sc_method: ScMethod,
    progress_template: Option<String>,
    detect_flashes: Option<bool>,
    lookahead_distance: Option<usize>,
) -> PyResult<DetectResult> {
    let input_path = PathBuf::from(&input);

    let args = SceneDetectArgs {
        input: input_path.clone(),
        user_min_scene_len: min_scene_len,
        user_max_scene_len: max_scene_len,
        sc_width,
        sc_height,
        sc_method,
        progress_template,
        detect_flashes: detect_flashes.unwrap_or(true),
        lookahead_distance: lookahead_distance.unwrap_or(5),
    };
    // fix vapoursynth script crash with error
    // _PyMem_DebugMalloc: Python memory allocator called without holding the GIL
    let detect_result = py
        .detach(|| scenechange::scene_detect(args))
        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;

    Ok(detect_result)
}

#[gen_stub_pyfunction]
#[pyfunction]
#[pyo3(signature = (input, progress_template=None))]
/// Detect scene change automatically with logic from [xav](https://github.com/emrakyz/xav)
/// and return SmartResult object to get scene chaneg result call to_list()
/// function.
///
/// Parameters
/// ----------
/// - **input** Video file path or vapoursynth script file (.vpy, .py) if
///   compile with vapoursynth input support.
///
/// - **progress_template** Custom progress bar template. See source code for
///   details. if you want to disable progress bar pass "" (empty str) value to
///   this.
fn smart_scene_detect(
    py: Python<'_>,
    input: String,
    progress_template: Option<String>,
) -> PyResult<SmartResult> {
    let input_path = PathBuf::from(&input);

    let args = SceneDetectArgs {
        input: input_path.clone(),
        user_min_scene_len: None,
        user_max_scene_len: None,
        sc_width: None,
        sc_height: None,
        sc_method: ScMethod::Standard,
        progress_template,
        detect_flashes: true,
        lookahead_distance: 5,
    };
    // fix vapoursynth script crash with error
    // _PyMem_DebugMalloc: Python memory allocator called without holding the GIL
    let detect_result = py
        .detach(|| scenechange::smart_scene_detect(args))
        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;

    Ok(detect_result)
}

#[pymodule(gil_used = true)]
fn pyav_scenechange(m: &Bound<'_, PyModule>) -> PyResult<()> {
    ctrlc::set_handler(|| {
        eprintln!("Warning: User Interupt (Ctrl-C) received, exiting.");
        exit(1)
    })
    .expect("Error setting Ctrl-C handler");
    m.add_function(wrap_pyfunction!(scene_detect, m)?)?;
    m.add_function(wrap_pyfunction!(smart_scene_detect, m)?)?;
    m.add_class::<ScMethod>()?;
    m.add_class::<DetectResult>()?;
    m.add_class::<BuildFeatures>()?;
    Ok(())
}

define_stub_info_gatherer!(stub_info);
