///////////////////////////////////////////////////////////////////////////////
//        Copyright (c) 2012-2020 Oscar Riveros. all rights reserved.        //
//                        oscar.riveros@peqnp.science                        //
//                                                                           //
//   Without any restriction, Oscar Riveros reserved rights, patents and     //
//  commercialization of this knowledge or derived directly from this work.  //
///////////////////////////////////////////////////////////////////////////////

#ifndef MIP_SOLVER_MIP_HH
#define MIP_SOLVER_MIP_HH

#include "result.hh"
#include "simplex.hh"

class pixie {
private:
  unsigned int vc{};
  unsigned int ec{};
  std::vector<double> obj;
  std::vector<std::vector<double>> ll;
  std::vector<C> cc;
  std::vector<double> rr;
  std::deque<bool> ints;

  std::tuple<unsigned int, unsigned int> sv() const {
    auto st = 0, at = 0;
    for (unsigned int i = 0; i < ec; ++i) {
      if (cc[i] == C::E) {
        ++at;
      } else {
        ++st;
        if ((rr[i] < 0.0 && cc[i] == C::L) || (rr[i] >= 0.0 && cc[i] == C::G)) {
          ++at;
        }
      }
    }
    return std::make_tuple(st, at);
  }

  simplex mt(const unsigned int st, const unsigned int at) const {
    simplex sm(vc, ec, st, at);
    auto idx = vc + 1;
    for (unsigned int i = 0; i < ec; ++i) {
      sm.table[i][0] = rr[i];
      for (unsigned int j = 0; j < vc; ++j) {
        sm.table[i][j + 1] = ll[i][j];
      }
      if (sm.table[i][0] < 0.0) {
        for (unsigned int j = 0; j <= vc; ++j) {
          sm.table[i][j] = -sm.table[i][j];
        }
      }
      if (cc[i] != C::E) {
        if ((rr[i] < 0.0 && cc[i] == C::L) || (rr[i] >= 0.0 && cc[i] == C::G)) {
          sm.table[i][idx] = -1.0;
        } else {
          sm.table[i][idx] = 1.0;
          sm.idx[i] = idx - 1;
        }
        ++idx;
      }
    }
    for (unsigned int i = 0; i < ec; ++i) {
      if ((rr[i] < 0.0 && cc[i] != C::G) || (rr[i] >= 0.0 && cc[i] != C::L)) {
        sm.table[i][idx] = 1.0;
        sm.idx[i] = idx - 1;
        sm.flags[i] = true;
        ++idx;
      }
    }
    if (at == 0) {
      for (unsigned int i = 0; i < vc; ++i) {
        sm.table[ec][i + 1] = -obj[i];
      }
    } else {
      for (unsigned int i = 0; i < ec; ++i) {
        if (!sm.flags[i])
          continue;
        for (unsigned int j = 0; j <= vc + st; ++j) {
          sm.table[ec][j] -= sm.table[i][j];
        }
      }
    }
    return sm;
  }

  void cz(const unsigned int &st, simplex &sm) const noexcept {
    for (unsigned int j = 0; j <= vc + st; ++j) {
      sm.table[ec][j] = 0.0;
      if (1 <= j && j <= vc) {
        sm.table[ec][j] = -obj[j - 1];
      }
      for (unsigned int i = 0; i < ec; ++i) {
        if (sm.idx[i] < vc) {
          sm.table[ec][j] += obj[sm.idx[i]] * sm.table[i][j];
        } else if (sm.idx[i] < vc + st) {

        } else {
          sm.table[ec][j] += (-1.0) * sm.table[i][j];
        }
      }
    }
  }

  void sb(const unsigned int &index, const double &border, const C &mode) {
    ++ec;
    std::vector<double> temp(vc, 0.0);
    temp[index] = 1.0;
    ll.push_back(temp);
    cc.push_back(mode);
    if (mode == C::L) {
      rr.push_back(floor(border));
    } else {
      rr.push_back(ceil(border));
    }
  }

  result po() const {
    result result(vc);
    size_t sc, ac;
    std::tie(sc, ac) = sv();
    auto sm = mt(sc, ac);
    if (ac > 0) {
      if (!sm.sl()) {
        return result;
      }
      if (!sm.is()) {
        return result;
      }
      cz(sc, sm);
      sm.ac = 0;
      if (!sm.sl()) {
        return result;
      }
    } else {
      if (!sm.sl()) {
        return result;
      }
    }
    result.z = sm.table[ec][0];
    for (size_t i = 0; i < ec; ++i) {
      if (sm.idx[i] < vc) {
        result.x[sm.idx[i]] = sm.table[i][0];
      }
    }
    if (result.z >= std::numeric_limits<double>::max()) {
      result.z = -std::numeric_limits<double>::max();
    }
    return result;
  }

  result optimize(const result &lb) {
    result pr = po();
    if (pr.z < lb.z) {
      return lb;
    }
    size_t idx = 0;
    for (size_t i = 0; i < vc; ++i) {
      if (ints[i] && !ii(pr.x[i])) {
        idx = i + 1;
        auto br{*this};
        br.sb(idx - 1, pr.x[idx - 1], C::L);
        sb(idx - 1, pr.x[idx - 1], C::G);
        return optimize(br.optimize(lb));
      }
    }
    return pr;
  }

public:
  pixie() = default;

  void set_integer_condition(const std::vector<bool> &integers) {
    ints.assign(integers.begin(), integers.end());
  }

  void add_constraint(const std::vector<double> &constraint,
                      const std::string &comparator, const double &right) {
    ll.push_back(constraint);
    if (comparator == "<=") {
      cc.push_back(C::L);
    } else if (comparator == "==") {
      cc.push_back(C::E);
    } else if (comparator == ">=") {
      cc.push_back(C::G);
    }
    rr.push_back(right);
    ec++;
  }

  result optimize() {
    result result(vc);
    return optimize(result);
  }

  void add_objective(const std::vector<double> &objective) {
    obj = objective;
    vc = objective.size();
  }

  //! Show contents
  void put(char *lp_path) const noexcept {
    std::ofstream lp_file(lp_path);
    lp_file << "max: ";
    for (unsigned i = 0; i < vc; ++i) {
      lp_file << (obj[i] >= 0 ? "+" + std::to_string(std::abs(obj[i]))
                              : "-" + std::to_string(std::abs(obj[i])))
              << " * x" << (i + 1) << " ";
    }
    lp_file << ";" << std::endl;
    for (unsigned i = 0; i < ec; ++i) {
      for (unsigned j = 0; j < vc; ++j) {
        if (ii(ll[i][j]) != 0.0) {
          lp_file << (ll[i][j] >= 0 ? "+" + std::to_string(std::abs(ll[i][j]))
                                    : "-" + std::to_string(std::abs(ll[i][j])))
                  << " * x" << (j + 1) << " ";
        }
      }
      std::string tt{"< >"};
      lp_file << tt[static_cast<unsigned int>(cc[i])] << "= ";
      lp_file << (rr[i] >= 0 ? "+" + std::to_string(std::abs(rr[i]))
                             : "-" + std::to_string(std::abs(rr[i])))
              << "; \n";
    }
    for (unsigned i = 0; i < vc; ++i) {
      if (ints[i]) {
        lp_file << "int "
                << "x" << (i + 1) << ";" << std::endl;
      }
    }
    lp_file << std::endl;

    lp_file.close();
  }
};

#endif // MIP_SOLVER_MIP_HH
