/**
 * @file Processor.hpp
 * @author The ARTIS Development Team
 * See the AUTHORS or Authors.txt file
 */

/*
 * ARTIS - the multimodeling and simulation environment
 * This file is a part of the ARTIS environment
 *
 * Copyright (C) 2013-2023 ULCO http://www.univ-littoral.fr
 *
 * 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/>.
 */

#ifndef ARTIS_FACTORY_PROCESSOR_HPP
#define ARTIS_FACTORY_PROCESSOR_HPP

#include "Base.hpp"
#include "Machine.hpp"
#include "ProductionOrder.hpp"

namespace artis::factory {

struct Location {
  int _pool_id; // -1 if global
  unsigned int _stock_id;
};

struct In {
  int _id; // -1 if current product
  Location _source;
  unsigned int _quantity;
};

struct Out {
  int _id; // -1 if current product
  Location _destination;
  unsigned int _quantity;
};

struct InOut {
  std::vector<In> _ins;
  std::vector<Out> _outs;
};

struct ProcessorDurations : Durations {
  uint load_time;
  uint processing_time;
  uint unload_time;
};

struct ProcessorParameters : MachineParameters {
  ProcessorDurations durations;
  std::map<unsigned int, InOut> in_out;
  std::map<unsigned int, std::shared_ptr<ProcessorDurations>> product_durations;
};

class Processor : public Dynamics<Processor, ProcessorParameters> {
public:
  struct inputs {
    enum values {
      IN, IN_ITEM, IN_STOCK, IN_STOCK_AVAILABLE = 4000
    };
  };

  struct outputs {
    enum values {
      OUT, OUT_ITEM_DEMAND = 1000, OUT_STOCK = 2000, OUT_STOCK_DEMAND = 3000, OUT_STOCK_AVAILABLE = 4000
    };
  };

  struct vars {
    enum values {
    };
  };

  Processor(const std::string &name, const Context<Processor, ProcessorParameters> &context)
    : Dynamics<Processor, ProcessorParameters>(name, context), _parameters(context.parameters()) {
    input_ports({{inputs::IN,       "in"},
                 {inputs::IN_ITEM,  "in_supply"},
                 {inputs::IN_STOCK, "in_stock"}});
    output_port({outputs::OUT, "out"});

    // demand: processor -> item stocks
    std::vector<unsigned int> item_stocks;

    for (const auto &p: _parameters.in_out) {
      for (const auto &q: p.second._ins) {
        if (q._source._pool_id == -1 and
            std::find(item_stocks.cbegin(), item_stocks.cend(), q._source._stock_id) == item_stocks.cend()) {
          item_stocks.push_back(q._source._stock_id);
        }
      }
    }
    for (const auto &p: item_stocks) {
      output_port({outputs::OUT_ITEM_DEMAND + p, "out_demand_supply_" + std::to_string(p)});
    }

    // processor -> intermediate stocks
    std::vector<std::pair<unsigned int, unsigned int>> stocks;

    for (const auto &p: _parameters.in_out) {
      for (const auto &q: p.second._outs) {
        auto r = std::make_pair((unsigned int) q._destination._pool_id, q._destination._stock_id);

        if (q._destination._pool_id != -1 and std::find(stocks.cbegin(), stocks.cend(), r) == stocks.cend()) {
          stocks.push_back(r);
        }
      }
    }
    for (const auto &p: stocks) {
      output_port(
        {outputs::OUT_STOCK + (p.first * 100 + p.second),
         "out_stock_" + std::to_string(p.first) + "_" + std::to_string(p.second)});
      output_port(
        {outputs::OUT_STOCK_AVAILABLE + (p.first * 100 + p.second),
         "out_stock_available_" + std::to_string(p.first) + "_" + std::to_string(p.second)});
      input_port(
        {inputs::IN_STOCK_AVAILABLE + (p.first * 100 + p.second),
         "in_stock_available_" + std::to_string(p.first) + "_" + std::to_string(p.second)});
    }

    // demand: processor -> intermediate stocks
    stocks.clear();
    for (const auto &p: _parameters.in_out) {
      for (const auto &q: p.second._ins) {
        auto r = std::make_pair((unsigned int) q._source._pool_id, q._source._stock_id);

        if (q._source._pool_id != -1 and std::find(stocks.cbegin(), stocks.cend(), r) == stocks.cend()) {
          stocks.push_back(r);
        }
      }
    }
    for (const auto &p: stocks) {
      output_port(
        {outputs::OUT_STOCK_DEMAND + (p.first * 100 + p.second),
         "out_stock_demand_" + std::to_string(p.first) + "_" + std::to_string(p.second)});
    }
  }

  ~Processor() override = default;

  void dint(const Time &t) override;

  void dext(const Time &t, const Time &e, const Bag &bag) override;

  void start(const Time &t) override;

  Time ta(const Time &t) const override;

  Bag lambda(const Time &t) const override;

  artis::common::event::Value observe(const Time &t, unsigned int index) const override;

private:
  const ProcessorDurations &durations(unsigned int product_id) const {
    if (_parameters.product_durations.find(product_id) == _parameters.product_durations.cend()) {
      return _parameters.durations;
    } else {
      return *_parameters.product_durations.at(product_id);
    }
  }

  struct Phase {
    enum values {
      READY,
      WAIT,
      LOADING,
      PROCESSING,
      UNLOADING,
      DEMAND_SUPPLY,
      WAIT_SUPPLY,
      SEND_STOCK_AVAILABLE,
      WAIT_STOCK_AVAILABLE
    };

    static std::string to_string(const values &value) {
      switch (value) {
        case READY:
          return "READY";
        case WAIT:
          return "WAIT";
        case LOADING:
          return "LOADING";
        case PROCESSING:
          return "PROCESSING";
        case UNLOADING:
          return "UNLOADING";
        case DEMAND_SUPPLY:
          return "DEMAND SUPPLY";
        case WAIT_SUPPLY:
          return "WAIT SUPPLY";
        case SEND_STOCK_AVAILABLE:
          return "SEND STOCK AVAILABLE";
        case WAIT_STOCK_AVAILABLE:
          return "WAIT STOCK AVAILABLE";
      }
      return "";
    }
  };

  struct Job {
    Phase::values _phase;
    std::unique_ptr<ProductionOrder> _po;
    unsigned int _demand_number;
    Time _next_time;
  };

// parameters
  ProcessorParameters _parameters;

// state
  std::map<unsigned int, Job> _jobs; // key = po_id
};

} // namespace artis::factory

#endif