use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use crate::{
    expression::Expression,
    input::{InputSet, InputType},
    intrinsic::{Constraint, Intrinsic, Signature},
    matching::SizeMatchable,
    predicate_forms::PredicateForm,
    typekinds::{ToRepr, TypeKind},
    wildcards::Wildcard,
    wildstring::WildString,
};

/// Maximum SVE vector size
const SVE_VECTOR_MAX_SIZE: u32 = 2048;
/// Vector register size
const VECTOR_REG_SIZE: u32 = 128;

/// Generator result
pub type Result<T = ()> = std::result::Result<T, String>;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArchitectureSettings {
    #[serde(alias = "arch")]
    pub arch_name: String,
    pub target_feature: Vec<String>,
    #[serde(alias = "llvm_prefix")]
    pub llvm_link_prefix: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GlobalContext {
    pub arch_cfgs: Vec<ArchitectureSettings>,
    #[serde(default)]
    pub uses_neon_types: bool,

    /// Should the yaml file automagically generate big endian shuffling
    #[serde(default)]
    pub auto_big_endian: Option<bool>,

    /// Should all LLVM wrappers convert their arguments to a signed type
    #[serde(default)]
    pub auto_llvm_sign_conversion: bool,

    /// Should SVE load/store tests be generated?
    #[serde(default)]
    pub generate_load_store_tests: bool,
}

/// Context of an intrinsic group
#[derive(Debug, Clone, Default)]
pub struct GroupContext {
    /// LLVM links to target input sets
    pub links: HashMap<String, InputSet>,
}

#[derive(Debug, Clone, Copy)]
pub enum VariableType {
    Argument,
    Internal,
}

#[derive(Debug, Clone)]
pub struct LocalContext {
    pub signature: Signature,

    pub input: InputSet,

    pub substitutions: HashMap<Wildcard, String>,
    pub variables: HashMap<String, (TypeKind, VariableType)>,
}

impl LocalContext {
    pub fn new(input: InputSet, original: &Intrinsic) -> LocalContext {
        LocalContext {
            signature: original.signature.clone(),
            input,
            substitutions: HashMap::new(),
            variables: HashMap::new(),
        }
    }

    pub fn provide_type_wildcard(&self, wildcard: &Wildcard) -> Result<TypeKind> {
        let err = || {
            format!(
                "provide_type_wildcard() wildcard {{{wildcard}}} not found for {}",
                &self.signature.name.to_string()
            )
        };

        /* If the type is already a vector then we can just return the vector */
        let make_neon = |tuple_size| {
            move |ty| match ty {
                TypeKind::Vector(_) => Ok(ty),
                _ => TypeKind::make_vector(ty, false, tuple_size),
            }
        };
        let make_sve = |tuple_size| move |ty| TypeKind::make_vector(ty, true, tuple_size);

        match wildcard {
            Wildcard::Type(idx) => self.input.typekind(*idx).ok_or_else(err),
            Wildcard::NEONType(idx, tuple_size, _) => self
                .input
                .typekind(*idx)
                .ok_or_else(|| {
                    dbg!("{:?}", &self);
                    err()
                })
                .and_then(make_neon(*tuple_size)),
            Wildcard::SVEType(idx, tuple_size) => self
                .input
                .typekind(*idx)
                .ok_or_else(err)
                .and_then(make_sve(*tuple_size)),
            Wildcard::Predicate(idx) => self.input.typekind(*idx).map_or_else(
                || {
                    if idx.is_none() && self.input.types_len() == 1 {
                        Err(err())
                    } else {
                        Err(format!(
                            "there is no type at index {} to infer the predicate from",
                            idx.unwrap_or(0)
                        ))
                    }
                },
                |ref ty| TypeKind::make_predicate_from(ty),
            ),
            Wildcard::MaxPredicate => self
                .input
                .iter()
                .filter_map(|arg| arg.typekind())
                .max_by(|x, y| {
                    x.base_type()
                        .and_then(|bt| bt.get_size().ok())
                        .unwrap_or(0)
                        .cmp(&y.base_type().and_then(|bt| bt.get_size().ok()).unwrap_or(0))
                })
                .map_or_else(
                    || Err("there are no types available to infer the predicate from".to_string()),
                    TypeKind::make_predicate_from,
                ),
            Wildcard::Scale(w, as_ty) => {
                let mut ty = self.provide_type_wildcard(w)?;
                if let Some(vty) = ty.vector_mut() {
                    let base_ty = if let Some(w) = as_ty.wildcard() {
                        *self.provide_type_wildcard(w)?.base_type().unwrap()
                    } else {
                        *as_ty.base_type().unwrap()
                    };
                    vty.cast_base_type_as(base_ty)
                }
                Ok(ty)
            }
            _ => Err(err()),
        }
    }

    pub fn provide_substitution_wildcard(&self, wildcard: &Wildcard) -> Result<String> {
        let err = || Err(format!("wildcard {{{wildcard}}} not found"));

        match wildcard {
            Wildcard::SizeLiteral(idx) => self.input.typekind(*idx)
                .map_or_else(err, |ty| Ok(ty.size_literal())),
            Wildcard::Size(idx) => self.input.typekind(*idx)
                .map_or_else(err, |ty| Ok(ty.size())),
            Wildcard::SizeMinusOne(idx) => self.input.typekind(*idx)
                .map_or_else(err, |ty| Ok((ty.size().parse::<i32>().unwrap()-1).to_string())),
            Wildcard::SizeInBytesLog2(idx) => self.input.typekind(*idx)
                .map_or_else(err, |ty| Ok(ty.size_in_bytes_log2())),
            Wildcard::NVariant if !self.substitutions.contains_key(wildcard) => Ok(String::new()),
            Wildcard::TypeKind(idx, opts) => {
                self.input.typekind(*idx)
                    .map_or_else(err, |ty| {
                        let literal = if let Some(opts) = opts {
                            #[allow(clippy::obfuscated_if_else)]
                            opts.contains(ty.base_type().map(|bt| *bt.kind()).ok_or_else(|| {
                                format!("cannot retrieve a type literal out of {ty}")
                            })?)
                            .then(|| ty.type_kind())
                            .unwrap_or_default()
                        } else {
                            ty.type_kind()
                        };
                        Ok(literal)
                    })
            }
            Wildcard::PredicateForms(_) => self
                .input
                .iter()
                .find_map(|arg| {
                    if let InputType::PredicateForm(pf) = arg {
                        Some(pf.get_suffix().to_string())
                    } else {
                        None
                    }
                })
                .ok_or_else(|| unreachable!("attempting to render a predicate form wildcard, but no predicate form was compiled for it")),
            _ => self
                .substitutions
                .get(wildcard)
                .map_or_else(err, |s| Ok(s.clone())),
        }
    }

    pub fn make_assertion_from_constraint(&self, constraint: &Constraint) -> Result<Expression> {
        match constraint {
            Constraint::AnyI32 {
                variable,
                any_values,
            } => {
                let where_ex = any_values
                    .iter()
                    .map(|value| format!("{variable} == {value}"))
                    .join(" || ");
                Ok(Expression::MacroCall("static_assert".to_string(), where_ex))
            }
            Constraint::RangeI32 {
                variable,
                range: SizeMatchable::Matched(range),
            } => Ok(Expression::MacroCall(
                "static_assert_range".to_string(),
                format!(
                    "{variable}, {min}..={max}",
                    min = range.start(),
                    max = range.end()
                ),
            )),
            Constraint::SVEMaxElems {
                variable,
                sve_max_elems_type: ty,
            }
            | Constraint::VecMaxElems {
                variable,
                vec_max_elems_type: ty,
            } => {
                if !self.input.is_empty() {
                    let higher_limit = match constraint {
                        Constraint::SVEMaxElems { .. } => SVE_VECTOR_MAX_SIZE,
                        Constraint::VecMaxElems { .. } => VECTOR_REG_SIZE,
                        _ => unreachable!(),
                    };

                    let max = ty.base_type()
                        .map(|ty| ty.get_size())
                        .transpose()?
                        .map_or_else(
                            || Err(format!("can't make an assertion out of constraint {self:?}: no valid type is present")),
                            |bitsize| Ok(higher_limit / bitsize - 1))?;
                    Ok(Expression::MacroCall(
                        "static_assert_range".to_string(),
                        format!("{variable}, 0..={max}"),
                    ))
                } else {
                    Err(format!(
                        "can't make an assertion out of constraint {self:?}: no types are being used"
                    ))
                }
            }
            _ => unreachable!("constraints were not built successfully!"),
        }
    }

    pub fn predicate_form(&self) -> Option<&PredicateForm> {
        self.input.iter().find_map(|arg| arg.predicate_form())
    }

    pub fn n_variant_op(&self) -> Option<&WildString> {
        self.input.iter().find_map(|arg| arg.n_variant_op())
    }
}

pub struct Context<'ctx> {
    pub local: &'ctx mut LocalContext,
    pub group: &'ctx mut GroupContext,
    pub global: &'ctx GlobalContext,
}
