use crate::{
    ir::{
        function::IsIRStatement,
        quantity::{self, local, Quantity, RegisterName},
    },
    utility::{
        data_type,
        data_type::Type,
        parsing::{self, in_multispace},
    },
};
use nom::{
    bytes::complete::tag,
    character::complete::space0,
    combinator::map,
    multi::separated_list1,
    sequence::{delimited, tuple},
    IResult,
};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Eq, PartialEq, Clone, Hash, Deserialize, Serialize)]
pub struct PhiSource {
    pub value: Quantity,
    pub block: String,
}
impl PartialOrd for PhiSource {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.block.partial_cmp(&other.block)
    }
}
impl Ord for PhiSource {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.block.cmp(&other.block)
    }
}
fn parse_phi_source(code: &str) -> IResult<&str, PhiSource> {
    map(
        delimited(
            tag("["),
            tuple((quantity::parse, space0, tag(","), space0, parsing::ident)),
            tag("]"),
        ),
        |(name, _, _, _, block)| PhiSource { value: name, block },
    )(code)
}
#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
pub struct Phi {
    pub to: RegisterName,
    pub data_type: Type,
    pub from: Vec<PhiSource>,
}
impl IsIRStatement for Phi {
    fn on_register_change(&mut self, from: &RegisterName, to: Quantity) {
        if &self.to == from {
            self.to = to.clone().unwrap_local();
        }
        for source in &mut self.from {
            if let Quantity::RegisterName(local) = &mut source.value {
                if local == from {
                    *local = to.clone().unwrap_local();
                }
            }
        }
    }
    fn generate_register(&self) -> Option<(RegisterName, Type)> {
        Some((self.to.clone(), self.data_type.clone()))
    }
    fn use_register(&self) -> Vec<RegisterName> {
        self.from
            .iter()
            .filter_map(|PhiSource { value: name, .. }| name.as_local())
            .cloned()
            .collect()
    }
}
impl fmt::Display for Phi {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} = phi {} ", self.to, self.data_type)?;
        for (i, source) in self.from.iter().enumerate() {
            if i != 0 {
                write!(f, ", ")?;
            }
            write!(f, "[{}, {}]", source.block, source.value)?;
        }
        Ok(())
    }
}
pub fn parse(code: &str) -> IResult<&str, Phi> {
    map(
        tuple((
            local::parse,
            space0,
            tag("="),
            space0,
            tag("phi"),
            space0,
            data_type::parse,
            space0,
            separated_list1(in_multispace(tag(",")), in_multispace(parse_phi_source)),
        )),
        |(to, _, _, _, _, _, data_type, _, from)| Phi {
            to,
            data_type,
            from,
        },
    )(code)
}
#[cfg(test)]
pub mod test_util {
    #![allow(clippy::borrow_interior_mutable_const)]
    use super::*;
    pub fn new(
        target: &str,
        source1_bb: &str,
        source1: &str,
        source2_bb: &str,
        source2: &str,
    ) -> Phi {
        Phi {
            to: RegisterName(target.to_string()),
            data_type: data_type::I32.clone(),
            from: vec![
                PhiSource {
                    value: RegisterName(source1.to_string()).into(),
                    block: source1_bb.to_string(),
                },
                PhiSource {
                    value: RegisterName(source2.to_string()).into(),
                    block: source2_bb.to_string(),
                },
            ],
        }
    }
}
#[cfg(test)]
mod tests {
    #![allow(clippy::borrow_interior_mutable_const)]
    use super::*;
    #[test]
    fn test_parse() {
        let result = parse("%1 = phi i32 [%2, bb1], [%4, bb2]").unwrap().1;
        assert_eq!(
            result,
            Phi {
                to: RegisterName("1".to_string()),
                data_type: data_type::I32.clone(),
                from: vec![
                    PhiSource {
                        value: RegisterName("2".to_string()).into(),
                        block: "bb1".to_string(),
                    },
                    PhiSource {
                        value: RegisterName("4".to_string()).into(),
                        block: "bb2".to_string(),
                    },
                ],
            }
        );
    }
}