use crate::comp_core::{
    constraints::{ParsedConstraints, TransformedConstraints},
    structure::{
        CompleteModelIntoIter, GlobModel, Model, PartialStructure,
        backend::complete_interp::owned::Pred,
    },
    vocabulary::{DomainEnum, PfuncIndex},
};
use sli_collections::rc::Rc;
use std::{fmt::Debug, marker::PhantomData};

pub mod z3;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SatResult {
    Sat,
    Unsat,
    Unknown,
}

impl SatResult {
    pub fn is_sat(&self) -> bool {
        matches!(self, Self::Sat)
    }

    pub fn is_unsat(&self) -> bool {
        matches!(self, Self::Unsat)
    }

    pub fn is_unknown(&self) -> bool {
        matches!(self, Self::Unknown)
    }
}

#[derive(Debug, Clone)]
pub enum ModelResult {
    Sat(GlobModel),
    Unsat,
    Unknown,
}

/// An enum representing how to interpret (simplify) formulas before translating to the
/// solver's format.
///
/// * NoInterp: apart from the solver required transforms (e.g. level mapping) does
/// nothing. That's it. :-)
/// * SatisfyingSetInterp: Ground the formulas using the bit vector approach.
/// * NaiveInterp: Ground the formulas using a "naive" top-down approach.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InterpMethod {
    NoInterp,
    SatisfyingSetInterp,
    NaiveInterp,
}

/// The default value is [InterpMethod::SatisfyingSetInterp].
impl Default for InterpMethod {
    fn default() -> Self {
        InterpMethod::SatisfyingSetInterp
    }
}

#[derive(Debug, Clone, Copy)]
pub enum TimeMeasurements {
    Transform,
    Grounding,
}

pub trait Timings {
    fn start_measurement(&mut self, measure: TimeMeasurements);
    fn end_measurement(&mut self, measure: TimeMeasurements);
}

pub struct MeasurementEnder<'a> {
    measurement: TimeMeasurements,
    timer: &'a mut dyn Timings,
}

impl<'a> MeasurementEnder<'a> {
    /// End timing
    pub fn end(self) {}
}

impl<'a> Drop for MeasurementEnder<'a> {
    fn drop(&mut self) {
        self.timer.end_measurement(self.measurement)
    }
}

impl dyn Timings + '_ {
    /// Returns [MeasurementEnder] to end measurement.
    /// Measurement stops when this struct is dropped or [MeasurementEnder::end] is called.
    pub fn start<'a>(&'a mut self, measurement: TimeMeasurements) -> MeasurementEnder<'a> {
        self.start_measurement(measurement);
        MeasurementEnder {
            measurement,
            timer: self,
        }
    }
}

impl Timings for () {
    fn start_measurement(&mut self, _measure: TimeMeasurements) {}
    fn end_measurement(&mut self, _measure: TimeMeasurements) {}
}

#[derive(Clone, Copy, PartialEq, Eq)]
pub struct InfiniteCodomainErr;

// With interior mutability accesing a solver from multiple threads should be avoided at all cost
pub trait Solver<'a>: Debug {
    fn initialize(constraints: Rc<ParsedConstraints>, structure: &'a PartialStructure) -> Self
    where
        Self: Sized,
    {
        Self::initialize_with(constraints, structure, Default::default())
    }

    fn initialize_with(
        constraints: Rc<ParsedConstraints>,
        structure: &'a PartialStructure,
        interp_method: InterpMethod,
    ) -> Self
    where
        Self: Sized,
    {
        Self::initialize_with_timing(constraints, structure, interp_method, &mut ())
    }
    fn initialize_with_timing(
        constraints: Rc<ParsedConstraints>,
        structure: &'a PartialStructure,
        interp_method: InterpMethod,
        timings: &mut dyn Timings,
    ) -> Self
    where
        Self: Sized;
    fn initial_constraints(&self) -> &ParsedConstraints;
    fn constraints(&self) -> &TransformedConstraints;
    fn check(&mut self) -> SatResult;
    fn check_get_model(&mut self) -> ModelResult {
        match self.check() {
            SatResult::Sat => ModelResult::Sat(self.get_model().expect("Internal Error")),
            SatResult::Unsat => ModelResult::Unsat,
            SatResult::Unknown => ModelResult::Unknown,
        }
    }
    fn get_model(&mut self) -> Option<GlobModel>;
    fn next_model(&mut self);
    fn propagate(&mut self) -> Option<PartialStructure>;
    fn get_range(
        &mut self,
        pfunc: PfuncIndex,
        args: DomainEnum,
    ) -> Result<Option<Pred>, InfiniteCodomainErr>;
}

pub trait SolverIter<'a>: Solver<'a> {
    /// Returns an iterator for iterating over the models generated by the solver.
    /// Use [take](std::iter::Iterator::take) for limiting the amount of models.
    fn iter_models(&mut self) -> ModelIterator<'_, 'a, Self> {
        ModelIterator::new(self)
    }
}

impl<'a, T> SolverIter<'a> for T where T: Solver<'a> {}

// a: lifetime of reference
// t: lifetime of FO(.) Theory
/// Iterates over models generated by a [Solver]
pub struct ModelIterator<'a, 't, T: Solver<'t> + ?Sized>
where
    't: 'a,
{
    solver: &'a mut T,
    phantom_data: PhantomData<&'t ()>,
}

impl<'a, 't, T: Solver<'t> + ?Sized> ModelIterator<'a, 't, T>
where
    't: 'a,
{
    pub fn new(solver: &'a mut T) -> Self {
        Self {
            solver,
            phantom_data: PhantomData,
        }
    }

    pub fn complete_with_infinite<'c>(self) -> CompleteModelIterator<'a, 't, T> {
        CompleteModelIterator::new(self).disable_skip_infinite()
    }

    pub fn complete(self) -> CompleteModelIterator<'a, 't, T> {
        CompleteModelIterator::new(self)
    }
}

impl<'a, 't, T: Solver<'t> + ?Sized> Iterator for ModelIterator<'a, 't, T> {
    type Item = GlobModel;

    fn next(&mut self) -> Option<Self::Item> {
        match self.solver.check_get_model() {
            ModelResult::Sat(model) => {
                self.solver.next_model();
                Some(model)
            }
            _ => None,
        }
    }
}

pub struct CompleteModelIterator<'a, 't, T: Solver<'t> + ?Sized> {
    model_iter: ModelIterator<'a, 't, T>,
    cur_glob_model_iter: Option<CompleteModelIntoIter>,
    skip_infinite: bool,
}

impl<'a, 't, T: Solver<'t> + ?Sized> CompleteModelIterator<'a, 't, T> {
    fn new(model_iter: ModelIterator<'a, 't, T>) -> Self {
        Self {
            model_iter,
            cur_glob_model_iter: None,
            skip_infinite: true,
        }
    }

    pub fn enable_skip_infinite(mut self) -> Self {
        self.skip_infinite = true;
        self
    }

    pub fn disable_skip_infinite(mut self) -> Self {
        self.skip_infinite = false;
        self
    }

    pub fn skip_infinite(self, value: bool) -> Self {
        if value {
            self.enable_skip_infinite()
        } else {
            self.disable_skip_infinite()
        }
    }
}

impl<'a, 't, T: Solver<'t> + ?Sized> Iterator for CompleteModelIterator<'a, 't, T> {
    type Item = Model;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            if let Some(value) = &mut self.cur_glob_model_iter {
                if let Some(value) = value.next() {
                    return Some(value);
                }
            }
            match self.model_iter.next() {
                Some(value) => {
                    let iter = value.into_iter_models();
                    self.cur_glob_model_iter = Some(iter.skip_infinite(self.skip_infinite));
                }
                // Only code path that may return None
                None => return None,
            }
        }
    }
}
