use {Path, PathSegment, QSelf, Ty};
use aster::ident::ToIdent;
use aster::invoke::{Invoke, Identity};
use aster::path::{PathBuilder, PathSegmentBuilder};
use aster::ty::TyBuilder;

// ////////////////////////////////////////////////////////////////////////////

pub struct QPathBuilder<F = Identity> {
    callback: F,
}

impl QPathBuilder {
    pub fn new() -> Self {
        QPathBuilder::with_callback(Identity)
    }
}

impl<F> QPathBuilder<F>
    where F: Invoke<(QSelf, Path)>
{
    /// Construct a `QPathBuilder` that will call the `callback` with a constructed `QSelf`
    /// and `Path`.
    pub fn with_callback(callback: F) -> Self {
        QPathBuilder { callback: callback }
    }

    /// Build a qualified path first by starting with a type builder.
    pub fn with_ty(self, ty: Ty) -> QPathTyBuilder<F> {
        QPathTyBuilder {
            builder: self,
            ty: ty,
        }
    }

    /// Build a qualified path first by starting with a type builder.
    pub fn ty(self) -> TyBuilder<Self> {
        TyBuilder::with_callback(self)
    }

    /// Build a qualified path with a concrete type and path.
    pub fn build(self, qself: QSelf, path: Path) -> F::Result {
        self.callback.invoke((qself, path))
    }
}

impl<F> Invoke<Ty> for QPathBuilder<F>
    where F: Invoke<(QSelf, Path)>
{
    type Result = QPathTyBuilder<F>;

    fn invoke(self, ty: Ty) -> QPathTyBuilder<F> {
        self.with_ty(ty)
    }
}

// ////////////////////////////////////////////////////////////////////////////

pub struct QPathTyBuilder<F> {
    builder: QPathBuilder<F>,
    ty: Ty,
}

impl<F> QPathTyBuilder<F>
    where F: Invoke<(QSelf, Path)>
{
    /// Build a qualified path with a path builder.
    // Clippy false positive
    // https://github.com/Manishearth/rust-clippy/issues/1285
    #[cfg_attr(feature = "clippy", allow(wrong_self_convention))]
    pub fn as_(self) -> PathBuilder<Self> {
        PathBuilder::with_callback(self)
    }

    pub fn id<T>(self, id: T) -> F::Result
        where T: ToIdent
    {
        let path = Path {
            global: false,
            segments: vec![],
        };
        self.as_().build(path).id(id)
    }

    pub fn segment<T>(self, id: T) -> PathSegmentBuilder<QPathQSelfBuilder<F>>
        where T: ToIdent
    {
        let path = Path {
            global: false,
            segments: vec![],
        };
        self.as_().build(path).segment(id)
    }
}

impl<F> Invoke<Path> for QPathTyBuilder<F>
    where F: Invoke<(QSelf, Path)>
{
    type Result = QPathQSelfBuilder<F>;

    fn invoke(self, path: Path) -> QPathQSelfBuilder<F> {
        QPathQSelfBuilder {
            builder: self.builder,
            qself: QSelf {
                ty: Box::new(self.ty),
                position: path.segments.len(),
            },
            path: path,
        }
    }
}

// ////////////////////////////////////////////////////////////////////////////

pub struct QPathQSelfBuilder<F> {
    builder: QPathBuilder<F>,
    qself: QSelf,
    path: Path,
}

impl<F> QPathQSelfBuilder<F>
    where F: Invoke<(QSelf, Path)>
{
    pub fn id<T>(self, id: T) -> F::Result
        where T: ToIdent
    {
        self.segment(id).build()
    }

    pub fn segment<T>(self, id: T) -> PathSegmentBuilder<QPathQSelfBuilder<F>>
        where T: ToIdent
    {
        PathSegmentBuilder::with_callback(id, self)
    }
}

impl<F> Invoke<PathSegment> for QPathQSelfBuilder<F>
    where F: Invoke<(QSelf, Path)>
{
    type Result = F::Result;

    fn invoke(mut self, segment: PathSegment) -> F::Result {
        self.path.segments.push(segment);
        self.builder.build(self.qself, self.path)
    }
}
