use pyo3::{
    prelude::*,
    types::{PyDict, PyTuple},
};
use pyo3_stub_gen::derive::{gen_stub_pyclass_enum, gen_stub_pymethods};

use crate::Action;

/// An action that can be taken in the world by the agents.
#[gen_stub_pyclass_enum]
#[pyclass(name = "Action", module = "lle.world", eq, eq_int, from_py_object)]
#[derive(Clone, Debug, PartialEq)]
pub enum PyAction {
    #[pyo3(name = "NORTH")]
    North = 0,
    #[pyo3(name = "SOUTH")]
    South = 1,
    #[pyo3(name = "EAST")]
    East = 2,
    #[pyo3(name = "WEST")]
    West = 3,
    #[pyo3(name = "STAY")]
    Stay = 4,
}

#[gen_stub_pymethods]
#[pymethods]
impl PyAction {
    /// Ordered list of all actions
    #[staticmethod]
    const fn variants() -> [PyAction; 5] {
        [
            PyAction::North,
            PyAction::South,
            PyAction::East,
            PyAction::West,
            PyAction::Stay,
        ]
    }

    /// The number of actions
    #[staticmethod]
    const fn cardinality() -> usize {
        PyAction::variants().len()
    }

    /// The (i, j) position delta in coordinates for this action.
    #[getter]
    fn delta(&self) -> (i32, i32) {
        let action: Action = self.into();
        action.delta()
    }

    #[new]
    fn new(value: u32) -> PyResult<Self> {
        match value {
            0 => Ok(Self::North),
            1 => Ok(Self::South),
            2 => Ok(Self::East),
            3 => Ok(Self::West),
            4 => Ok(Self::Stay),
            _ => Err(pyo3::exceptions::PyValueError::new_err(format!(
                "Invalid action value: {value}. Valid values for actions are between 0 and 4."
            ))),
        }
    }

    fn __hash__(&self) -> u64 {
        match self {
            Self::North => 0,
            Self::South => 1,
            Self::East => 2,
            Self::West => 3,
            Self::Stay => 4,
        }
    }

    fn __repr__(&self) -> String {
        format!("{self:?}")
    }

    /// The integer value of this action.
    #[getter]
    fn value(&self) -> u32 {
        match self {
            Self::North => 0,
            Self::South => 1,
            Self::East => 2,
            Self::West => 3,
            Self::Stay => 4,
        }
    }

    /// The opposite action of this action.
    /// Note: STAY is its own opposite.
    fn opposite(&self) -> Self {
        let action: Action = self.into();
        action.opposite().into()
    }

    fn __deepcopy__(&self, _memo: &Bound<'_, PyDict>) -> Self {
        self.clone()
    }

    fn __getstate__(&self) -> PyResult<u32> {
        Ok(self.value())
    }

    fn __setstate__(&mut self, state: u32) -> PyResult<()> {
        *self = match state {
            0 => Self::North,
            1 => Self::South,
            2 => Self::East,
            3 => Self::West,
            4 => Self::Stay,
            _ => {
                return Err(pyo3::exceptions::PyValueError::new_err(format!(
                    "Invalid action value: {state}. Valid values for actions are between 0 and 4."
                )));
            }
        };
        Ok(())
    }
    fn __getnewargs__<'a>(&self, py: Python<'a>) -> Bound<'a, PyTuple> {
        PyTuple::new(py, [self.value()].iter()).unwrap()
    }

    #[getter]
    #[gen_stub(skip)]
    fn name(&self) -> String {
        match self {
            Self::North => "NORTH".to_string(),
            Self::South => "SOUTH".to_string(),
            Self::East => "EAST".to_string(),
            Self::West => "WEST".to_string(),
            Self::Stay => "STAY".to_string(),
        }
    }
}

impl Into<Action> for &PyAction {
    fn into(self) -> Action {
        match self {
            PyAction::North => Action::North,
            PyAction::South => Action::South,
            PyAction::East => Action::East,
            PyAction::West => Action::West,
            PyAction::Stay => Action::Stay,
        }
    }
}

impl Into<Action> for PyAction {
    fn into(self) -> Action {
        match self {
            PyAction::North => Action::North,
            PyAction::South => Action::South,
            PyAction::East => Action::East,
            PyAction::West => Action::West,
            PyAction::Stay => Action::Stay,
        }
    }
}

impl From<&Action> for PyAction {
    fn from(action: &Action) -> Self {
        match action {
            Action::North => PyAction::North,
            Action::South => PyAction::South,
            Action::East => PyAction::East,
            Action::West => PyAction::West,
            Action::Stay => PyAction::Stay,
        }
    }
}

impl From<Action> for PyAction {
    fn from(action: Action) -> Self {
        match action {
            Action::North => PyAction::North,
            Action::South => PyAction::South,
            Action::East => PyAction::East,
            Action::West => PyAction::West,
            Action::Stay => PyAction::Stay,
        }
    }
}
