use core::fmt::Debug;
use core::marker::PhantomData;

use crate::ctor::Ctor;

use crate::ctor::Handler;
use crate::ctx::Context;
use crate::ctx::Match;
use crate::ctx::new_span_inc;
use crate::err::Error;
use crate::neu::calc_length;
use crate::regex::Regex;
use crate::span::Span;

use super::Condition;
use super::Neu;
use super::NeuCond;

///
/// Construct a regex that matches unit `L` and then unit `R`.
///
/// # Ctor
///
/// Return [`Orig`](crate::ctx::Context::Orig) with the [`Span`] as the index if the match is found.
///
/// # Example
///
/// ```
/// # use neure::prelude::*;
/// #
/// # fn main() -> color_eyre::Result<()> {
/// #     color_eyre::install()?;
///     let re = b'+'
///         .or(b'-')
///         .then(u8::is_ascii_hexdigit)
///         .then(u8::is_ascii_hexdigit.count::<3>())
///         .pat();
///
///     assert_eq!(BytesCtx::new(b"+AE00").ctor(&re)?, b"+AE00");
///     assert!(BytesCtx::new(b"-GH66").ctor(&re).is_err());
///     assert_eq!(BytesCtx::new(b"-83FD").ctor(&re)?, b"-83FD");
///     Ok(())
/// # }
/// ```
pub struct NeureThen<C, L, R, T, I>
where
    L: Neu<T>,
    R: Neu<T>,
{
    left: L,
    right: R,
    cond: I,
    marker: PhantomData<(C, T)>,
}

impl<C, L, R, T, I> core::ops::Not for NeureThen<C, L, R, T, I>
where
    L: Neu<T>,
    R: Neu<T>,
{
    type Output = crate::regex::Assert<Self>;

    fn not(self) -> Self::Output {
        crate::regex::not(self)
    }
}

impl<C, L, R, T, I> Debug for NeureThen<C, L, R, T, I>
where
    I: Debug,
    L: Neu<T> + Debug,
    R: Neu<T> + Debug,
{
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("NeureThen")
            .field("left", &self.left)
            .field("right", &self.right)
            .field("cond", &self.cond)
            .finish()
    }
}

impl<C, L, R, T, I> Default for NeureThen<C, L, R, T, I>
where
    I: Default,
    L: Neu<T> + Default,
    R: Neu<T> + Default,
{
    fn default() -> Self {
        Self {
            left: Default::default(),
            right: Default::default(),
            cond: Default::default(),
            marker: Default::default(),
        }
    }
}

impl<C, L, R, T, I> Clone for NeureThen<C, L, R, T, I>
where
    I: Clone,
    L: Neu<T> + Clone,
    R: Neu<T> + Clone,
{
    fn clone(&self) -> Self {
        Self {
            left: self.left.clone(),
            cond: self.cond.clone(),
            right: self.right.clone(),
            marker: self.marker,
        }
    }
}

impl<C, L, R, T, I> Copy for NeureThen<C, L, R, T, I>
where
    I: Copy,
    L: Neu<T> + Copy,
    R: Neu<T> + Copy,
{
}

impl<C, L, R, T, I> NeureThen<C, L, R, T, I>
where
    L: Neu<T>,
    R: Neu<T>,
{
    pub const fn new(left: L, right: R, cond: I) -> Self {
        Self {
            left,
            cond,
            right,
            marker: PhantomData,
        }
    }

    pub const fn left(&self) -> &L {
        &self.left
    }

    pub const fn left_mut(&mut self) -> &mut L {
        &mut self.left
    }

    pub fn set_left(&mut self, unit: L) -> &mut Self {
        self.left = unit;
        self
    }

    pub const fn right(&self) -> &R {
        &self.right
    }

    pub const fn right_mut(&mut self) -> &mut R {
        &mut self.right
    }

    pub fn set_right(&mut self, unit: R) -> &mut Self {
        self.right = unit;
        self
    }
}

impl<'a, C, L, R, I> Condition<'a, C> for NeureThen<C, L, R, C::Item, I>
where
    L: Neu<C::Item>,
    R: Neu<C::Item>,
    C: Context<'a> + 'a,
{
    type Out<F> = NeureThen<C, L, R, C::Item, F>;

    fn set_cond<F>(self, cond: F) -> Self::Out<F>
    where
        F: NeuCond<'a, C>,
    {
        NeureThen::new(self.left, self.right, cond)
    }
}

impl<'a, L, R, C, O, I, H> Ctor<'a, C, O, H> for NeureThen<C, L, R, C::Item, I>
where
    L: Neu<C::Item>,
    R: Neu<C::Item>,
    I: NeuCond<'a, C>,
    C: Match<'a> + 'a,
    H: Handler<C, Out = O>,
{
    #[inline(always)]
    fn construct(&self, ctx: &mut C, func: &mut H) -> Result<O, Error> {
        let ret = ctx.try_mat(self);

        func.invoke(ctx, &ret?).map_err(Into::into)
    }
}

impl<'a, L, R, C, I> Regex<C> for NeureThen<C, L, R, C::Item, I>
where
    C: Context<'a> + 'a,
    L: Neu<C::Item>,
    R: Neu<C::Item>,
    I: NeuCond<'a, C>,
{
    #[inline(always)]
    fn try_parse(&self, ctx: &mut C) -> Result<Span, crate::err::Error> {
        let mut ret = Err(Error::NeureThen);
        let mut iter = ctx.peek()?;
        let remaining_len = ctx.len() - ctx.offset();
        let min_length = self.left.min_length() + self.right.min_length();

        crate::debug_regex_beg!("NeureThen", min_length, ctx.offset());
        if remaining_len >= min_length
            && let (Some(fst), Some(snd)) = (iter.next(), iter.next())
            && self.left.is_match(&fst.1)
            && self.cond.check(ctx, &(fst.0, fst.1))?
            && self.right.is_match(&snd.1)
            && self.cond.check(ctx, &(snd.0, snd.1))?
        {
            let len = calc_length(Some(fst.0), iter.next().map(|v| v.0), remaining_len);

            ret = Ok(new_span_inc(ctx, len));
        }
        crate::debug_regex_reval!("NeureThen", ret)
    }
}
