//! Utilities to convert from typeless instances defined in [`crate::rcore`].
//! See [`TypelessMentionable::cast`]/[`Point::cast`].
//!
//! p.s. the implementation is horrific.

use std::convert::identity;

use crate::func::context::*;
use crate::rcore::*;

use super::{typeless::*, wrapped_origin::*, *};

struct CastResolver<'a, Ctx: Context<'a>> {
    points: Vec<Point<'a, Ctx, TypelessMentionable<'a, Ctx>>>,
}

/// Used to represent errors that might arise during resolution/parsion.
/// Is expected to be classified as [`Context::LookupError`] rather than [`Factory::ParseError`].
/// [`CastError::AddressIndexOutOfBounds`] and [`CastError::AddressPointMismatch`]
/// variants generally shouldn't happen.
#[derive(Debug)]
pub enum CastError<'a> {
    Typeless(TypelessError<'a>),
    /// If you don't know what that means, it's a good idea to [`panic!`].
    /// Happens due to internal resolver using indices rather than `point`s.
    /// This error usually indicates inconsistent behaviour
    /// of [`Mentionable::points_typed`] and/or [`Mentionable::topology`].
    AddressIndexOutOfBounds {
        index: usize,
        length: usize,
    },
    /// See [`CastError::AddressIndexOutOfBounds`].
    AddressPointMismatch {
        expected: Hash,
        received: Hash,
        index: usize,
    },
}

impl<'a> Display for CastError<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Typeless(typeless_error) => {
                write!(f, "typeless cast error: {}", typeless_error)
            }
            Self::AddressIndexOutOfBounds { index, length } => {
                write!(f, "cast index out of bound: {}>={}", index, length)
            }
            Self::AddressPointMismatch {
                expected,
                received,
                index,
            } => write!(
                f,
                "address mismatch at index {}: {}!={}",
                index,
                hex::encode(expected),
                hex::encode(received),
            ),
        }
    }
}

impl<'a> CastError<'a> {
    fn pure<Ctx: Context<'a>>(self) -> HashResolution<'a, Ctx>
    where
        Ctx::LookupError: From<CastError<'a>>,
    {
        Ctx::pure(Err(self.into()))
    }
}

impl<'a, Ctx: Context<'a>> CastResolver<'a, Ctx> {
    fn rc(points: Vec<Point<'a, Ctx, TypelessMentionable<'a, Ctx>>>) -> Rc<dyn Resolver<'a, Ctx>>
    where
        Ctx::LookupError: From<CastError<'a>>,
    {
        Rc::new(Self { points })
    }

    fn _get_point(
        &self,
        index: usize,
    ) -> Result<&Point<'a, Ctx, TypelessMentionable<'a, Ctx>>, CastError<'a>> {
        match self.points.get(index) {
            Some(point) => Ok(point),
            None => Err(CastError::AddressIndexOutOfBounds {
                index,
                length: self.points.len(),
            }),
        }
    }

    fn _validate_point(
        &self,
        point: &Point<'a, Ctx, TypelessMentionable<'a, Ctx>>,
        address: Address,
    ) -> Result<(), CastError<'a>> {
        if point.point == address.point {
            Ok(())
        } else {
            Err(CastError::AddressPointMismatch {
                expected: point.point,
                received: address.point,
                index: address.index,
            })
        }
    }

    fn get_point(
        &self,
        address: Address,
    ) -> Result<&Point<'a, Ctx, TypelessMentionable<'a, Ctx>>, CastError<'a>> {
        let point = self._get_point(address.index)?;
        self._validate_point(point, address)?;
        Ok(point)
    }
}

fn cast_resolved<'a, Ctx: Context<'a>>(
    resolved: ResolutionResult<'a, Ctx, TypelessMentionable<'a, Ctx>>,
) -> HashResolutionResult<'a, Ctx>
where
    Ctx::LookupError: From<CastError<'a>>,
{
    match resolved {
        Ok(mentionable) => Ok((
            mentionable.bytes(),
            CastResolver::rc(mentionable.points_vec()),
        )),
        Err(error) => Err(match error {
            ResolutionError::Lookup(lookup_error) => lookup_error,
            ResolutionError::Parse(parse_error) => CastError::Typeless(parse_error).into(),
        }),
    }
}

impl<'a, Ctx: Context<'a>> Resolver<'a, Ctx> for CastResolver<'a, Ctx>
where
    Ctx::LookupError: From<CastError<'a>>,
{
    fn resolve(self: Rc<Self>, address: Address) -> HashResolution<'a, Ctx> {
        let point = match self.get_point(address) {
            Ok(point) => point,
            Err(cast_error) => return cast_error.pure::<Ctx>(),
        };
        point.resolve_map(cast_resolved)
    }
}

impl<'a, Ctx: Context<'a>> TypelessMentionable<'a, Ctx>
where
    Ctx::LookupError: From<CastError<'a>>,
{
    pub fn cast_full<A: Mentionable<'a, Ctx>>(
        &self,
        factory: A::Fctr,
        map_resolver: impl FnOnce(Rc<dyn Resolver<'a, Ctx>>) -> Rc<dyn Resolver<'a, Ctx>>,
    ) -> ParseResultA<'a, Ctx, A> {
        factory.parse_slice(
            &self.bytes(),
            &map_resolver(CastResolver::rc(self.points_vec())),
        )
    }

    /// Re-parse the object as if it is of a specific type. Has potential to break topology.
    pub fn cast<A: Mentionable<'a, Ctx>>(&self, factory: A::Fctr) -> ParseResultA<'a, Ctx, A> {
        self.cast_full(factory, identity)
    }
}

fn cast_resolve<'a, Ctx: Context<'a>, A: Mentionable<'a, Ctx>>(
    typeless_origin: Rc<dyn Origin<'a, Ctx, Mtbl = TypelessMentionable<'a, Ctx>>>,
    factory: A::Fctr,
) -> Resolution<'a, Ctx, A>
where
    Ctx::LookupError: From<CastError<'a>>,
{
    Ctx::fmap(
        typeless_origin.clone().resolve(),
        move |resolved| match resolved {
            Ok(typeless_mentionable) => match typeless_mentionable.cast(factory) {
                Ok(mentionable) => Ok(Rc::new(mentionable)),
                Err(parse_error) => Err(ResolutionError::Parse(parse_error)),
            },
            Err(error) => Err(ResolutionError::Lookup(match error {
                ResolutionError::Lookup(lookup_error) => lookup_error,
                ResolutionError::Parse(parse_error) => CastError::Typeless(parse_error).into(),
            })),
        },
    )
}

fn cast_origin<'a, Ctx: Context<'a>, A: Mentionable<'a, Ctx>>(
    typeless_origin: Rc<dyn Origin<'a, Ctx, Mtbl = TypelessMentionable<'a, Ctx>>>,
    factory: A::Fctr,
) -> Rc<dyn Origin<'a, Ctx, Mtbl = A>>
where
    Ctx::LookupError: From<CastError<'a>>,
{
    let origin_rb = typeless_origin.clone();
    wrapped_origin(
        factory.clone(),
        move || cast_resolve(typeless_origin.clone(), factory.clone()),
        move || origin_rb.clone().resolve_bytes(),
    )
}

impl<'a, Ctx: Context<'a>> Point<'a, Ctx, TypelessMentionable<'a, Ctx>>
where
    Ctx::LookupError: From<CastError<'a>>,
{
    fn cast_origin<A: Mentionable<'a, Ctx>>(
        &self,
        factory: A::Fctr,
    ) -> Rc<dyn Origin<'a, Ctx, Mtbl = A>> {
        let typeless_origin = self.origin.clone();
        cast_origin(typeless_origin, factory)
    }

    /// See [`TypelessMentionable::cast`]
    pub fn cast<A: Mentionable<'a, Ctx>>(&self, factory: A::Fctr) -> Point<'a, Ctx, A> {
        Point {
            point: self.point,
            origin: self.cast_origin(factory),
        }
    }
}