use super::{
    AbstractDomain, AbstractIdentifier, HasTop, Interval, PointerDomain, RegisterDomain,
    SizedDomain, SpecializeByConditional, TryToBitvec, TryToInterval,
};
use crate::intermediate_representation::*;
use crate::prelude::*;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Display;

/// An abstract domain representing either a pointer or a non-pointer value.
/// Both non-pointer values and offsets of pointers are represented by the same abstract domain `T`.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum DataDomain<T: RegisterDomain> {
    /// The `Top` element of the domain.
    /// Describes a value for which nothing is known.
    Top(ByteSize),
    /// The value is a pointer to an abstract memory object.
    Pointer(PointerDomain<T>),
    /// The value is a non-pointer value or a pointer to global memory.
    /// The latter can happen if pointers to global memory are described by their absolute value.
    Value(T),
}

impl<T: RegisterDomain> DataDomain<T> {
    /// For pointer values replace an abstract identifier with another one and add the offset_adjustment to the pointer offset.
    /// This is needed to adjust stack pointer on call and return instructions.
    pub fn replace_abstract_id(
        &mut self,
        old_id: &AbstractIdentifier,
        new_id: &AbstractIdentifier,
        offset_adjustment: &T,
    ) {
        if let Self::Pointer(pointer) = self {
            pointer.replace_abstract_id(old_id, new_id, offset_adjustment);
        }
    }

    /// Return a set of all referenced abstract IDs. The set is empty if `self` is not a pointer.
    pub fn referenced_ids(&self) -> BTreeSet<AbstractIdentifier> {
        if let Self::Pointer(pointer) = self {
            pointer.ids().cloned().collect()
        } else {
            BTreeSet::new()
        }
    }

    /// If *self* is a pointer, remove all provided IDs from the target list of it.
    /// If this would leave the pointer without any targets, replace it with *Top*.
    pub fn remove_ids(&mut self, ids_to_remove: &BTreeSet<AbstractIdentifier>) {
        if let Self::Pointer(pointer) = self {
            let remaining_targets: BTreeMap<AbstractIdentifier, T> = pointer
                .targets()
                .iter()
                .filter_map(|(id, offset)| {
                    if ids_to_remove.get(id).is_none() {
                        Some((id.clone(), offset.clone()))
                    } else {
                        None
                    }
                })
                .collect();
            if remaining_targets.is_empty() {
                *self = Self::new_top(self.bytesize());
            } else {
                *self = Self::Pointer(PointerDomain::with_targets(remaining_targets));
            }
        }
    }
}

impl<T: SpecializeByConditional + RegisterDomain> SpecializeByConditional for DataDomain<T> {
    fn add_signed_less_equal_bound(self, bound: &Bitvector) -> Result<Self, Error> {
        if let Self::Value(value) = self {
            Ok(Self::Value(value.add_signed_less_equal_bound(bound)?))
        } else {
            Ok(self)
        }
    }

    fn add_unsigned_less_equal_bound(self, bound: &Bitvector) -> Result<Self, Error> {
        if let Self::Value(value) = self {
            Ok(Self::Value(value.add_unsigned_less_equal_bound(bound)?))
        } else {
            Ok(self)
        }
    }

    fn add_signed_greater_equal_bound(self, bound: &Bitvector) -> Result<Self, Error> {
        if let Self::Value(value) = self {
            Ok(Self::Value(value.add_signed_greater_equal_bound(bound)?))
        } else {
            Ok(self)
        }
    }

    fn add_unsigned_greater_equal_bound(self, bound: &Bitvector) -> Result<Self, Error> {
        if let Self::Value(value) = self {
            Ok(Self::Value(value.add_unsigned_greater_equal_bound(bound)?))
        } else {
            Ok(self)
        }
    }

    fn add_not_equal_bound(self, bound: &Bitvector) -> Result<Self, Error> {
        if let Self::Value(value) = self {
            Ok(Self::Value(value.add_not_equal_bound(bound)?))
        } else {
            Ok(self)
        }
    }
}

impl<T: RegisterDomain> SizedDomain for DataDomain<T> {
    // Return the bitsize of `self`.
    fn bytesize(&self) -> ByteSize {
        use DataDomain::*;
        match self {
            Top(size) => *size,
            Pointer(pointer) => pointer.bytesize(),
            Value(bitvec) => bitvec.bytesize(),
        }
    }

    // Return a new *Top* element with the given bytesize
    fn new_top(bytesize: ByteSize) -> Self {
        Self::Top(bytesize)
    }
}

impl<T: RegisterDomain> HasTop for DataDomain<T> {
    // Generate a new *Top* element with the same bitsize as `self`.
    fn top(&self) -> Self {
        DataDomain::new_top(self.bytesize())
    }
}

impl<T: RegisterDomain> RegisterDomain for DataDomain<T> {
    /// Compute the (abstract) result of a binary operation
    fn bin_op(&self, op: BinOpType, rhs: &Self) -> Self {
        use BinOpType::*;
        use DataDomain::*;
        match (self, op, rhs) {
            (Value(left), _, Value(right)) => Value(left.bin_op(op, right)),
            (Pointer(pointer), IntAdd, Value(value)) | (Value(value), IntAdd, Pointer(pointer)) => {
                Pointer(pointer.add_to_offset(value))
            }
            (Pointer(pointer), IntSub, Value(value)) => Pointer(pointer.sub_from_offset(value)),
            (Pointer(pointer_lhs), IntSub, Pointer(pointer_rhs)) => {
                if pointer_lhs.ids().len() == 1 && pointer_rhs.ids().len() == 1 {
                    let (id_lhs, offset_lhs) = pointer_lhs.targets().iter().next().unwrap();
                    let (id_rhs, offset_rhs) = pointer_rhs.targets().iter().next().unwrap();
                    if id_lhs == id_rhs {
                        Self::Value(offset_lhs.bin_op(IntSub, offset_rhs))
                    } else {
                        Self::Top(self.bytesize())
                    }
                } else {
                    // We cannot be sure that both pointers point to the same target
                    Self::Top(self.bytesize())
                }
            }
            (_, IntEqual, _)
            | (_, IntNotEqual, _)
            | (_, IntLess, _)
            | (_, IntLessEqual, _)
            | (_, IntSLess, _)
            | (_, IntSLessEqual, _)
            | (_, IntCarry, _)
            | (_, IntSCarry, _)
            | (_, IntSBorrow, _)
            | (_, BoolXOr, _)
            | (_, BoolOr, _)
            | (_, BoolAnd, _)
            | (_, FloatEqual, _)
            | (_, FloatNotEqual, _)
            | (_, FloatLess, _)
            | (_, FloatLessEqual, _) => T::new_top(ByteSize::new(1)).into(),
            (_, IntAdd, _)
            | (_, IntSub, _)
            | (_, IntMult, _)
            | (_, IntDiv, _)
            | (_, IntSDiv, _)
            | (_, IntRem, _)
            | (_, IntSRem, _)
            | (_, IntLeft, _)
            | (_, IntRight, _)
            | (_, IntSRight, _)
            | (_, IntAnd, _)
            | (_, IntOr, _)
            | (_, IntXOr, _)
            | (_, FloatAdd, _)
            | (_, FloatSub, _)
            | (_, FloatMult, _)
            | (_, FloatDiv, _) => Self::new_top(self.bytesize()),
            (_, Piece, _) => Self::new_top(self.bytesize() + rhs.bytesize()),
        }
    }

    /// Compute the (abstract) result of a unary operation
    fn un_op(&self, op: UnOpType) -> Self {
        if let Self::Value(value) = self {
            Self::Value(value.un_op(op))
        } else {
            match op {
                UnOpType::BoolNegate | UnOpType::FloatNaN => Self::new_top(ByteSize::new(1)),
                _ => Self::new_top(self.bytesize()),
            }
        }
    }

    /// extract a sub-bitvector
    fn subpiece(&self, low_byte: ByteSize, size: ByteSize) -> Self {
        if let Self::Value(value) = self {
            Self::Value(value.subpiece(low_byte, size))
        } else if low_byte == ByteSize::new(0) && size == self.bytesize() {
            // The operation is a no-op
            self.clone()
        } else {
            Self::new_top(size)
        }
    }

    /// Cast a bitvector using the given cast type
    fn cast(&self, kind: CastOpType, width: ByteSize) -> Self {
        if let Self::Value(value) = self {
            Self::Value(value.cast(kind, width))
        } else {
            // The result of casting pointers is undefined.
            Self::new_top(width)
        }
    }
}

impl<T: RegisterDomain> AbstractDomain for DataDomain<T> {
    // Merge `self` with `other`.
    fn merge(&self, other: &Self) -> Self {
        use DataDomain::*;
        match (self, other) {
            (Top(bytesize), _) | (_, Top(bytesize)) => Top(*bytesize),
            (Pointer(pointer1), Pointer(pointer2)) => Pointer(pointer1.merge(pointer2)),
            (Value(val1), Value(val2)) => Value(val1.merge(val2)),
            (Pointer(_), Value(_)) | (Value(_), Pointer(_)) => Top(self.bytesize()),
        }
    }

    /// Return whether the element represents a top element or not.
    fn is_top(&self) -> bool {
        matches!(self, Self::Top(_))
    }
}

impl<T: RegisterDomain> From<PointerDomain<T>> for DataDomain<T> {
    fn from(val: PointerDomain<T>) -> Self {
        Self::Pointer(val)
    }
}

impl<T: RegisterDomain> From<T> for DataDomain<T> {
    fn from(value: T) -> Self {
        Self::Value(value)
    }
}

impl<T: RegisterDomain + From<Bitvector>> From<Bitvector> for DataDomain<T> {
    fn from(bitvector: Bitvector) -> Self {
        Self::Value(bitvector.into())
    }
}

impl<T: RegisterDomain + TryToBitvec> TryToBitvec for DataDomain<T> {
    /// If the domain represents a single, absolute value, return it.
    fn try_to_bitvec(&self) -> Result<Bitvector, Error> {
        match self {
            DataDomain::Value(value) => value.try_to_bitvec(),
            DataDomain::Pointer(_) => Err(anyhow!("Value is a pointer.")),
            DataDomain::Top(_) => Err(anyhow!("Value is Top")),
        }
    }
}

impl<T: RegisterDomain + TryToInterval> TryToInterval for DataDomain<T> {
    /// If the domain represents (or can be widened to) an interval of absolute values, return the interval.
    fn try_to_interval(&self) -> Result<Interval, Error> {
        match self {
            DataDomain::Value(value) => value.try_to_interval(),
            DataDomain::Pointer(_) => Err(anyhow!("Value is a pointer.")),
            DataDomain::Top(_) => Err(anyhow!("Value is Top")),
        }
    }
}

impl<T: RegisterDomain> std::ops::Add for DataDomain<T> {
    type Output = DataDomain<T>;

    fn add(self, rhs: Self) -> Self {
        self.bin_op(BinOpType::IntAdd, &rhs)
    }
}

impl<T: RegisterDomain> std::ops::Sub for DataDomain<T> {
    type Output = DataDomain<T>;

    fn sub(self, rhs: Self) -> Self {
        self.bin_op(BinOpType::IntSub, &rhs)
    }
}

impl<T: RegisterDomain + Display> DataDomain<T> {
    /// Get a more compact json-representation of the data domain.
    /// Intended for pretty printing, not useable for serialization/deserialization.
    pub fn to_json_compact(&self) -> serde_json::Value {
        match self {
            Self::Top(bitsize) => serde_json::Value::String(format!("Top:{}", bitsize)),
            Self::Pointer(pointer) => {
                let target_iter = pointer.targets().iter().map(|(id, offset)| {
                    (
                        format!("{}", id),
                        serde_json::Value::String(format!("{}", offset)),
                    )
                });
                let targets = serde_json::Value::Object(target_iter.collect());
                let mut obj_map = serde_json::Map::new();
                obj_map.insert("Pointer".to_string(), targets);
                serde_json::Value::Object(obj_map)
            }
            Self::Value(bitvector) => serde_json::Value::String(format!("Value: {}", bitvector)),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::super::*;
    use super::*;

    type Data = DataDomain<BitvectorDomain>;

    fn bv(value: i64) -> BitvectorDomain {
        BitvectorDomain::Value(Bitvector::from_i64(value))
    }

    fn new_id(name: &str) -> AbstractIdentifier {
        AbstractIdentifier::new(
            Tid::new("time0"),
            AbstractLocation::Register(name.into(), ByteSize::new(8)),
        )
    }

    fn new_pointer_domain(location: &str, offset: i64) -> PointerDomain<BitvectorDomain> {
        let id = new_id(location);
        PointerDomain::new(id, bv(offset))
    }

    fn new_pointer(location: &str, offset: i64) -> Data {
        Data::Pointer(new_pointer_domain(location, offset))
    }

    fn new_value(value: i64) -> Data {
        Data::Value(bv(value))
    }

    #[test]
    fn data_abstract_domain() {
        let pointer = new_pointer("Rax".into(), 0);
        let data = new_value(42);
        assert_eq!(pointer.merge(&pointer), pointer);
        assert_eq!(pointer.merge(&data), Data::new_top(ByteSize::new(8)));
        assert_eq!(
            data.merge(&new_value(41)),
            Data::Value(BitvectorDomain::new_top(ByteSize::new(8)))
        );

        let other_pointer = new_pointer("Rbx".into(), 0);
        match pointer.merge(&other_pointer) {
            Data::Pointer(_) => (),
            _ => panic!(),
        }
    }

    #[test]
    fn data_register_domain() {
        use BinOpType::*;
        let data = new_value(42);
        assert_eq!(data.bytesize(), ByteSize::new(8));

        let three = new_value(3);
        let pointer = new_pointer("Rax".into(), 0);
        assert_eq!(data.bin_op(IntAdd, &three), new_value(45));
        assert_eq!(pointer.bin_op(IntAdd, &three), new_pointer("Rax".into(), 3));
        assert_eq!(three.un_op(UnOpType::Int2Comp), new_value(-3));

        assert_eq!(
            three.subpiece(ByteSize::new(0), ByteSize::new(4)),
            Data::Value(BitvectorDomain::Value(Bitvector::from_i32(3)))
        );

        assert_eq!(
            data.cast(CastOpType::IntSExt, ByteSize::new(16)).bytesize(),
            ByteSize::new(16)
        );

        let one = Data::Value(BitvectorDomain::Value(Bitvector::from_i32(1)));
        let two = Data::Value(BitvectorDomain::Value(Bitvector::from_i32(2)));
        let concat = new_value((1 << 32) + 2);
        assert_eq!(one.bin_op(Piece, &two), concat);
    }

    #[test]
    fn remove_ids() {
        let mut targets = BTreeMap::new();
        targets.insert(new_id("Rax"), bv(1));
        targets.insert(new_id("Rbx"), bv(2));
        let mut data: Data = PointerDomain::with_targets(targets).into();

        let mut ids_to_remove = BTreeSet::new();
        ids_to_remove.insert(new_id("Rbx"));
        ids_to_remove.insert(new_id("Rcx"));

        data.remove_ids(&ids_to_remove);
        assert_eq!(
            data.referenced_ids(),
            vec![new_id("Rax")].into_iter().collect()
        );

        data = bv(42).into();
        data.remove_ids(&ids_to_remove);
        assert_eq!(data, bv(42).into());
    }

    #[test]
    fn float_nan_bytesize() {
        let top_value: DataDomain<BitvectorDomain> = DataDomain::new_top(ByteSize::new(8));
        let result = top_value.un_op(UnOpType::FloatNaN);
        assert!(result.is_top());
        assert_eq!(result.bytesize(), ByteSize::new(1));
    }
}
