use crate::parse_error::*;
use crate::refine::*;
pub use std::fmt::{Debug, Display};
pub use std::hash::{Hash, Hasher};
pub use std::marker::PhantomData;
pub use std::ops::Deref;
pub use std::str::FromStr;

#[derive(Debug)]
pub struct Tagged<T, D, F>(T, PhantomData<(D, F)>);

impl<T, D, F> Tagged<T, D, F> {
    pub fn new(rep: T) -> Result<Self, F::RefineError>
    where
        F: Refine<T>,
    {
        F::refine(rep).map(|next| Tagged(next, PhantomData))
    }
    pub fn rep(&self) -> &T {
        &self.0
    }
    pub fn untag(self) -> T {
        self.0
    }
}

impl<T: Eq, D, F> Eq for Tagged<T, D, F> {}

impl<T: PartialEq, D, F> PartialEq for Tagged<T, D, F> {
    fn eq(&self, other: &Self) -> bool {
        self.rep() == other.rep()
    }
}

impl<T: Ord, D, F> Ord for Tagged<T, D, F> {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.rep().cmp(other.rep())
    }
}

impl<T: PartialOrd, D, F> PartialOrd for Tagged<T, D, F> {
    fn partial_cmp(
        &self,
        other: &Self,
    ) -> Option<std::cmp::Ordering> {
        self.rep().partial_cmp(other.rep())
    }
}

impl<T: Clone, D, F> Clone for Tagged<T, D, F> {
    fn clone(&self) -> Self {
        Tagged(self.rep().clone(), PhantomData)
    }
}

impl<T: Copy, D, F> Copy for Tagged<T, D, F> {}

impl<T: Display, D, F> Display for Tagged<T, D, F> {
    fn fmt(
        &self,
        f: &mut std::fmt::Formatter<'_>,
    ) -> std::fmt::Result {
        self.rep().fmt(f)
    }
}

impl<T: Hash, D, F> Hash for Tagged<T, D, F> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.rep().hash(state);
    }
}

impl<T, D, F> Deref for Tagged<T, D, F> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        self.rep()
    }
}

impl<T, D, F> FromStr for Tagged<T, D, F>
where
    T: Debug + FromStr,
    D: Debug,
    F: Debug + Refine<T>,
    T::Err: Debug,
    F::RefineError: Debug,
{
    type Err = ParseError<T, D, F>;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Tagged::new(T::from_str(s).map_err(|e| {
            ParseError::Decode(e, PhantomData)
        })?)
        .map_err(|e| ParseError::Refine(e, PhantomData))
    }
}

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

#[cfg(feature = "serde")]
impl<T: Serialize, D, F> Serialize for Tagged<T, D, F> {
    fn serialize<S>(
        &self,
        serializer: S,
    ) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.rep().serialize(serializer)
    }
}

#[cfg(feature = "serde")]
impl<'de, T, D, F> Deserialize<'de> for Tagged<T, D, F>
where
    T: Deserialize<'de>,
    F: Refine<T>,
    F::RefineError: Display,
{
    fn deserialize<DE>(
        deserializer: DE,
    ) -> Result<Self, DE::Error>
    where
        DE: serde::Deserializer<'de>,
    {
        T::deserialize(deserializer).and_then(|rep| {
            Tagged::new(rep)
                .map_err(serde::de::Error::custom)
        })
    }
}

#[cfg(feature = "diesel")]
mod diesel_impl {
    use super::*;
    use diesel::Queryable;
    use diesel::backend::Backend;
    use diesel::deserialize::FromSql;
    use diesel::expression::AsExpression;
    use diesel::serialize::{Output, ToSql};
    use diesel::sql_types::SingleValue;
    use std::error::Error;

    impl<T, D, F, ST> AsExpression<ST> for Tagged<T, D, F>
    where
        T: Clone + AsExpression<ST>,
        ST: SingleValue,
    {
        type Expression = T::Expression;
        fn as_expression(self) -> Self::Expression {
            self.rep().clone().as_expression()
        }
    }

    impl<T, D, F, ST> AsExpression<ST> for &Tagged<T, D, F>
    where
        T: Clone + AsExpression<ST>,
        ST: SingleValue,
    {
        type Expression = T::Expression;
        fn as_expression(self) -> Self::Expression {
            self.rep().clone().as_expression()
        }
    }

    impl<T, D, F, ST, DB> ToSql<ST, DB> for Tagged<T, D, F>
    where
        T: ToSql<ST, DB>,
        D: Debug,
        F: Debug,
        DB: Backend,
    {
        fn to_sql<'a>(
            &'a self,
            out: &mut Output<'a, '_, DB>,
        ) -> diesel::serialize::Result {
            self.rep().to_sql(out)
        }
    }

    impl<T, D, F, ST, DB> FromSql<ST, DB> for Tagged<T, D, F>
    where
        T: FromSql<ST, DB>,
        F: Refine<T>,
        F::RefineError: 'static + Error + Send + Sync,
        DB: Backend,
    {
        fn from_sql(
            bytes: DB::RawValue<'_>,
        ) -> diesel::deserialize::Result<Self> {
            let rep = T::from_sql(bytes)?;
            Ok(Tagged::new(rep).map_err(Box::new)?)
        }
    }

    impl<T, D, F, ST, DB> Queryable<ST, DB> for Tagged<T, D, F>
    where
        T: Queryable<ST, DB>,
        F: Refine<T>,
        F::RefineError: 'static + Error + Send + Sync,
        DB: Backend,
    {
        type Row = T::Row;
        fn build(
            row: Self::Row,
        ) -> diesel::deserialize::Result<Self> {
            let rep = Queryable::build(row)?;
            Ok(Tagged::new(rep).map_err(Box::new)?)
        }
    }
}
