/*
 * proto-suff
 * Copyright (C) 2020  Univ. Artois & CNRS
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "MinimumMajoritaryExplainer.hpp"
#include "src/Tree.hpp"
#include <iostream>
#include <sys/types.h>
#include <tuple>
#include <vector>

namespace protoss {
MinimumMajoritaryExplainer::MinimumMajoritaryExplainer() {}

/**
 * @brief Destroy the Minimum Majoritary Explainer:: Minimum Majoritary
 * Explainer object
 */
MinimumMajoritaryExplainer::~MinimumMajoritaryExplainer() {
  for (auto tree : forest)
    delete tree;
  forest.clear();
} // destructor

/**
 * @brief Add a tree in the data structure.
 *
 * @param tree is a list that represent the tree in a raw fashion following the
 * infixe format.
 */
void MinimumMajoritaryExplainer::addTree(const boost::python::list &tree) {
  std::vector<int> rawTree;
  for (int i = 0; i < len(tree); i++)
    rawTree.push_back(boost::python::extract<int>(tree[i]));
  rawTree.pop_back();
  forest.push_back(new Tree());
  forest.back()->parse(rawTree);

  if (m_maxFeatureIndex < forest.back()->getMaxIdentifier())
    m_maxFeatureIndex = forest.back()->getMaxIdentifier();
} // addTree

/**
 * @brief Consider as input an example and try to reduce it by computing a
 * minimum majoritary reason in a greedy way.
 *
 * @param example is a list of boolean features we want to explain.
 * @return boost::python::list an explaination.
 */
boost::python::list
MinimumMajoritaryExplainer::explain(const boost::python::list &example) {
  // init the stored example.
  std::vector<int> features;
  std::vector<u_char> storedExample(m_maxFeatureIndex + 1, LIT_UNDEF);
  for (int i = 0; i < len(example); i++) {
    int v = boost::python::extract<int>(example[i]);
    if (abs(v) <= m_maxFeatureIndex)
      features.push_back(v);
  }

#define NB_ITERATION 50
  srand(0);
  std::vector<int> bestExplanation;
  std::vector<Tree *> consideredTree(forest);

  for (unsigned ite = 0; ite < NB_ITERATION; ite++) {
    // init the example.
    for (auto v : features)
      storedExample[abs(v)] = v > 0 ? LIT_TRUE : LIT_FALSE;

    // init the trees.
    int counter = 0, notClassed = 0;
    for (auto tree : consideredTree) {
      int cl = tree->initExample(storedExample);
      if (!cl) {
        notClassed++;
        continue;
      }
      if (cl == BOTTOM)
        counter--;
      else
        counter++;
    }

    // compute the class w.r.t. majority.
    assert(abs(counter) > notClassed);
    int computedClass = (counter < 0) ? BOTTOM : TOP;

    if (!ite) {
      consideredTree.resize(0);
      for (auto tree : forest)
        if (tree->getSavedClass() == computedClass)
          consideredTree.emplace_back(tree);
    }

    // collect useful tree.
    std::vector<Tree *> usefulTree(consideredTree);
    std::vector<int> explanation;

    // try to remove some feature for the explanation.
    std::vector<int> tmp(features);
    if (ite)
      std::random_shuffle(tmp.begin(), tmp.end());

    while (tmp.size()) {
      int v = tmp.back();
      tmp.pop_back();

      // check if v is removed.
      int save = storedExample[abs(v)];
      storedExample[abs(v)] = LIT_UNDEF;

      counter = 0;
      for (auto tree : usefulTree)
        if (tree->canRemoveFeature(storedExample, abs(v)))
          counter++;

      if (counter > (forest.size() >> 1)) {
        unsigned j = 0;
        for (unsigned i = 0; i < usefulTree.size(); i++)
          if (usefulTree[i]->hasBeenCorrectlyClassified())
            usefulTree[j++] = usefulTree[i];
        usefulTree.resize(j);
      } else {
        storedExample[abs(v)] = save;
        explanation.push_back(v);

        for (unsigned i = 0; i < usefulTree.size(); i++)
          usefulTree[i]->undoLastModification();
      }
    }

    if (ite == 0 || bestExplanation.size() > explanation.size())
      bestExplanation = explanation;
  }

  // return the best explanation found so far.
  boost::python::list ret;
  for (auto v : bestExplanation)
    ret.append(v);
  return ret;
} // explain

} // namespace protoss