#include "mc.hh"

ModelChecker::ModelChecker(SymRS *srs, Options *opts)
  : using_ctx_aut(false)
{
  this->srs = srs;
  this->opts = opts;

  cuddMgr = srs->getCuddMgr();

  initStates = srs->getEncInitStates();

  totalStateVars = srs->getTotalStateVars();

  pv = srs->getEncPV();
  pv_succ = srs->getEncPVsucc();
  pv_E = srs->getEncPV_E();
  pv_succ_E = srs->getEncPVsucc_E();
  pv_ctx_E = srs->getEncPVctx_E();
  pv_proc_enab_E = srs->getEncPVproc_enab_E();
  pv_drs_E = srs->getEncPVdrs_E();

  assert(pv != nullptr);
  assert(pv_succ != nullptr);
  assert(pv_E != nullptr);
  assert(pv_succ_E != nullptr);
  assert(pv_ctx_E != nullptr);
  assert(pv_proc_enab_E != nullptr);
  assert(pv_drs_E != nullptr);

  //
  // Transition relations
  //
  //  trp -- partitioned TR
  //  trm -- monolithic TR
  //
  // If we use trp, then trm is nullptr (same for trm)
  //
  trp = srs->getEncPartTrans();

  if (trp == nullptr) {
    trp_size = 0;
  }
  else {
    trp_size = trp->size();
  }

  trm = srs->getEncMonoTrans();

  if (srs->usingContextAutomaton()) {
    using_ctx_aut = true;
    pv_ca = srs->getEncCtxAutPV();
    pv_ca_succ = srs->getEncCtxAutPVsucc();
    pv_ca_E = srs->getEncCtxAutPV_E();
    pv_ca_succ_E = srs->getEncCtxAutPVsucc_E();
  }
  else {
    using_ctx_aut = false;
    pv_ca = nullptr;
    pv_ca_succ = nullptr;
    pv_ca_E = nullptr;
    pv_ca_succ_E = nullptr;
  }

  // Initialise the set of reachable states
  reach = nullptr;
}

inline BDD ModelChecker::getSucc(const BDD &states)
{
  BDD q = BDD_TRUE;

  VERB_L2("Computing successors");

  if (opts->part_tr_rel) {
    for (const auto &trans : *trp) {
      q *= states * trans;
      reorder();
    }
  }
  else {
    q *= states * *trm;
  }

  q = (q.ExistAbstract(*pv_E)).SwapVariables(*pv_succ, *pv);

  // we should have one BDD for cleaning up
  // it should be calculated at the very beginning

  q = q.ExistAbstract(*pv_ctx_E);
  q = q.ExistAbstract(*pv_proc_enab_E);

  return q;
}

inline BDD ModelChecker::getPreE(const BDD &states)
{
  BDD q = BDD_TRUE;
  VERB_L2("Computing preE");
  BDD x = states.SwapVariables(*pv, *pv_succ);

  if (opts->part_tr_rel) {
    for (const auto &trans : *trp) {
      q *= x * trans;
      reorder();
    }
  }
  else {
    q *= x * *trm;
  }

  q = q.ExistAbstract(*pv_succ_E);
  q = q.ExistAbstract(*pv_ctx_E);
  q = q.ExistAbstract(*pv_proc_enab_E);

  return q;
}

inline BDD ModelChecker::getPreEctx(const BDD &states, const BDD *contexts)
{
  BDD q = BDD_TRUE;
  VERB_L2("Computing (context-restricted) preE");
  BDD x = states.SwapVariables(*pv, *pv_succ);

  if (opts->part_tr_rel) {
    for (const auto &trans : *trp) {
      q *= x * trans;
      reorder();
    }
    q *= *contexts;
  }
  else {
    q *= x * *trm * *contexts;
  }

  q = q.ExistAbstract(*pv_succ_E);
  q = q.ExistAbstract(*pv_ctx_E);
  q = q.ExistAbstract(*pv_proc_enab_E);
  return q;
}

void ModelChecker::dropCtxAutStatePart(BDD &states)
{
  states = states.ExistAbstract(*pv_ca_E);
}

void ModelChecker::printReach(void)
{
  VERB_LN(2, "Printing/generating reachable states");

  if (opts->measure) {
    opts->ver_time = cpuTime();
  }

  assert(initStates != nullptr);

  BDD *reach = new BDD(*initStates);
  BDD reach_p = BDD_FALSE;

  unsigned int k = 0;

  while (*reach != reach_p) {
    if (opts->show_progress) {
      cout << "\rIteration " << ++k << flush;
    }

    reach_p = *reach;
    *reach += getSucc(*reach);
  }

  dropCtxAutStatePart(*reach);
  srs->printDecodedRctSysStates(*reach);
  cleanup();

  if (opts->measure) {
    opts->ver_time = cpuTime() - opts->ver_time;
    opts->ver_mem = memUsed();
  }
}

void ModelChecker::printReachWithSucc(void)
{
  VERB_LN(2, "Printing/generating reachable states (with successors)");

  BDD *reach = new BDD(*initStates);
  BDD reach_p = cuddMgr->bddZero();

  while (*reach != reach_p) {
    reach_p = *reach;
    BDD newStates = getSucc(*reach) - *reach;
    *reach += newStates;

  }

  BDD unproc = *reach;

  while (!unproc.IsZero()) {
    BDD t;
    t = unproc.PickOneMinterm(*pv);
    cout << "Successors of " << srs->decodedRctSysStateToStr(t) << ":" << endl;
    srs->printDecodedRctSysStates(getSucc(t));
    unproc -= t;
  }

  cleanup();
}

bool ModelChecker::checkReach(const Entities testState)
{
  if (opts->measure) {
    opts->ver_time = cpuTime();
  }

  // BDD st = srs->getEncState(testState);
  BDD st = BDD_FALSE;
  assert(0);

  BDD reach = *initStates;
  BDD reach_p = cuddMgr->bddZero();

  while (reach != reach_p) {
    if (reach * st != cuddMgr->bddZero()) {
      cleanup();
      return true;
    }

    reach_p = reach;
    reach += getSucc(reach);
  }

  cleanup();

  if (opts->measure) {
    opts->ver_time = cpuTime() - opts->ver_time;
    opts->ver_mem = memUsed();
  }

  return false;
}

BDD ModelChecker::getStatesRSCTLK(const FormRSCTLK *form)
{
  assert(reach != nullptr);
  Oper oper = form->getOper();

  if (oper == RSCTLK_PV) {
    return *form->getBDD() * *reach;
  }
  else if (oper == RSCTLK_TF) {
    if (form->getTF() == true) {
      return *reach;
    }
    else {
      return cuddMgr->bddZero();
    }
  }
  else if (oper == RSCTLK_AND) {
    return getStatesRSCTLK(form->getLeftSF()) * getStatesRSCTLK(form->getRightSF());
  }
  else if (oper == RSCTLK_OR) {
    return getStatesRSCTLK(form->getLeftSF()) + getStatesRSCTLK(form->getRightSF());
  }
  else if (oper == RSCTLK_XOR) {
    return getStatesRSCTLK(form->getLeftSF()) ^ getStatesRSCTLK(form->getRightSF());
  }
  else if (oper == RSCTLK_IMPL) {
    return (!getStatesRSCTLK(form->getLeftSF()) * *reach) + getStatesRSCTLK(
             form->getRightSF());
  }
  else if (oper == RSCTLK_NOT) {
    return !getStatesRSCTLK(form->getLeftSF()) * *reach;
  }
  else if (oper == RSCTLK_EX) {
    return getPreE(getStatesRSCTLK(form->getLeftSF())) * *reach;
  }
  else if (oper == RSCTLK_EG) {
    return statesEG(getStatesRSCTLK(form->getLeftSF()));
  }
  else if (oper == RSCTLK_EU) {
    return statesEU(
             getStatesRSCTLK(form->getLeftSF()),
             getStatesRSCTLK(form->getRightSF())
           );
  }
  else if (oper == RSCTLK_EF) {
    return statesEF(getStatesRSCTLK(form->getLeftSF()));
  }
  else if (oper == RSCTLK_AX) {
    return !getPreE(!getStatesRSCTLK(form->getLeftSF()) * *reach) * *reach;
  }
  else if (oper == RSCTLK_AG) {
    return !statesEF(
             !getStatesRSCTLK(form->getLeftSF()) * *reach) * *reach;
  }
  else if (oper == RSCTLK_AU) {
    BDD ng = !getStatesRSCTLK(form->getRightSF()) * *reach;
    BDD nf = !getStatesRSCTLK(form->getLeftSF()) * *reach;

    BDD x = !statesEU(ng, ng * nf) * *reach;

    if (!x.IsZero()) {
      x = x * !statesEG(ng) * *reach;
    }

    return x;
  }
  else if (oper == RSCTLK_AF) {
    return !statesEG(
             !getStatesRSCTLK(form->getLeftSF()) * *reach) * *reach;
  }
  /***** CONTEXT RESTRICTIONS: ******/
  else if (oper == RSCTLK_EX_ACT) {
    return getPreEctx(getStatesRSCTLK(form->getLeftSF()),
                      form->getActionsBDD()) * *reach;
  }
  else if (oper == RSCTLK_EG_ACT) {
    return statesEGctx(form->getActionsBDD(),
                       getStatesRSCTLK(form->getLeftSF())); /***** EG? *****/
  }
  else if (oper == RSCTLK_EU_ACT) {
    return statesEUctx(
             form->getActionsBDD(),
             getStatesRSCTLK(form->getLeftSF()),
             getStatesRSCTLK(form->getRightSF())
           );
  }
  else if (oper == RSCTLK_EF_ACT) {
    return statesEFctx(form->getActionsBDD(), getStatesRSCTLK(form->getLeftSF()));
  }
  else if (oper == RSCTLK_AX_ACT) {
    return !getPreEctx(!getStatesRSCTLK(form->getLeftSF()) * *reach,
                       form->getActionsBDD()) * *reach;
  }
  else if (oper == RSCTLK_AG_ACT) {
    return !statesEFctx(form->getActionsBDD(),
                        !getStatesRSCTLK(form->getLeftSF()) * *reach) * *reach;
  }
  else if (oper == RSCTLK_AU_ACT) {
    BDD ng = !getStatesRSCTLK(form->getRightSF()) * *reach;
    BDD nf = !getStatesRSCTLK(form->getLeftSF()) * *reach;

    BDD x = !statesEUctx(form->getActionsBDD(), ng, ng * nf) * *reach;

    if (!x.IsZero()) {
      x = x * !statesEGctx(form->getActionsBDD(), ng) * *reach;
    }

    return x;
  }
  else if (oper == RSCTLK_AF_ACT) {
    return !statesEGctx(form->getActionsBDD(),
                        !getStatesRSCTLK(form->getLeftSF()) * *reach) * *reach;
  }
  else if (oper == RSCTLK_NK) {
    auto proc_id = srs->rs->getProcessID(form->getSingleAgent());
    return statesNK(getStatesRSCTLK(form->getLeftSF()) * *reach, proc_id) * *reach;
  }
  else if (oper == RSCTLK_UK) {
    auto proc_id = srs->rs->getProcessID(form->getSingleAgent());
    return *reach - (statesNK(*reach - getStatesRSCTLK(form->getLeftSF()), proc_id) * *reach);
  }

  assert(0); // Should never happen
  return BDD_FALSE;
}

BDD ModelChecker::statesEG(const BDD &states)
{
  BDD x = states;
  BDD x_p = cuddMgr->bddZero();

  while (x != x_p) {
    x_p = x;
    x = x * getPreE(x);
  }

  return x;
}

BDD ModelChecker::statesEU(const BDD &statesA, const BDD &statesB)
{
  BDD x = statesA;
  BDD x_p = *reach;

  while (x != x_p) {
    x_p = x;
    x = x + (statesA * getPreE(x));
  }

  return x;
}

BDD ModelChecker::statesEF(const BDD &states)
{
  BDD x = states;
  BDD x_p = *reach;

  while (x != x_p) {
    x_p = x;
    x = x + (*reach * getPreE(x));
  }

  return x;
}

BDD ModelChecker::statesEGctx(const BDD *contexts, const BDD &states)
{
  BDD x = states;
  BDD x_p = cuddMgr->bddZero();

  while (x != x_p) {
    x_p = x;
    x = x * getPreEctx(x, contexts);
  }

  return x;
}

BDD ModelChecker::statesEUctx(const BDD *contexts, const BDD &statesA,
                              const BDD &statesB)
{
  BDD x = statesA;
  BDD x_p = *reach;

  while (x != x_p) {
    x_p = x;
    x = x + (statesA * getPreEctx(x, contexts));
  }

  return x;
}

BDD ModelChecker::statesEFctx(const BDD *contexts, const BDD &states)
{
  BDD x = states;
  BDD x_p = *reach;

  while (x != x_p) {
    x_p = x;
    x = x + (*reach * getPreEctx(x, contexts));
  }

  return x;
}

BDD ModelChecker::statesNK(const BDD &states, Process proc_id)
{
  // bdd = new BDD((arg[0]->satStates(reach) * reach).ExistAbstract(bddOnlyIth(getAgent())) * reach);
  BDD x = states;

  x = x.ExistAbstract(getIthOnly(proc_id));

  return x;
}

BDD ModelChecker::getIthOnly(Process proc_id)
{
  /* if possible, we return the BDD from cache */
  if (ith_only_E.count(proc_id) == 1) {
    return ith_only_E[proc_id];
  }

  /* nothing has been found in the cache */
  BDD bdd = BDD_TRUE;

  for (auto i = 0; i < pv_drs_E->size(); ++i) {

    if (i == proc_id) {
      continue;
    }

    bdd *= (*pv_drs_E)[i];
  }

  ith_only_E[proc_id] = bdd;

  return bdd;
}

bool ModelChecker::checkRSCTLK(FormRSCTLK *form)
{
  if (form->isERSCTLK()) {
    return checkRSCTLKbmc(form);
  }
  else {
    return checkRSCTLKfull(form);
  }
}

bool ModelChecker::checkRSCTLKfull(FormRSCTLK *form)
{
  if (opts->measure) {
    opts->ver_time = cpuTime();
  }

  assert(form != nullptr);

  bool result = false;

  VERB("Model checking for RSCTLK formula: " << form->toStr());

  VERB("Processing the formula: encoding entities");
  form->encodeEntities(srs);
  VERB("Entities encoded");

  VERB("Processing the formula: encoding actions/contexts");
  form->encodeActions(srs);
  VERB("Contexts encoded");

  assert(reach == nullptr);

  reach = new BDD(*initStates);
  BDD reach_p = cuddMgr->bddZero();

  VERB("Generating state space");

  unsigned int k = 0;

  while (*reach != reach_p) {

    if (opts->show_progress) {
      cout << "\rIteration " << ++k << flush;
    }

    if (opts->reorder_reach) {
      VERB_L2("Reordering")
      Cudd_ReduceHeap(cuddMgr->getManager(), CUDD_REORDER_SIFT, 10000);
    }

    reach_p = *reach;
    *reach += getSucc(*reach);
  }

  if (opts->show_progress) {
    cout << endl;
  }

  VERB("Checking the formula");

  //if (*initStates * getStatesRSCTLK(form) != cuddMgr->bddZero())
  if (*initStates * getStatesRSCTLK(form) == *initStates) {
    result = true;
  }
  else {
    result = false;
  }

  cleanup();

  cout << "Formula " << form->toStr() << (result ? " holds" : " does not hold")
       << endl;

  if (opts->measure) {
    opts->ver_time = cpuTime() - opts->ver_time;
    opts->ver_mem = memUsed();
  }

  return result;
}

bool ModelChecker::checkRSCTLKbmc(FormRSCTLK *form)
{
  if (opts->measure) {
    opts->ver_time = cpuTime();
  }

  assert(form != nullptr);

  if (!form->isERSCTLK()) {
    FERROR("Formula " << form->toStr() <<
           " is not syntactically an ERSCTLK formula");
  }

  bool result = false;

  VERB("Bounded model checking for RSCTLK formula: " << form->toStr());

  VERB("Processing the formula: encoding entities");
  form->encodeEntities(srs);
  VERB("Entities encoded");

  VERB("Processing the formula: encoding actions/contexts");
  form->encodeActions(srs);
  VERB("Contexts encoded");

  assert(reach == nullptr);

  reach = new BDD(*initStates);
  BDD reach_p = cuddMgr->bddZero();

  unsigned int k = 0;

  while (*reach != reach_p) {
    if (opts->show_progress) {
      cout << "\rIteration " << ++k << flush;
    }

    //if (*initStates * getStatesRSCTLK(form) != cuddMgr->bddZero())
    if (*initStates * getStatesRSCTLK(form) == *initStates) {
      result = true;
      break;
    }

    reach_p = *reach;
    *reach += getSucc(*reach);
  }

  if (opts->show_progress) {
    cout << endl;
  }

  cleanup();

  cout << "Formula " << form->toStr() << " " << (result ? "holds" :
       "does not hold") << endl;

  if (opts->measure) {
    opts->ver_time = cpuTime() - opts->ver_time;
    opts->ver_mem = memUsed();
  }

  return result;
}

void ModelChecker::reorder(void)
{
  if (opts->reorder_trans) {
    VERB_L2("Reordering START");
    // Cudd_ReduceHeap(cuddMgr->getManager(), CUDD_REORDER_SIFT, 100000);
    cuddMgr->ReduceHeap(CUDD_REORDER_GROUP_SIFT);
    VERB_L2("Reordering DONE");
  }
}

void ModelChecker::cleanup(void)
{
  delete reach;
  reach = nullptr;
}

/** EOF **/
