use std::borrow::Borrow;
use std::fmt;

pub use bad_idea::BrokenF32;
use print::*;

/// An all-in-one package for building `ATerm`s. Maybe be pure, or have internal mutability to give
/// you maximally shared `ATerm`s.
pub trait ATermFactory<B, S> {
    /// The `ATerm` the factory builds
    type ATerm: ATerm<Blob = B, Rec = Self::ATermRef>;
    /// The reference to an `ATerm` that's returned. You usually want these to be cheaply cloneable!
    type ATermRef: Borrow<Self::ATerm>;

    fn int(&self, value: i32) -> Self::ATermRef {
        self.no_annos(self.t_int(value))
    }
    /// The string variant in ATerms is represented as an application with zero children!
    fn string(&self, value: S) -> Self::ATermRef {
        self.no_annos(self.t_string(value))
    }
    /// The tuple in ATerms is represented as an application with an empty constructor string!
    fn tuple<I>(&self, children: I) -> Self::ATermRef
        where I: IntoIterator<Item = Self::ATermRef> {
        self.no_annos(self.t_tuple(children))
    }
    fn real(&self, value: f32) -> Self::ATermRef {
        self.no_annos(self.t_real(value))
    }
    fn application<I>(&self, constructor: S, children: I) -> Self::ATermRef
        where I: IntoIterator<Item = Self::ATermRef>
    {
        self.no_annos(self.t_application(constructor, children))
    }
    fn list<I>(&self, value: I) -> Self::ATermRef
        where I: IntoIterator<Item = Self::ATermRef>
    {
        self.no_annos(self.t_list(value))
    }
    fn placeholder(&self, value: TermPlaceholder<Self::ATermRef>) -> Self::ATermRef {
        self.no_annos(self.t_placeholder(value))
    }
    fn blob(&self, value: B) -> Self::ATermRef {
        self.no_annos(self.t_blob(value))
    }
    fn long(&self, value: i64) -> Self::ATermRef {
        self.no_annos(self.t_long(value))
    }

    fn t_int(&self, value: i32) -> Term<Self::ATermRef, B, S> {
        Term::Int(value)
    }
    /// The string variant in ATerms is represented as an application with zero children!
    fn t_string(&self, value: S) -> Term<Self::ATermRef, B, S>;
//        self.t_application(format!("{:?}", value), iter::empty())
    /// The tuple in ATerms is represented as an application with an empty constructor string!
    fn t_tuple<I>(&self, children: I) -> Term<Self::ATermRef, B, S>
        where I: IntoIterator<Item = Self::ATermRef>;
//        self.t_application(String::new(), children)
    fn t_real(&self, value: f32) -> Term<Self::ATermRef, B, S> {
        Term::Real(BrokenF32(value))
    }
    fn t_application<I>(&self, constructor: S, children: I) -> Term<Self::ATermRef, B, S>
        where I: IntoIterator<Item = Self::ATermRef>
    {
        Term::Application(constructor,
                          children
                              .into_iter()
                              .collect::<Vec<_>>()
                              .into_boxed_slice())
    }
    fn t_list<I>(&self, value: I) -> Term<Self::ATermRef, B, S>
        where I: IntoIterator<Item = Self::ATermRef>
    {
        Term::List(value.into_iter().collect::<Vec<_>>().into_boxed_slice())
    }
    fn t_placeholder(&self, value: TermPlaceholder<Self::ATermRef>) -> Term<Self::ATermRef, B, S> {
        Term::Placeholder(value)
    }
    fn t_blob(&self, value: B) -> Term<Self::ATermRef, B, S> {
        Term::Blob(value)
    }
    fn t_long(&self, value: i64) -> Term<Self::ATermRef, B, S> {
        Term::Long(value)
    }

    fn no_annos(&self, term: Term<Self::ATermRef, B, S>) -> Self::ATermRef;

    fn with_annos<A>(&self, term: Term<Self::ATermRef, B, S>, annos: A) -> Self::ATermRef
        where A: IntoIterator<Item = Self::ATermRef>;
}

pub trait SharedATermFactory<B, S>: ATermFactory<B, S> {
    fn get_shared(&self, value: Self::ATermRef) -> Self::ATermRef
        where Self::ATermRef: Clone,
              B: Clone;
}

pub trait Cons {
    /// Returns whether this constructor is a tuple constructor
    fn is_tuple_cons(&self) -> bool;
    /// Returns Some(string) when the constructor encapsulates a string
    fn to_string_option(&self) -> Option<String>;
    /// Returns whether the constructor encapsulates a string
    fn is_string_cons(&self) -> bool {
        self.to_string_option().is_some()
    }
}

impl<'a> Cons for &'a str {
    fn is_tuple_cons(&self) -> bool {
        self.is_empty()
    }

    fn to_string_option(&self) -> Option<String> {
        use utils::string_unescape;
        if self.is_string_cons() {
            Some(string_unescape(self))
        } else {
            None
        }
    }

    fn is_string_cons(&self) -> bool {
        self.starts_with('"') && self.ends_with('"')
    }
}

impl Cons for String {
    fn is_tuple_cons(&self) -> bool {
        self.is_empty()
    }

    fn to_string_option(&self) -> Option<String> {
        use utils::string_unescape;
        if self.is_string_cons() {
            Some(string_unescape(self))
        } else {
            None
        }
    }

    fn is_string_cons(&self) -> bool {
        self.starts_with('"') && self.ends_with('"')
    }
}

/// The basic term type, without annotations
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum Term<Rec, Blob, S> {
    /// An 32bit signed integer
    Int(i32),
    /// A 64bit signed integer. This is an optional component in the spec, but it's easy to support.
    Long(i64),
    /// An 32bit floating point number, but with a broken equality so a coherent implementation of
    /// Hash could be given (if NaN != NaN, then what Hash should it get?). You shouldn't directly
    /// check equality of floating point numbers anyways because of rounding.
    Real(BrokenF32),
    /// The main thing: Application of a constructor, using a string for the constructor name.
    Application(S, Box<[Rec]>),
    /// A list of ATerms.
    List(Box<[Rec]>),
    /// A placeholder for pattern matching. Only here for compatibility with the spec, use Rust's
    /// pattern matching facilities instead!
    Placeholder(TermPlaceholder<Rec>),
    /// A Binary Large OBject. Here used to embed any other type. Note it will need to implement
    /// some traits if you still want to parse or print it.
    Blob(Blob),
}

impl<Rec, B, S> fmt::Display for Term<Rec, B, S>
    where Rec: ATermWrite,
          B: ATermWrite,
          S: fmt::Display
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.to_ascii(f)
    }
}

/// These placeholders match the constructors of Term.
/// The Application has sub-placeholders for the children of the constructor. The Term placeholder
/// is basically a wildcard, it matches anything.
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum TermPlaceholder<Rec> {
    Int,
    String,
    Real,
    Term,
    Application(Box<[Rec]>),
    List,
    Placeholder,
    Blob,
    Long,
}

impl<Rec> fmt::Display for TermPlaceholder<Rec>
    where Rec: ATermWrite
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.to_ascii(f)
    }
}

/// The annotated term. This only combines the term with the annotations.
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct ATermInner<ATerm, B, S> {
    /// The actual term.
    pub term: Term<ATerm, B, S>,
    /// The annotations on the term. The spec says this is an ATerm list of pairs of ATerms. We
    /// extend this to a general list of aterms so we can support more systems (e.g. Stratego/XT).
    pub annotations: Box<[ATerm]>,
}

impl<ATerm, B, S> ATermInner<ATerm, B, S> {
    pub fn no_annos(term: Term<ATerm, B, S>) -> Self {
        ATermInner {
            term: term,
            annotations: Box::new([]),
        }
    }

    pub fn with_annos<A>(term: Term<ATerm, B, S>, annos: A) -> Self
        where A: IntoIterator<Item =ATerm>
    {
        ATermInner {
            term: term,
            annotations: annos.into_iter().collect::<Vec<_>>().into_boxed_slice(),
        }
    }
}

/// The generic description of what it means to be an `ATerm`. That is: you need to be convertible
/// to an `ATermInner` to provide the minimum of term and annotations. Also gives you useful getters
/// so matching on different terms is easier.
pub trait ATerm {
    /// Basically the current type, but you may want to add something extra, so this is more
    /// flexible.
    type Rec: Borrow<Self>;
    /// The extension point to add more variants to terms.
    type Blob;
    /// The type of the constructor to use
    type Cons: Cons;

    fn into_inner(self) -> ATermInner<Self::Rec, Self::Blob, Self::Cons>;
    fn as_inner(&self) -> &ATermInner<Self::Rec, Self::Blob, Self::Cons>;

    fn get_int(&self) -> Option<i32> {
        match self.as_inner().term {
            Term::Int(i) => Some(i),
            _ => None,
        }
    }

    fn get_long(&self) -> Option<i64> {
        match self.as_inner().term {
            Term::Long(l) => Some(l),
            _ => None,
        }
    }

    fn get_real(&self) -> Option<f32> {
        match self.as_inner().term {
            Term::Real(BrokenF32(r)) => Some(r),
            _ => None,
        }
    }

    fn get_application(&self) -> Option<(&Cons, &[Self::Rec])> {
        match self.as_inner().term {
            Term::Application(ref c, ref r) => Some((c, r)),
            _ => None,
        }
    }

    fn get_list(&self) -> Option<&[Self::Rec]> {
        match self.as_inner().term {
            Term::List(ref r) => Some(r),
            _ => None,
        }
    }

    fn get_placeholder(&self) -> Option<&TermPlaceholder<Self::Rec>> {
        match self.as_inner().term {
            Term::Placeholder(ref tp) => Some(tp),
            _ => None,
        }
    }

    fn get_blob(&self) -> Option<&Self::Blob> {
        match self.as_inner().term {
            Term::Blob(ref b) => Some(b),
            _ => None,
        }
    }

    fn get_string(&self) -> Option<String> {
        match self.as_inner().term {
            Term::Application(ref c, ref r) if r.is_empty() => c.to_string_option(),
            _ => None,
        }
    }

    fn get_tuple(&self) -> Option<&[Self::Rec]> {
        match self.as_inner().term {
            Term::Application(ref c, ref r) if c.is_tuple_cons() => Some(r),
            _ => None,
        }
    }
}

impl<Rec, B, S> ATerm for ATermInner<Rec, B, S>
    where Rec: Borrow<ATermInner<Rec, B, S>>,
          S: Cons
{
    type Rec = Rec;
    type Blob = B;
    type Cons = S;

    #[inline]
    fn into_inner(self) -> ATermInner<Rec, B, S> {
        self
    }
    #[inline]
    fn as_inner(&self) -> &ATermInner<Self::Rec, Self::Blob, Self::Cons> {
        self
    }
}

impl<ATerm, B, S> fmt::Display for ATermInner<ATerm, B, S>
    where ATerm: ATermWrite,
          B: ATermWrite,
          S: fmt::Display
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.to_ascii(f)
    }
}
