use std::cmp;
use std::fmt;
use std::ops::Deref;
use std::str::FromStr;

use itertools::Itertools as _;

use super::cli::Language;
use super::indentation::Indentation;
use super::values::value_for_array;

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Sign {
    Signed,
    Unsigned,
}

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum TypeKind {
    BFloat,
    Float,
    Int(Sign),
    Char(Sign),
    Poly,
    Void,
    Mask,
    Vector,
}

impl FromStr for TypeKind {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "bfloat" | "BF16" => Ok(Self::BFloat),
            "float" | "double" | "FP16" | "FP32" | "FP64" => Ok(Self::Float),
            "int" | "long" | "short" | "SI8" | "SI16" | "SI32" | "SI64" => {
                Ok(Self::Int(Sign::Signed))
            }
            "poly" => Ok(Self::Poly),
            "char" => Ok(Self::Char(Sign::Signed)),
            "uint" | "unsigned" | "UI8" | "UI16" | "UI32" | "UI64" => Ok(Self::Int(Sign::Unsigned)),
            "void" => Ok(Self::Void),
            "MASK" => Ok(Self::Mask),
            "M128" | "M256" | "M512" => Ok(Self::Vector),
            _ => Err(format!("Impossible to parse argument kind {s}")),
        }
    }
}

impl fmt::Display for TypeKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Self::BFloat => "bfloat",
                Self::Float => "float",
                Self::Int(Sign::Signed) => "int",
                Self::Int(Sign::Unsigned) => "uint",
                Self::Poly => "poly",
                Self::Void => "void",
                Self::Char(Sign::Signed) => "char",
                Self::Char(Sign::Unsigned) => "unsigned char",
                Self::Mask => "mask",
                Self::Vector => "vector",
            }
        )
    }
}

impl TypeKind {
    /// Gets the type part of a c typedef for a type that's in the form of {type}{size}_t.
    pub fn c_prefix(&self) -> &str {
        match self {
            Self::Float => "float",
            Self::Int(Sign::Signed) => "int",
            Self::Int(Sign::Unsigned) => "uint",
            Self::Mask => "uint",
            Self::Poly => "poly",
            Self::Char(Sign::Signed) => "char",
            Self::Vector => "int",
            _ => unreachable!("Not used: {self:#?}"),
        }
    }

    /// Gets the rust prefix for the type kind i.e. i, u, f.
    pub fn rust_prefix(&self) -> &str {
        match self {
            Self::BFloat => "bf",
            Self::Float => "f",
            Self::Int(Sign::Signed) => "i",
            Self::Int(Sign::Unsigned) => "u",
            Self::Poly => "u",
            Self::Char(Sign::Unsigned) => "u",
            Self::Char(Sign::Signed) => "i",
            _ => unreachable!("Unused type kind: {self:#?}"),
        }
    }
}

#[derive(Debug, PartialEq, Clone)]
pub struct IntrinsicType {
    pub constant: bool,

    /// whether this object is a const pointer
    pub ptr_constant: bool,

    pub ptr: bool,

    pub kind: TypeKind,
    /// The bit length of this type (e.g. 32 for u32).
    pub bit_len: Option<u32>,

    /// Length of the SIMD vector (i.e. 4 for uint32x4_t), A value of `None`
    /// means this is not a simd type. A `None` can be assumed to be 1,
    /// although in some places a distinction is needed between `u64` and
    /// `uint64x1_t` this signals that.
    pub simd_len: Option<u32>,

    /// The number of rows for SIMD matrices (i.e. 2 for uint8x8x2_t).
    /// A value of `None` represents a type that does not contain any
    /// rows encoded in the type (e.g. uint8x8_t).
    /// A value of `None` can be assumed to be 1 though.
    pub vec_len: Option<u32>,
}

impl IntrinsicType {
    pub fn kind(&self) -> TypeKind {
        self.kind
    }

    pub fn inner_size(&self) -> u32 {
        if let Some(bl) = self.bit_len {
            cmp::max(bl, 8)
        } else {
            unreachable!("{self:#?}")
        }
    }

    pub fn num_lanes(&self) -> u32 {
        self.simd_len.unwrap_or(1)
    }

    pub fn num_vectors(&self) -> u32 {
        self.vec_len.unwrap_or(1)
    }

    pub fn is_simd(&self) -> bool {
        self.simd_len.is_some() || self.vec_len.is_some()
    }

    pub fn is_ptr(&self) -> bool {
        self.ptr
    }

    pub fn c_scalar_type(&self) -> String {
        match self.kind() {
            TypeKind::Char(_) => String::from("char"),
            TypeKind::Vector => String::from("int32_t"),
            _ => format!(
                "{prefix}{bits}_t",
                prefix = self.kind().c_prefix(),
                bits = self.inner_size()
            ),
        }
    }

    pub fn c_promotion(&self) -> &str {
        match *self {
            IntrinsicType {
                kind,
                bit_len: Some(8),
                ..
            } => match kind {
                TypeKind::Int(Sign::Signed) => "int",
                TypeKind::Int(Sign::Unsigned) => "unsigned int",
                TypeKind::Poly => "uint8_t",
                _ => "",
            },
            IntrinsicType {
                kind: TypeKind::Poly,
                bit_len: Some(bit_len),
                ..
            } => match bit_len {
                8 => unreachable!("handled above"),
                16 => "uint16_t",
                32 => "uint32_t",
                64 => "uint64_t",
                128 => "",
                _ => panic!("invalid bit_len"),
            },
            IntrinsicType {
                kind: TypeKind::Float,
                bit_len: Some(bit_len),
                ..
            } => match bit_len {
                16 => "float16_t",
                32 => "float",
                64 => "double",
                128 => "",
                _ => panic!("invalid bit_len"),
            },
            IntrinsicType {
                kind: TypeKind::Char(_),
                ..
            } => "char",
            _ => "",
        }
    }

    pub fn populate_random(
        &self,
        indentation: Indentation,
        loads: u32,
        language: &Language,
    ) -> String {
        match self {
            IntrinsicType {
                bit_len: Some(bit_len @ (1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 16 | 32 | 64)),
                kind:
                    kind @ (TypeKind::Int(_) | TypeKind::Poly | TypeKind::Char(_) | TypeKind::Mask),
                simd_len,
                vec_len,
                ..
            } => {
                let (prefix, suffix) = match language {
                    Language::Rust => ('[', ']'),
                    Language::C => ('{', '}'),
                };
                let body_indentation = indentation.nested();
                format!(
                    "{prefix}\n{body}\n{indentation}{suffix}",
                    body = (0..(simd_len.unwrap_or(1) * vec_len.unwrap_or(1) + loads - 1))
                        .format_with(",\n", |i, fmt| {
                            let src = value_for_array(*bit_len, i);
                            assert!(src == 0 || src.ilog2() < *bit_len);
                            if *kind == TypeKind::Int(Sign::Signed) && (src >> (*bit_len - 1)) != 0
                            {
                                // `src` is a two's complement representation of a negative value.
                                let mask = !0u64 >> (64 - *bit_len);
                                let ones_compl = src ^ mask;
                                let twos_compl = ones_compl + 1;
                                if (twos_compl == src) && (language == &Language::C) {
                                    // `src` is INT*_MIN. C requires `-0x7fffffff - 1` to avoid
                                    // undefined literal overflow behaviour.
                                    fmt(&format_args!("{body_indentation}-{ones_compl:#x} - 1"))
                                } else {
                                    fmt(&format_args!("{body_indentation}-{twos_compl:#x}"))
                                }
                            } else {
                                fmt(&format_args!("{body_indentation}{src:#x}"))
                            }
                        })
                )
            }
            IntrinsicType {
                kind: TypeKind::Float,
                bit_len: Some(bit_len @ (16 | 32 | 64)),
                simd_len,
                vec_len,
                ..
            } => {
                let (prefix, cast_prefix, cast_suffix, suffix) = match (language, bit_len) {
                    (&Language::Rust, 16) => ('[', "f16::from_bits(", ")", ']'),
                    (&Language::Rust, 32) => ('[', "f32::from_bits(", ")", ']'),
                    (&Language::Rust, 64) => ('[', "f64::from_bits(", ")", ']'),
                    (&Language::C, 16) => ('{', "cast<float16_t, uint16_t>(", ")", '}'),
                    (&Language::C, 32) => ('{', "cast<float, uint32_t>(", ")", '}'),
                    (&Language::C, 64) => ('{', "cast<double, uint64_t>(", ")", '}'),
                    _ => unreachable!(),
                };
                format!(
                    "{prefix}\n{body}\n{indentation}{suffix}",
                    body = (0..(simd_len.unwrap_or(1) * vec_len.unwrap_or(1) + loads - 1))
                        .format_with(",\n", |i, fmt| fmt(&format_args!(
                            "{indentation}{cast_prefix}{src:#x}{cast_suffix}",
                            indentation = indentation.nested(),
                            src = value_for_array(*bit_len, i)
                        )))
                )
            }
            IntrinsicType {
                kind: TypeKind::Vector,
                bit_len: Some(128 | 256 | 512),
                simd_len,
                vec_len,
                ..
            } => {
                let (prefix, suffix) = match language {
                    Language::Rust => ('[', ']'),
                    Language::C => ('{', '}'),
                };
                let body_indentation = indentation.nested();
                let effective_bit_len = 32;
                format!(
                    "{prefix}\n{body}\n{indentation}{suffix}",
                    body = (0..(vec_len.unwrap_or(1) * simd_len.unwrap_or(1) + loads - 1))
                        .format_with(",\n", |i, fmt| {
                            let src = value_for_array(effective_bit_len, i);
                            assert!(src == 0 || src.ilog2() < effective_bit_len);
                            if (src >> (effective_bit_len - 1)) != 0 {
                                // `src` is a two's complement representation of a negative value.
                                let mask = !0u64 >> (64 - effective_bit_len);
                                let ones_compl = src ^ mask;
                                let twos_compl = ones_compl + 1;
                                if (twos_compl == src) && (language == &Language::C) {
                                    // `src` is INT*_MIN. C requires `-0x7fffffff - 1` to avoid
                                    // undefined literal overflow behaviour.
                                    fmt(&format_args!("{body_indentation}-{ones_compl:#x} - 1"))
                                } else {
                                    fmt(&format_args!("{body_indentation}-{twos_compl:#x}"))
                                }
                            } else {
                                fmt(&format_args!("{body_indentation}{src:#x}"))
                            }
                        })
                )
            }
            _ => unimplemented!("populate random: {self:#?}"),
        }
    }

    pub fn is_rust_vals_array_const(&self) -> bool {
        match self {
            // Floats have to be loaded at runtime for stable NaN conversion.
            IntrinsicType {
                kind: TypeKind::Float,
                ..
            } => false,
            IntrinsicType {
                kind: TypeKind::Int(_) | TypeKind::Poly,
                ..
            } => true,
            _ => true,
        }
    }

    pub fn as_call_param_c(&self, name: &String) -> String {
        if self.ptr {
            format!("&{name}")
        } else {
            name.clone()
        }
    }
}

pub trait IntrinsicTypeDefinition: Deref<Target = IntrinsicType> {
    /// Determines the load function for this type.
    /// can be implemented in an `impl` block
    fn get_load_function(&self, _language: Language) -> String;

    /// can be implemented in an `impl` block
    fn get_lane_function(&self) -> String;

    /// Gets a string containing the typename for this type in C format.
    /// can be directly defined in `impl` blocks
    fn c_type(&self) -> String;

    /// can be directly defined in `impl` blocks
    fn c_single_vector_type(&self) -> String;

    /// Generates a std::cout for the intrinsics results that will match the
    /// rust debug output format for the return type. The generated line assumes
    /// there is an int i in scope which is the current pass number.
    fn print_result_c(&self, indentation: Indentation, additional: &str) -> String;

    /// Generates a std::cout for the intrinsics results that will match the
    /// rust debug output format for the return type. The generated line assumes
    /// there is an int i in scope which is the current pass number.
    fn print_result_rust(&self) -> String {
        String::from("format_args!(\"{__return_value:.150?}\")")
    }

    /// To enable architecture-specific logic
    fn rust_scalar_type(&self) -> String {
        format!(
            "{prefix}{bits}",
            prefix = self.kind().rust_prefix(),
            bits = self.inner_size()
        )
    }

    fn generate_final_type_cast(&self) -> String {
        let type_data = self.c_promotion();
        if type_data.len() > 2 {
            format!("({type_data})")
        } else {
            String::new()
        }
    }
}
