pub use lazy_static::lazy_static;

pub use preserves;
pub use preserves::value::Reader;
pub use preserves::value::boundary as B;

pub mod interpret;

use preserves::value::ArcValue;
use preserves::value::Domain;
use preserves::value::IOValue;
use preserves::value::NestedValue;
use preserves::value::NoEmbeddedDomainCodec;
use preserves::value::Value;

use std::convert::From;
use std::convert::Into;
use std::convert::TryFrom;
use std::io;
use std::sync::Arc;

use thiserror::Error;

pub trait NestedValueCodec {} // marker trait
impl NestedValueCodec for () {}

pub trait Parse<L, Value: NestedValue>: Sized {
    fn parse(language: L, value: &Value) -> Result<Self, ParseError>;
}

impl<'a, T: NestedValueCodec, Value: NestedValue> Parse<&'a T, Value> for Value {
    fn parse(_language: &'a T, value: &Value) -> Result<Self, ParseError> {
        Ok(value.clone())
    }
}

pub trait Unparse<L, Value: NestedValue> {
    fn unparse(&self, language: L) -> Value;
}

impl<'a, T: NestedValueCodec, Value: NestedValue> Unparse<&'a T, Value> for Value {
    fn unparse(&self, _language: &'a T) -> Value {
        self.clone()
    }
}

pub trait Codec<N: NestedValue> {
    fn parse<'a, T: Parse<&'a Self, N>>(&'a self, value: &N) -> Result<T, ParseError>;
    fn unparse<'a, T: Unparse<&'a Self, N>>(&'a self, value: &T) -> N;
}

impl<L, N: NestedValue> Codec<N> for L {
    fn parse<'a, T: Parse<&'a L, N>>(&'a self, value: &N) -> Result<T, ParseError> {
        T::parse(self, value)
    }

    fn unparse<'a, T: Unparse<&'a L, N>>(&'a self, value: &T) -> N {
        value.unparse(self)
    }
}

pub trait Deserialize<N: NestedValue>
where
    Self: Sized
{
    fn deserialize<'de, R: Reader<'de, N>>(r: &mut R) -> Result<Self, ParseError>;
}

pub fn decode_lit<N: NestedValue>(bs: &[u8]) -> io::Result<N> {
    preserves::value::packed::from_bytes(bs, NoEmbeddedDomainCodec)
}

pub fn decode_embedded<D: Domain>(
    v: &IOValue,
) -> Result<ArcValue<Arc<D>>, ParseError>
where
    for<'a> D: TryFrom<&'a IOValue, Error = ParseError>
{
    v.copy_via(&mut |d| Ok(Value::Embedded(Arc::new(D::try_from(d)?))))
}

pub fn encode_embedded<D: Domain>(
    v: &ArcValue<Arc<D>>,
) -> IOValue
where
    for<'a> IOValue: From<&'a D>
{
    v.copy_via::<_, _, std::convert::Infallible>(
        &mut |d| Ok(Value::Embedded(IOValue::from(d)))).unwrap()
}

#[derive(Error, Debug)]
pub enum ParseError {
    #[error("Input not conformant with Schema: {0}")]
    ConformanceError(&'static str),
    #[error(transparent)]
    Preserves(preserves::error::Error),
}

impl From<preserves::error::Error> for ParseError {
    fn from(v: preserves::error::Error) -> Self {
        match v {
            preserves::error::Error::Expected(_, _) =>
                ParseError::ConformanceError("preserves::error::Error::Expected"),
            _ =>
                ParseError::Preserves(v),
        }
    }
}

impl From<io::Error> for ParseError {
    fn from(v: io::Error) -> Self {
        preserves::error::Error::from(v).into()
    }
}

impl From<ParseError> for io::Error {
    fn from(v: ParseError) -> Self {
        match v {
            ParseError::ConformanceError(_) => io::Error::new(io::ErrorKind::InvalidData, v),
            ParseError::Preserves(e) => e.into(),
        }
    }
}

impl ParseError {
    pub fn conformance_error(context: &'static str) -> Self {
        ParseError::ConformanceError(context)
    }

    pub fn is_conformance_error(&self) -> bool {
        return if let ParseError::ConformanceError(_) = self { true } else { false }
    }
}
