use crate::Fold;
use rustpython_parser_core::{
    source_code::{LinearLocator, RandomLocator, SourceLocation, SourceRange},
    text_size::TextRange,
};
use std::{convert::Infallible, unreachable};

impl crate::fold::Fold<TextRange> for RandomLocator<'_> {
    type TargetU = SourceRange;
    type Error = std::convert::Infallible;
    type UserContext = SourceLocation;

    fn will_map_user(&mut self, user: &TextRange) -> Self::UserContext {
        self.locate(user.start())
    }

    fn map_user(
        &mut self,
        user: TextRange,
        start: Self::UserContext,
    ) -> Result<Self::TargetU, Self::Error> {
        let end = self.locate(user.end());
        Ok((start..end).into())
    }
}

fn linear_locate_expr_joined_str(
    locator: &mut LinearLocator<'_>,
    node: crate::ExprJoinedStr<TextRange>,
    location: SourceRange,
) -> Result<crate::ExprJoinedStr<SourceRange>, Infallible> {
    let crate::ExprJoinedStr { range: _, values } = node;

    let mut located_values = Vec::with_capacity(values.len());
    for value in values.into_iter() {
        let located = match value {
            crate::Expr::Constant(constant) => {
                let node = crate::ExprConstant {
                    range: location,
                    value: constant.value,
                    kind: constant.kind,
                };
                crate::Expr::Constant(node)
            }
            crate::Expr::FormattedValue(formatted) => {
                let node = crate::ExprFormattedValue {
                    range: location,
                    value: locator.fold(formatted.value)?,
                    conversion: formatted.conversion,
                    format_spec: formatted
                        .format_spec
                        .map(|spec| match *spec {
                            crate::Expr::JoinedStr(joined_str) => {
                                let node =
                                    linear_locate_expr_joined_str(locator, joined_str, location)?;
                                Ok(crate::Expr::JoinedStr(node))
                            }
                            expr => locator.fold(expr),
                        })
                        .transpose()?
                        .map(Box::new),
                };
                crate::Expr::FormattedValue(node)
            }
            _ => unreachable!("missing expr type for joined_str?"),
        };
        located_values.push(located);
    }

    Ok(crate::ExprJoinedStr {
        range: location,
        values: located_values,
    })
}

impl crate::fold::Fold<TextRange> for LinearLocator<'_> {
    type TargetU = SourceRange;
    type Error = std::convert::Infallible;
    type UserContext = SourceLocation;

    fn will_map_user(&mut self, user: &TextRange) -> Self::UserContext {
        self.locate(user.start())
    }

    fn map_user(
        &mut self,
        user: TextRange,
        start: Self::UserContext,
    ) -> Result<Self::TargetU, Self::Error> {
        let end = self.locate(user.end());
        Ok((start..end).into())
    }

    fn fold_expr_dict(
        &mut self,
        node: crate::ExprDict<TextRange>,
    ) -> Result<crate::ExprDict<Self::TargetU>, Self::Error> {
        let crate::ExprDict {
            range,
            keys,
            values,
        } = node;
        let context = self.will_map_user(&range);
        assert_eq!(keys.len(), values.len());
        let mut located_keys = Vec::with_capacity(keys.len());
        let mut located_values = Vec::with_capacity(values.len());
        for (key, value) in keys.into_iter().zip(values.into_iter()) {
            located_keys.push(self.fold(key)?);
            located_values.push(self.fold(value)?);
        }
        let range = self.map_user(range, context)?;
        Ok(crate::ExprDict {
            range,
            keys: located_keys,
            values: located_values,
        })
    }

    fn fold_expr_if_exp(
        &mut self,
        node: crate::ExprIfExp<TextRange>,
    ) -> Result<crate::ExprIfExp<Self::TargetU>, Self::Error> {
        let crate::ExprIfExp {
            range,
            test,
            body,
            orelse,
        } = node;
        let context = self.will_map_user(&range);
        let body = self.fold(body)?;
        let test = self.fold(test)?;
        let orelse = self.fold(orelse)?;
        let range = self.map_user(range, context)?;
        Ok(crate::ExprIfExp {
            range,
            test,
            body,
            orelse,
        })
    }

    fn fold_stmt_class_def(
        &mut self,
        node: crate::StmtClassDef<TextRange>,
    ) -> Result<crate::StmtClassDef<Self::TargetU>, Self::Error> {
        let crate::StmtClassDef {
            name,
            bases,
            keywords,
            body,
            decorator_list,
            type_params,
            range,
        } = node;
        let decorator_list = self.fold(decorator_list)?;
        let context = self.will_map_user(&range);

        let name = self.fold(name)?;
        let type_params = self.fold(type_params)?;
        let bases = self.fold(bases)?;
        let keywords = self.fold(keywords)?;
        let body = self.fold(body)?;
        let range = self.map_user(range, context)?;

        Ok(crate::StmtClassDef {
            name,
            bases,
            keywords,
            body,
            decorator_list,
            type_params,
            range,
        })
    }
    fn fold_stmt_function_def(
        &mut self,
        node: crate::StmtFunctionDef<TextRange>,
    ) -> Result<crate::StmtFunctionDef<Self::TargetU>, Self::Error> {
        let crate::StmtFunctionDef {
            name,
            args,
            body,
            decorator_list,
            returns,
            type_comment,
            range,
            type_params,
        } = node;
        let decorator_list = self.fold(decorator_list)?;
        let context = self.will_map_user(&range);

        let name = self.fold(name)?;
        let type_params = self.fold(type_params)?;
        let args: Box<crate::Arguments<SourceRange>> = self.fold(args)?;
        let returns = self.fold(returns)?;
        let body = self.fold(body)?;
        let type_comment = self.fold(type_comment)?;
        let range = self.map_user(range, context)?;
        Ok(crate::StmtFunctionDef {
            name,
            args,
            body,
            decorator_list,
            returns,
            type_params,
            type_comment,
            range,
        })
    }
    fn fold_stmt_async_function_def(
        &mut self,
        node: crate::StmtAsyncFunctionDef<TextRange>,
    ) -> Result<crate::StmtAsyncFunctionDef<Self::TargetU>, Self::Error> {
        let crate::StmtAsyncFunctionDef {
            name,
            args,
            body,
            decorator_list,
            returns,
            type_comment,
            type_params,
            range,
        } = node;
        let decorator_list = self.fold(decorator_list)?;
        let context = self.will_map_user(&range);

        let name = self.fold(name)?;
        let type_params = self.fold(type_params)?;
        let args: Box<crate::Arguments<SourceRange>> = self.fold(args)?;
        let returns = self.fold(returns)?;
        let body = self.fold(body)?;
        let type_comment = self.fold(type_comment)?;
        let range = self.map_user(range, context)?;
        Ok(crate::StmtAsyncFunctionDef {
            name,
            args,
            body,
            decorator_list,
            returns,
            type_comment,
            type_params,
            range,
        })
    }
    fn fold_expr_joined_str(
        &mut self,
        node: crate::ExprJoinedStr<TextRange>,
    ) -> Result<crate::ExprJoinedStr<Self::TargetU>, Self::Error> {
        let start = self.locate(node.range.start());
        let end = self.locate_only(node.range.end());
        let location = SourceRange::new(start, end);
        linear_locate_expr_joined_str(self, node, location)
    }

    fn fold_expr_call(
        &mut self,
        node: crate::ExprCall<TextRange>,
    ) -> Result<crate::ExprCall<Self::TargetU>, Self::Error> {
        let crate::ExprCall {
            range,
            func,
            args,
            keywords,
        } = node;
        let context = self.will_map_user(&range);
        let func = self.fold(func)?;
        let keywords = LinearLookaheadLocator(self).fold(keywords)?;
        let args = self.fold(args)?;
        let range = self.map_user(range, context)?;
        Ok(crate::ExprCall {
            range,
            func,
            args,
            keywords,
        })
    }
}

struct LinearLookaheadLocator<'a, 'b>(&'b mut LinearLocator<'a>);

impl crate::fold::Fold<TextRange> for LinearLookaheadLocator<'_, '_> {
    type TargetU = SourceRange;
    type Error = std::convert::Infallible;
    type UserContext = SourceLocation;

    fn will_map_user(&mut self, user: &TextRange) -> Self::UserContext {
        self.0.locate_only(user.start())
    }

    fn map_user(
        &mut self,
        user: TextRange,
        start: Self::UserContext,
    ) -> Result<Self::TargetU, Self::Error> {
        let end = self.0.locate_only(user.end());
        Ok((start..end).into())
    }
}
