#include <artis-star/common/RootCoordinator.hpp>
#include <artis-star/kernel/pdevs/Coordinator.hpp>
#include <artis-star/kernel/pdevs/GraphManager.hpp>
#include <iostream>
#include <random>

#include "FactoryGraphManager.hpp"
#include "Machine.hpp"

using namespace artis::factory;

constexpr unsigned int product_number = 10;
constexpr unsigned int machine_number = 5;
constexpr unsigned int po_number = 100;
constexpr unsigned int iteration_number = 1000;

unsigned int processing_times[product_number][machine_number] = {
  {2,  7,  12, 17, 22},
  {3,  8,  13, 18, 23},
  {1,  9,  12, 19, 6},
  {5,  10, 15, 20, 25},
  {6,  11, 16, 21, 26},
  {27, 28, 29, 30, 2},
  {3,  4,  15, 10, 7},
  {8,  9,  10, 11, 12},
  {13, 3,  15, 7,  17},
  {18, 19, 20, 21, 22}
};

struct Solution {
  std::vector<unsigned int> po_order;
  std::vector<unsigned int> capacities;
};

class MyView : public View {
public:
  MyView() {
    for (int i = 0; i < (int) machine_number - 1; ++i) {
      selector("Stock_" + std::to_string(i) + ":entry_number",
               {FactoryGraphManager::STOCK + i, Stock::vars::ENTRY_NUMBER});
    }
    selector("Sink:finished_po_number",
             {FactoryGraphManager::MACHINE + machine_number, Sink::vars::FINISHED_PO_NUMBER});
  }
};

std::vector<unsigned int> make_po_order() {
  std::random_device rd;
  std::mt19937 gen(rd());
  std::vector<unsigned int> order;
  std::vector<unsigned int> allNumbers;

  for (unsigned int i = 0; i < po_number; ++i) {
    allNumbers.push_back(i);
  }
  while (order.size() < po_number) {
    std::uniform_int_distribution<unsigned int> dist(0, allNumbers.size() - 1);
    unsigned int index = dist(gen);

    order.push_back(allNumbers[index]);
    allNumbers.erase(allNumbers.begin() + index);
  }
  return order;
}

std::vector<unsigned int> make_capacities() {
  std::vector<unsigned int> capacities(machine_number - 1, -1);

  return capacities;
}

class Evaluator {
public:
  Evaluator() {
    make_flow_shop();
  }

  std::vector<double> evaluate(const Solution &solution) {
    apply(solution);

    FactoryGraphManagerParameters graph_parameters{factory};
    artis::common::context::Context<artis::common::DoubleTime> context(0, 7200);
    artis::common::RootCoordinator<
      artis::common::DoubleTime, artis::pdevs::Coordinator<
        artis::common::DoubleTime, FactoryGraphManager, artis::common::NoParameters, FactoryGraphManagerParameters>
    > rc(context, "root", artis::common::NoParameters(), graph_parameters);

    rc.attachView("V", new MyView);
    rc.switch_to_timed_observer(1);

    rc.run(context);

    auto sink_values = rc.observer().view("V").get("Sink:finished_po_number");
    double t = -1;

    for (const auto &v: sink_values) {
      unsigned int n;

      v.second.operator()(n);
      if (n == po_number) {
        t = v.first;
        break;
      }
    }

    double sum = 0;
    for (unsigned int i = 0; i < machine_number - 1; ++i) {
      auto stock_values = rc.observer().view("V").get("Stock_" + std::to_string(i) + ":entry_number");

      for (const auto &v: stock_values) {
        unsigned int n;

        v.second.operator()(n);
        if (v.first > t) {
          break;
        }
        sum += n;
      }
    }
    return {t, sum / (machine_number - 1) / t};
  }

private:
  void apply(const Solution &solution) {
    factory._generator._po_order = solution.po_order;
    for (unsigned int i = 0; i < machine_number - 1; ++i) {
      std::get<3>(factory._pools[0])[i]._capacity = (int)solution.capacities[i];
    }
  }

  void fix_durations() {
    for (const auto &product: factory._products) {
      unsigned int product_id = product.first;
      unsigned int pool_index = 0;

      for (const auto &pool_machine: product.second._pool_jobs) {
        unsigned int pool_id = pool_machine._pool_id;
        const auto &pool = *std::find_if(factory._pools.cbegin(), factory._pools.cend(),
                                         [pool_id](const auto &e) { return std::get<0>(e) == pool_id; });
        const auto &machines = std::get<2>(pool);
        unsigned int sequence_index = 0;

        for (const auto &machine: pool_machine._machine_jobs) {
          unsigned int machine_id = machine._machine_id;
          const auto &m = *std::find_if(machines.cbegin(), machines.cend(),
                                        [machine_id](const auto &e) { return e->machine_id == machine_id; });

          if (m->machine_type == PROCESSOR) {
            auto &M = (ProcessorParameters &) (*m);

            M.product_durations[product_id] = std::static_pointer_cast<ProcessorDurations>(machine._durations);
          }
          ++sequence_index;
        }
        ++pool_index;
      }
    }
  }

  void make_flow_shop() {
    // generator
    factory._generator._min_send_speed_rate = 0;
    factory._generator._max_send_speed_rate = 0;
    factory._generator._random_seed = 62642;
    factory._generator._po_number = po_number;
    // products
    for (unsigned int i = 0; i < product_number; ++i) {
      Product product{i, {}, {PoolJob{0, {}}}, true};

      for (unsigned int j = 0; j < machine_number; ++j) {
        std::vector<In> ins;
        std::vector<Out> outs;

        if (j > 0) {
          ins.push_back({-1, {0, j - 1}, 1});
        }
        if (j < machine_number - 1) {
          outs.push_back({-1, {0, j}, 1});
        }
        product._pool_jobs[0]._machine_jobs.push_back(
          MachineJob{j, ins, outs,
                     std::make_shared<ProcessorDurations>(ProcessorDurations{{}, 1, processing_times[i][j], 1})});
      }

      product._pool_jobs[0]._machine_jobs.push_back(MachineJob{machine_number, {}, {}, {}});
      factory._products[i] = product;
    }
    // pools
    factory._pools.push_back({0, 0, {}, {}});
    for (unsigned int i = 0; i < machine_number; ++i) {
      std::map<unsigned int, InOut> in_out;

      for (const auto &product: factory._products) {
        for (const auto &pool_job: product.second._pool_jobs) {
          if (pool_job._pool_id == 0) {
            for (const auto &machine_job: pool_job._machine_jobs) {
              if (machine_job._machine_id == i) {
                in_out[product.first] = InOut{machine_job._ins, machine_job._outs};
              }
            }
          }
        }
      }
      std::get<2>(factory._pools[0]).push_back(std::make_shared<ProcessorParameters>(ProcessorParameters{{i,  0, 0, 1},
                                                                                                         {{}, 1, 3, 1},
                                                                                                         in_out,
                                                                                                         {}}));
    }
    std::get<2>(factory._pools[0]).push_back(
      std::make_shared<SinkParameters>(SinkParameters{{machine_number, 1, 0, 1}}));
    // intermediate stocks
    for (unsigned int i = 0; i < machine_number - 1; ++i) {
      std::get<3>(factory._pools[0]).push_back({i, -1});
    }

    fix_durations();
  }

  Factory factory;
};

int main() {
  Evaluator evaluator;

  unsigned int best_score = 99999;

  for (unsigned int i = 0; i < iteration_number; ++i) {
    std::random_device rd;
    std::mt19937 generator(rd());
    std::vector<unsigned int> liste_po = make_po_order();

    std::shuffle(liste_po.begin(), liste_po.end(), generator); // Random solver, pas forcément le meilleur, mais c'est fonctionnel \_(ツ)_/¯

    Solution solution{liste_po, make_capacities()}; // Fixer la capacité des stocks à l'infini ? Le but n'est pas de réduire cette variable ?

    unsigned int score = evaluator.evaluate(solution)[0];

    std::cout << score << " " << evaluator.evaluate(solution)[1] << std::endl;
    if (score < best_score)
    {
      // Diminution du score, je garde cette solution
      best_score = score;
      std::cout << "^ EST UN NOUVEAU SCORE MINIMAL ^" << std::endl;
    }
  }
  std::cout << "Score minimal : " << best_score <<std::endl;
}