use interface::{Term, ATermInner, TermPlaceholder};

use std::fmt;
use std::ops::Deref;
use std::rc::Rc;

pub trait ATermWrite {
    fn to_ascii<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result;
    fn to_taf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result;
    fn to_baf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result;
    fn to_saf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result;

    fn to_ascii_string(&self) -> Result<String, fmt::Error> {
        let mut s = String::new();
        self.to_ascii(&mut s)?;
        Ok(s)
    }

    fn to_taf_string(&self) -> Result<String, fmt::Error> {
        let mut s = String::new();
        self.to_taf(&mut s)?;
        Ok(s)
    }

    fn to_baf_string(&self) -> Result<String, fmt::Error> {
        let mut s = String::new();
        self.to_baf(&mut s)?;
        Ok(s)
    }

    fn to_saf_string(&self) -> Result<String, fmt::Error> {
        let mut s = String::new();
        self.to_saf(&mut s)?;
        Ok(s)
    }
}

impl ATermWrite for () {
    #[inline]
    fn to_ascii<W: fmt::Write>(&self, _writer: &mut W) -> fmt::Result {
        Ok(())
    }

    #[inline]
    fn to_taf<W: fmt::Write>(&self, _writer: &mut W) -> fmt::Result {
        Ok(())
    }

    #[inline]
    fn to_baf<W: fmt::Write>(&self, _writer: &mut W) -> fmt::Result {
        Ok(())
    }

    #[inline]
    fn to_saf<W: fmt::Write>(&self, _writer: &mut W) -> fmt::Result {
        Ok(())
    }
}

impl<T> ATermWrite for Rc<T>
    where T: ATermWrite
{
    #[inline]
    fn to_ascii<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        self.deref().to_ascii(writer)
    }

    #[inline]
    fn to_taf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        self.deref().to_taf(writer)
    }

    #[inline]
    fn to_baf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        self.deref().to_baf(writer)
    }

    #[inline]
    fn to_saf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        self.deref().to_saf(writer)
    }
}

impl<A, T> ATermWrite for [T]
    where A: ATermWrite,
          T: Deref<Target = A>
{
    fn to_ascii<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        let mut iter = self.iter().peekable();
        while let Some(t) = iter.next() {
            if iter.peek().is_some() {
                t.to_ascii(writer)?;
                write!(writer, ", ")?;
            } else {
                t.to_ascii(writer)?;
            }
        }
        Ok(())
    }

    fn to_taf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        let mut iter = self.iter().peekable();
        while let Some(t) = iter.next() {
            if iter.peek().is_some() {
                t.to_taf(writer)?;
                write!(writer, ", ")?;
            } else {
                t.to_taf(writer)?;
            }
        }
        Ok(())
    }

    #[allow(unused_variables)]
    fn to_baf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        unimplemented!()
    }

    #[allow(unused_variables)]
    fn to_saf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        unimplemented!()
    }
}

impl<Rec, B, S> ATermWrite for Term<Rec, B, S>
    where Rec: ATermWrite,
          B: ATermWrite,
          S: fmt::Display
{
    fn to_ascii<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        use self::Term::*;
        match *self {
            Int(ref i) => write!(writer, "{}", i),
            Real(ref bf32) => write!(writer, "{}", bf32.0),
            Application(ref cons, ref args) => {
                if args.is_empty() {
                    write!(writer, "{}(", cons)
                } else {
                    write!(writer, "{}(", cons)?;
                    list_to_ascii(args, writer)?;
                    write!(writer, ")")
                }
            }
            List(ref l) => {
                write!(writer, "[")?;
                list_to_ascii(l, writer)?;
                write!(writer, "]")
            }
            Placeholder(ref tp) => tp.to_ascii(writer),
            Blob(ref data) => data.to_ascii(writer),
            Long(ref l) => write!(writer, "{}", l),
        }
    }

    #[allow(unused_variables)]
    fn to_taf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        unimplemented!()
    }

    #[allow(unused_variables)]
    fn to_baf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        unimplemented!()
    }

    #[allow(unused_variables)]
    fn to_saf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        unimplemented!()
    }
}

impl<Rec> ATermWrite for TermPlaceholder<Rec>
    where Rec: ATermWrite
{
    fn to_ascii<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        use self::TermPlaceholder::*;
        match *self {
            Int => write!(writer, "<{}>", "int"),
            String => write!(writer, "<{}>", "str"),
            Real => write!(writer, "<{}>", "real"),
            Term => write!(writer, "<{}>", "term"),
            Application(ref args) => {
                write!(writer, "<appl(")?;
                list_to_ascii(args, writer)?;
                write!(writer, ")>")
            }
            List => write!(writer, "<{}>", "list"),
            Placeholder => write!(writer, "<{}>", "placeholder"),
            Blob => write!(writer, "<{}>", "blob"),
            Long => write!(writer, "<{}>", "long"),
        }
    }

    #[allow(unused_variables)]
    fn to_taf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        unimplemented!()
    }

    #[allow(unused_variables)]
    fn to_baf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        unimplemented!()
    }

    #[allow(unused_variables)]
    fn to_saf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        unimplemented!()
    }
}

impl<Rec, B, S> ATermWrite for ATermInner<Rec, B, S>
    where Rec: ATermWrite,
          B: ATermWrite,
          S: fmt::Display
{
    fn to_ascii<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        if self.annotations.is_empty() {
            write!(writer, "{}", self.term)
        } else {
            write!(writer, "{}{{", self.term)?;
            list_to_ascii(&self.annotations, writer)?;
            write!(writer, "}}")
        }
    }

    #[allow(unused_variables)]
    fn to_taf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        unimplemented!()
    }

    #[allow(unused_variables)]
    fn to_baf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        unimplemented!()
    }

    #[allow(unused_variables)]
    fn to_saf<W: fmt::Write>(&self, writer: &mut W) -> fmt::Result {
        unimplemented!()
    }
}

fn list_to_ascii<W, A>(list: &Box<[A]>, writer: &mut W) -> fmt::Result
    where W: fmt::Write,
          A: ATermWrite
{
    let mut iter = list.iter().peekable();
    while let Some(aterm_ref) = iter.next() {
        aterm_ref.to_ascii(writer)?;
        if iter.peek().is_some() {
            writer.write_str(",")?;
        }
    }
    Ok(())
}
