use std::time::{Duration, Instant};

use dimacs::Clause;
use log::info;

use crate::MusEngine;

pub struct IfElseEngine<
    IfType: MusEngine,
    ElseType: MusEngine,
    Pred: Fn(&[Clause], &[Clause]) -> bool,
> {
    pred: Pred,
    if_engine: IfType,
    else_engine: ElseType,
    call_duration: Option<Duration>,
}

impl<
        IfType: MusEngine + Default,
        ElseType: MusEngine + Default,
        Pred: Fn(&[Clause], &[Clause]) -> bool,
    > From<Pred> for IfElseEngine<IfType, ElseType, Pred>
{
    fn from(pred: Pred) -> Self {
        IfElseEngine {
            pred,
            if_engine: IfType::default(),
            else_engine: ElseType::default(),
            call_duration: None,
        }
    }
}

impl<IfType: MusEngine, ElseType: MusEngine, Pred: Fn(&[Clause], &[Clause]) -> bool> MusEngine
    for IfElseEngine<IfType, ElseType, Pred>
{
    fn optimize_with_deadline(
        &mut self,
        formula: &[Clause],
        mandatory: &[Clause],
        deadline: Option<Instant>,
    ) -> super::MusStatus {
        let full_deadline = deadline.or(self.call_duration.map(|d| Instant::now() + d));
        if (self.pred)(formula, mandatory) {
            self.if_engine
                .optimize_with_deadline(formula, mandatory, full_deadline)
        } else {
            self.else_engine
                .optimize_with_deadline(formula, mandatory, full_deadline)
        }
    }

    fn report(&self) {
        info!("If-part report:");
        self.if_engine.report();
        info!("Else-part report:");
        self.else_engine.report();
    }
}

impl<IfType: MusEngine, ElseType: MusEngine, Pred: Fn(&[Clause], &[Clause]) -> bool>
    IfElseEngine<IfType, ElseType, Pred>
{
    pub fn if_engine_mut(&mut self) -> &mut IfType {
        &mut self.if_engine
    }

    pub fn else_engine_mut(&mut self) -> &mut ElseType {
        &mut self.else_engine
    }

    pub fn with_duration(&mut self, bounding_time_limit: std::time::Duration) {
        self.call_duration = Some(bounding_time_limit);
    }
}
