use std::marker::PhantomData;

use crate::TulispObject;
use crate::TulispValue;
use crate::error::Error;
use crate::object::Span;

#[derive(Debug, Clone)]
pub struct Cons {
    car: TulispObject,
    cdr: TulispObject,
}

impl PartialEq for Cons {
    fn eq(&self, other: &Self) -> bool {
        self.car.equal(&other.car) && self.cdr.equal(&other.cdr)
    }
}

impl Cons {
    pub fn new(car: TulispObject, cdr: TulispObject) -> Self {
        Cons { car, cdr }
    }

    pub fn push(&mut self, val: TulispObject) -> Result<(), Error> {
        self.push_with_meta(val, None, None)
    }

    pub(crate) fn push_with_meta(
        &mut self,
        val: TulispObject,
        span: Option<Span>,
        ctxobj: Option<TulispObject>,
    ) -> Result<(), Error> {
        let mut last = self.cdr.clone();

        while last.consp() {
            last = last.cdr()?;
        }
        if last.null() {
            last.assign(TulispValue::List {
                cons: Cons {
                    car: val,
                    cdr: TulispObject::nil(),
                },
                ctxobj,
            });
            last.with_span(span);
        } else {
            return Err(Error::type_mismatch("Cons: unable to push".to_string()));
        }
        Ok(())
    }

    pub fn append(&mut self, val: TulispObject) -> Result<(), Error> {
        let mut last = self.cdr.clone();
        let mut last_but_one = None;
        while last.consp() {
            last_but_one = Some(last.clone());
            last = last.cdr()?;
        }
        if last.null() {
            if let Some(last_but_one) = last_but_one {
                last_but_one.assign(TulispValue::List {
                    cons: Cons {
                        car: last_but_one.car()?,
                        cdr: val.deep_copy()?,
                    },
                    ctxobj: last_but_one.ctxobj(),
                })
            } else {
                self.cdr = val.deep_copy()?;
            }
        } else {
            return Err(Error::type_mismatch(format!("Unable to append: {}", val)));
        }
        Ok(())
    }

    pub(crate) fn car(&self) -> &TulispObject {
        &self.car
    }

    pub(crate) fn cdr(&self) -> &TulispObject {
        &self.cdr
    }
}

impl Drop for Cons {
    fn drop(&mut self) {
        if self.cdr.strong_count() > 1 || !self.cdr.consp() {
            return;
        }
        let mut cdr = self.cdr.take();
        while let TulispValue::List { cons, .. } = cdr {
            if cons.cdr.strong_count() > 1 {
                break;
            }
            cdr = cons.cdr.take();
        }
    }
}

#[derive(Default)]
pub struct BaseIter {
    pub(crate) next: TulispObject,
}

impl Iterator for BaseIter {
    type Item = TulispObject;

    fn next(&mut self) -> Option<Self::Item> {
        if self.next.null() {
            return None;
        }
        let car = self.next.car().ok()?;
        self.next = self.next.cdr().ok()?;
        Some(car)
    }
}

pub struct Iter<T: std::convert::TryFrom<TulispObject>> {
    iter: BaseIter,
    _d: PhantomData<T>,
}

impl<T: std::convert::TryFrom<TulispObject>> Iter<T> {
    pub fn new(iter: BaseIter) -> Self {
        Self {
            iter,
            _d: Default::default(),
        }
    }
}

impl<T: 'static + std::convert::TryFrom<TulispObject>> Iterator for Iter<T> {
    type Item = Result<T, Error>;

    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next().map(|vv| {
            vv.clone().try_into().map_err(|_| {
                let tid = std::any::type_name::<T>();
                Error::type_mismatch(format!("Iter<{}> can't handle {}", tid, vv))
            })
        })
    }
}
