use itertools::Itertools;
use regex::Regex;
use serde::Deserialize;

use crate::argument::{Argument, ArgumentList, Constraint};
use crate::intrinsic::Intrinsic;
use crate::types::{IntrinsicType, TypeKind};

pub fn get_acle_intrinsics(filename: &str) -> Vec<Intrinsic> {
    let data = std::fs::read_to_string(filename).expect("Failed to open ACLE intrinsics file");

    let data = data
        .lines()
        .filter_map(|l| {
            (!(l.starts_with("<COMMENT>") || l.is_empty() || l.starts_with("<SECTION>")))
                .then(|| l.replace("<HEADER>\t", ""))
        })
        .join("\n");

    let mut csv_reader = csv::ReaderBuilder::new()
        .delimiter(b'\t')
        .from_reader(data.as_bytes());

    let mut intrinsics: Vec<Intrinsic> = csv_reader
        .deserialize()
        .filter_map(|x: Result<ACLEIntrinsicLine, _>| x.ok().map(|i| i.into()))
        .collect::<Vec<_>>();

    // Intrinsics such as vshll_n_s8 exist twice in the ACLE with different constraints.
    intrinsics.sort_by(|a, b| a.name.cmp(&b.name));
    let (intrinsics, duplicates) = intrinsics.partition_dedup_by(|a, b| a.name == b.name);
    for duplicate in duplicates {
        let name = &duplicate.name;
        let constraints = duplicate
            .arguments
            .args
            .drain(..)
            .filter(|a| a.has_constraint());
        let intrinsic = intrinsics.iter_mut().find(|i| &i.name == name).unwrap();

        for mut constraint in constraints {
            let real_constraint = intrinsic
                .arguments
                .args
                .iter_mut()
                .find(|a| a.name == constraint.name)
                .unwrap();
            real_constraint
                .constraints
                .push(constraint.constraints.pop().unwrap());
        }
    }

    intrinsics.to_vec()
}

impl Into<Intrinsic> for ACLEIntrinsicLine {
    fn into(self) -> Intrinsic {
        let signature = self.intrinsic;
        let (ret_ty, remaining) = signature.split_once(' ').unwrap();

        let results = type_from_c(ret_ty)
            .unwrap_or_else(|_| panic!("Failed to parse return type: {}", ret_ty));

        let (name, args) = remaining.split_once('(').unwrap();
        let args = args.trim_end_matches(')');

        // Typo in ACLE data
        let args = args.replace("int16x8q_t", "int16x8_t");

        let arg_prep = self.argument_preparation.as_str();
        let args = args
            .split(',')
            .enumerate()
            .map(move |(idx, arg)| {
                let arg = arg.trim();
                if arg.starts_with("__builtin_constant_p") {
                    handle_constraint(idx, arg, arg_prep)
                } else {
                    from_c(idx, arg)
                }
            })
            .collect();
        let arguments = ArgumentList { args };
        let a64_only = match &*self.supported_architectures {
            "A64" => true,
            "v7/A32/A64" | "A32/A64" => false,
            _ => panic!("Invalid supported architectures"),
        };

        Intrinsic {
            name: name.to_string(),
            arguments,
            results,
            a64_only,
        }
    }
}

fn handle_constraint(idx: usize, arg: &str, prep: &str) -> Argument {
    let prep = prep.replace(' ', "");

    let name = arg
        .trim_start_matches("__builtin_constant_p")
        .trim_start_matches(|ref c| c == &' ' || c == &'(')
        .trim_end_matches(')')
        .to_string();

    let ty = IntrinsicType::Type {
        constant: true,
        kind: TypeKind::Int,
        bit_len: Some(32),
        simd_len: None,
        vec_len: None,
    };

    let constraints = prep
        .split(';')
        .find_map(|p| handle_range_constraint(&name, p).or_else(|| handle_eq_constraint(&name, p)))
        .map(|c| vec![c])
        .unwrap_or_default();

    Argument {
        pos: idx,
        name,
        ty,
        constraints,
    }
}

fn handle_range_constraint(name: &str, data: &str) -> Option<Constraint> {
    lazy_static! {
        static ref RANGE_CONSTRAINT: Regex =
            Regex::new(r#"([0-9]+)<=([[:alnum:]]+)<=([0-9]+)"#).unwrap();
    }

    let captures = RANGE_CONSTRAINT.captures(data)?;
    if captures.get(2).map(|c| c.as_str() == name).unwrap_or(false) {
        match (captures.get(1), captures.get(3)) {
            (Some(start), Some(end)) => {
                let start = start.as_str().parse::<i64>().unwrap();
                let end = end.as_str().parse::<i64>().unwrap() + 1;
                Some(Constraint::Range(start..end))
            }
            _ => panic!("Invalid constraint"),
        }
    } else {
        None
    }
}

fn handle_eq_constraint(name: &str, data: &str) -> Option<Constraint> {
    lazy_static! {
        static ref EQ_CONSTRAINT: Regex = Regex::new(r#"([[:alnum:]]+)==([0-9]+)"#).unwrap();
    }
    let captures = EQ_CONSTRAINT.captures(data)?;
    if captures.get(1).map(|c| c.as_str() == name).unwrap_or(false) {
        captures
            .get(2)
            .map(|c| Constraint::Equal(c.as_str().parse::<i64>().unwrap()))
    } else {
        None
    }
}

fn from_c(pos: usize, s: &str) -> Argument {
    let name_index = s
        .chars()
        .rev()
        .take_while(|c| c != &'*' && c != &' ')
        .count();

    let name_start = s.len() - name_index;
    let name = s[name_start..].to_string();
    let s = s[..name_start].trim();

    Argument {
        pos,
        name,
        ty: type_from_c(s).unwrap_or_else(|_| panic!("Failed to parse type: {}", s)),
        constraints: vec![],
    }
}

fn type_from_c(s: &str) -> Result<IntrinsicType, String> {
    const CONST_STR: &str = "const ";

    if let Some(s) = s.strip_suffix('*') {
        let (s, constant) = if s.ends_with(CONST_STR) {
            (&s[..s.len() - (CONST_STR.len() + 1)], true)
        } else {
            (s, false)
        };

        let s = s.trim_end();

        Ok(IntrinsicType::Ptr {
            constant,
            child: Box::new(type_from_c(s)?),
        })
    } else {
        // [const ]TYPE[{bitlen}[x{simdlen}[x{vec_len}]]][_t]

        let (mut s, constant) = if let Some(s) = s.strip_prefix(CONST_STR) {
            (s, true)
        } else {
            (s, false)
        };
        s = s.strip_suffix("_t").unwrap_or(s);

        let mut parts = s.split('x'); // [[{bitlen}], [{simdlen}], [{vec_len}] ]

        let start = parts.next().ok_or("Impossible to parse type")?;

        if let Some(digit_start) = start.find(|c: char| c.is_ascii_digit()) {
            let (arg_kind, bit_len) = start.split_at(digit_start);

            let arg_kind = arg_kind.parse::<TypeKind>()?;
            let bit_len = bit_len.parse::<u32>().map_err(|err| err.to_string())?;

            let simd_len = parts.next().map(|part| part.parse::<u32>().ok()).flatten();
            let vec_len = parts.next().map(|part| part.parse::<u32>().ok()).flatten();

            Ok(IntrinsicType::Type {
                constant,
                kind: arg_kind,
                bit_len: Some(bit_len),
                simd_len,
                vec_len,
            })
        } else {
            Ok(IntrinsicType::Type {
                constant,
                kind: start.parse::<TypeKind>()?,
                bit_len: None,
                simd_len: None,
                vec_len: None,
            })
        }
    }
}

#[derive(Deserialize, Debug, PartialEq, Clone)]
struct ACLEIntrinsicLine {
    #[serde(rename = "Intrinsic")]
    intrinsic: String,
    #[serde(rename = "Argument preparation")]
    argument_preparation: String,
    #[serde(rename = "AArch64 Instruction")]
    aarch64_instruction: String,
    #[serde(rename = "Result")]
    result: String,
    #[serde(rename = "Supported architectures")]
    supported_architectures: String,
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::argument::Argument;
    use crate::types::{IntrinsicType, TypeKind};

    #[test]
    fn parse_simd() {
        let expected = Argument {
            pos: 0,
            name: "a".into(),
            ty: IntrinsicType::Type {
                constant: false,
                kind: TypeKind::Int,
                bit_len: Some(32),
                simd_len: Some(4),
                vec_len: None,
            },
            constraints: vec![],
        };
        let actual = from_c(0, "int32x4_t a");
        assert_eq!(expected, actual);
    }

    #[test]
    fn parse_simd_with_vec() {
        let expected = Argument {
            pos: 0,
            name: "a".into(),
            ty: IntrinsicType::Type {
                constant: false,
                kind: TypeKind::Int,
                bit_len: Some(32),
                simd_len: Some(4),
                vec_len: Some(2),
            },
            constraints: vec![],
        };
        let actual = from_c(0, "int32x4x2_t a");
        assert_eq!(expected, actual);
    }

    #[test]
    fn test_ptr() {
        let expected = Argument {
            pos: 0,
            name: "ptr".into(),
            ty: crate::types::IntrinsicType::Ptr {
                constant: true,
                child: Box::new(IntrinsicType::Type {
                    constant: false,
                    kind: TypeKind::Int,
                    bit_len: Some(8),
                    simd_len: None,
                    vec_len: None,
                }),
            },
            constraints: vec![],
        };
        let actual = from_c(0, "int8_t const *ptr");
        assert_eq!(expected, actual);
    }
}
