use super::Scored;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

const MIN: &str = "min";
const MAX: &str = "max";

#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Objective {
    Single(Optimize),
    Multi(Vec<Optimize>),
}

impl Objective {
    pub fn is_single(&self) -> bool {
        matches!(self, Objective::Single(_))
    }

    pub fn is_multi(&self) -> bool {
        matches!(self, Objective::Multi(_))
    }

    pub fn dims(&self) -> usize {
        match self {
            Objective::Single(_) => 1,
            Objective::Multi(opts) => opts.len(),
        }
    }

    pub fn validate<T: AsRef<[K]>, K>(&self, values: &T) -> bool {
        match self {
            Objective::Single(_) => values.as_ref().len() == 1,
            Objective::Multi(opts) => values.as_ref().len() == opts.len(),
        }
    }

    pub fn cmp<T>(&self, a: &T, b: &T) -> std::cmp::Ordering
    where
        T: PartialOrd,
    {
        match self {
            Objective::Single(opt) => {
                if opt.is_better(a, b) {
                    std::cmp::Ordering::Less
                } else if opt.is_better(b, a) {
                    std::cmp::Ordering::Greater
                } else {
                    std::cmp::Ordering::Equal
                }
            }
            Objective::Multi(opts) => {
                for &opt in opts {
                    if opt.is_better(a, b) {
                        return std::cmp::Ordering::Less;
                    } else if opt.is_better(b, a) {
                        return std::cmp::Ordering::Greater;
                    }
                }
                std::cmp::Ordering::Equal
            }
        }
    }

    pub fn sort<T: AsMut<[K]>, K: Scored + PartialOrd>(&self, population: &mut T) {
        match self {
            Objective::Single(opt) => opt.sort(population),
            Objective::Multi(_) => population.as_mut().sort_unstable_by(|one, two| {
                if let (Some(score_one), Some(score_two)) = (one.score(), two.score()) {
                    self.dominance_cmp(score_one.as_ref(), score_two.as_ref())
                } else {
                    std::cmp::Ordering::Equal
                }
            }),
        }
    }

    fn dominance_cmp<T>(&self, a: &[T], b: &[T]) -> std::cmp::Ordering
    where
        T: PartialOrd,
    {
        match self {
            Objective::Single(opt) => {
                if opt.is_better(&a[0], &b[0]) {
                    std::cmp::Ordering::Less
                } else if opt.is_better(&b[0], &a[0]) {
                    std::cmp::Ordering::Greater
                } else {
                    std::cmp::Ordering::Equal
                }
            }
            Objective::Multi(opts) => {
                for ((a, b), opt) in a.iter().zip(b.iter()).zip(opts) {
                    if opt.is_better(a, b) {
                        return std::cmp::Ordering::Less;
                    } else if opt.is_better(b, a) {
                        return std::cmp::Ordering::Greater;
                    }
                }
                std::cmp::Ordering::Equal
            }
        }
    }

    pub fn is_better<T>(&self, a: &T, b: &T) -> bool
    where
        T: PartialOrd,
    {
        match self {
            Objective::Single(opt) => opt.is_better(a, b),
            Objective::Multi(opts) => {
                for &opt in opts {
                    if !opt.is_better(a, b) {
                        return false;
                    }
                }
                true
            }
        }
    }
}

impl AsRef<[Optimize]> for Objective {
    fn as_ref(&self) -> &[Optimize] {
        match self {
            Objective::Single(opt) => std::slice::from_ref(opt),
            Objective::Multi(opts) => opts.as_slice(),
        }
    }
}

impl Default for Objective {
    fn default() -> Self {
        Objective::Single(Optimize::Maximize)
    }
}

impl From<Vec<Optimize>> for Objective {
    fn from(opts: Vec<Optimize>) -> Self {
        if opts.len() == 1 {
            Objective::Single(opts[0])
        } else {
            Objective::Multi(opts)
        }
    }
}

impl From<Vec<&str>> for Objective {
    fn from(values: Vec<&str>) -> Self {
        let opts: Vec<Optimize> = values.into_iter().map(|s| Optimize::from(s)).collect();

        if opts.len() == 1 {
            Objective::Single(opts[0])
        } else {
            Objective::Multi(opts)
        }
    }
}

impl Into<Vec<&str>> for Objective {
    fn into(self) -> Vec<&'static str> {
        match self {
            Objective::Single(opt) => vec![opt.into()],
            Objective::Multi(opts) => opts.into_iter().map(|opt| opt.into()).collect(),
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Optimize {
    Minimize,
    Maximize,
}

impl Optimize {
    pub fn sort<T: AsMut<[K]>, K: PartialOrd>(&self, population: &mut T) {
        match self {
            Optimize::Minimize => population
                .as_mut()
                .sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)),
            Optimize::Maximize => population
                .as_mut()
                .sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)),
        }
    }

    pub fn is_better<T>(&self, a: &T, b: &T) -> bool
    where
        T: PartialOrd,
    {
        match self {
            Optimize::Minimize => a < b,
            Optimize::Maximize => a > b,
        }
    }

    pub fn is_minimize(&self) -> bool {
        matches!(self, Optimize::Minimize)
    }

    pub fn is_maximize(&self) -> bool {
        matches!(self, Optimize::Maximize)
    }
}

impl From<&str> for Optimize {
    fn from(value: &str) -> Self {
        match value.to_lowercase().as_str() {
            MIN => Optimize::Minimize,
            MAX => Optimize::Maximize,
            _ => Optimize::Maximize,
        }
    }
}

impl Into<&str> for Optimize {
    fn into(self) -> &'static str {
        match self {
            Optimize::Minimize => MIN,
            Optimize::Maximize => MAX,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_optimize_is_better() {
        assert!(Optimize::Minimize.is_better(&1, &2));
        assert!(!Optimize::Minimize.is_better(&2, &1));
        assert!(Optimize::Maximize.is_better(&2, &1));
        assert!(!Optimize::Maximize.is_better(&1, &2));
    }

    #[test]
    fn test_objective_is_better_single() {
        let obj = Objective::Single(Optimize::Minimize);
        assert!(obj.is_better(&1, &2));
        assert!(!obj.is_better(&2, &1));
        let obj = Objective::Single(Optimize::Maximize);
        assert!(obj.is_better(&2, &1));
        assert!(!obj.is_better(&1, &2));
    }
}
