use std::fmt;
use std::hash::Hash;
use std::str::FromStr;

use crate::Error;

use super::version::{ParsedVersion, Revision, Version};
use super::{parse, Dep, Intersects};

pub enum CpvOrDep {
    Cpv(Cpv),
    Dep(Dep),
}

impl FromStr for CpvOrDep {
    type Err = Error;

    fn from_str(s: &str) -> crate::Result<Self> {
        if let Ok(d) = Dep::from_str(s) {
            Ok(CpvOrDep::Dep(d))
        } else {
            Ok(CpvOrDep::Cpv(Cpv::from_str(s)?))
        }
    }
}

impl Intersects<CpvOrDep> for CpvOrDep {
    fn intersects(&self, other: &CpvOrDep) -> bool {
        use CpvOrDep::*;
        match (self, other) {
            (Cpv(obj1), Cpv(obj2)) => obj1.intersects(obj2),
            (Cpv(obj1), Dep(obj2)) => obj1.intersects(obj2),
            (Dep(obj1), Cpv(obj2)) => obj1.intersects(obj2),
            (Dep(obj1), Dep(obj2)) => obj1.intersects(obj2),
        }
    }
}

/// Parsed package identifier from borrowed input string.
#[derive(Debug)]
pub(crate) struct ParsedCpv<'a> {
    pub(crate) category: &'a str,
    pub(crate) package: &'a str,
    pub(crate) version: ParsedVersion<'a>,
    pub(crate) version_str: &'a str,
}

impl ParsedCpv<'_> {
    pub(crate) fn into_owned(self) -> crate::Result<Cpv> {
        Ok(Cpv {
            category: self.category.to_string(),
            package: self.package.to_string(),
            version: self.version.into_owned(self.version_str)?,
        })
    }
}

/// Package identifier.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Cpv {
    category: String,
    package: String,
    version: Version,
}

impl Cpv {
    /// Create a new Cpv from a given string (e.g. cat/pkg-1).
    pub fn new(s: &str) -> crate::Result<Self> {
        parse::cpv(s)
    }

    /// Verify a string represents a valid CPV.
    pub fn valid(s: &str) -> crate::Result<()> {
        parse::cpv_str(s)?;
        Ok(())
    }

    /// Return a Cpv's category.
    pub fn category(&self) -> &str {
        &self.category
    }

    /// Return a Cpv's package.
    pub fn package(&self) -> &str {
        &self.package
    }

    /// Return a Cpv's version.
    pub fn version(&self) -> &Version {
        &self.version
    }

    /// Return a Cpv's revision.
    pub fn revision(&self) -> Option<&Revision> {
        self.version.revision()
    }

    /// Return the package name and version.
    pub fn p(&self) -> String {
        format!("{}-{}", self.package(), self.version.base())
    }

    /// Return the package name, version, and revision.
    pub fn pf(&self) -> String {
        format!("{}-{}", self.package(), self.pvr())
    }

    /// Return the revision.
    pub fn pr(&self) -> String {
        format!("r{}", self.revision().map(|r| r.as_str()).unwrap_or("0"))
    }

    /// Return the version.
    pub fn pv(&self) -> String {
        self.version.base().to_string()
    }

    /// Return the version and revision.
    pub fn pvr(&self) -> String {
        self.version.as_str().to_string()
    }

    /// Return the category and package.
    pub fn cpn(&self) -> String {
        format!("{}/{}", self.category, self.package)
    }
}

impl FromStr for Cpv {
    type Err = Error;

    fn from_str(s: &str) -> crate::Result<Self> {
        parse::cpv(s)
    }
}

impl fmt::Display for Cpv {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}/{}-{}", self.category, self.package, self.version.as_str())
    }
}

/// Determine if two Cpvs intersect.
impl Intersects<Cpv> for Cpv {
    fn intersects(&self, other: &Cpv) -> bool {
        self == other
    }
}

/// Determine if a Cpv intersects with a package dependency.
impl Intersects<Dep> for Cpv {
    fn intersects(&self, other: &Dep) -> bool {
        other.intersects(self)
    }
}

impl TryFrom<&str> for Cpv {
    type Error = Error;

    fn try_from(value: &str) -> crate::Result<Cpv> {
        Cpv::from_str(value)
    }
}

impl TryFrom<&Cpv> for Cpv {
    type Error = Error;

    fn try_from(value: &Cpv) -> crate::Result<Cpv> {
        Ok(value.clone())
    }
}
