/**
 * @file IntermediateStock.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_STOCK_HPP
#define ARTIS_FACTORY_STOCK_HPP

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

namespace artis::factory {

struct StockEntry {
  unsigned int po_id;
  unsigned int po_index;
  unsigned int product_id;
  unsigned int quantity;

  StockEntry(unsigned int po_id = 0, unsigned int po_index = 0, unsigned int product_id = 0, unsigned int quantity = 0)
    : po_id(po_id), po_index(po_index), product_id(product_id), quantity(quantity) {}

  bool operator==(const StockEntry &other) const {
    return po_id == other.po_id and po_index == other.po_index and product_id == other.product_id and
           quantity == other.quantity;
  }

  std::string to_string() const {
    return "StockEntry < " + std::to_string(po_id) + " | " + std::to_string(po_index) + " | " +
           std::to_string(product_id) + " | " + std::to_string(quantity) + " >";
  }
};

struct ProductDemand {
  unsigned int po_id;
  unsigned int po_index;
  unsigned int product_id;
  unsigned int quantity;

  ProductDemand(unsigned int po_id = 0, unsigned int po_index = 0, unsigned int product_id = 0,
                unsigned int quantity = 0)
    : po_id(po_id), po_index(po_index), product_id(product_id), quantity(quantity) {}

  bool operator==(const ProductDemand &other) const {
    return po_id == other.po_id and po_index == other.po_index and product_id == other.product_id and
           quantity == other.quantity;
  }

  std::string to_string() const {
    return "ProductDemand < " + std::to_string(po_id) + " | " + std::to_string(po_index) + " | " +
           std::to_string(product_id) + " | " + std::to_string(quantity) + " >";
  }
};

struct StockAvailableResponse {
  int po_id;

  enum values {
    AVAILABLE, NOT_AVAILABLE, NEW_AVAILABLE
  } response;

  StockAvailableResponse(unsigned int po_id = 0, values response = NOT_AVAILABLE)
    : po_id(po_id), response(response) {}

  bool operator==(const StockAvailableResponse &other) const {
    return po_id == other.po_id and response == other.response;
  }

  std::string to_string() const {
    return "StockAvailableResponse < " + std::to_string(po_id) + " | " +
           (response == AVAILABLE ? "AVAILABLE" : (response == NOT_AVAILABLE ? "NOT AVAILABLE" : "NEW AVAILABLE")) + " >";
  }
};

struct ProductResponse {
  int product_id;
  int po_id;

  ProductResponse(int product_id = -1, int po_id = -1) : product_id(product_id), po_id(po_id) {}

  bool operator==(const ProductResponse &other) const {
    return product_id == other.product_id and po_id == other.po_id;
  }

  std::string to_string() const {
    return "Response < " + std::to_string(product_id) + " | " + std::to_string(po_id) + " >";
  }
};

struct StockParameters {
  std::vector<unsigned int> _in_machines;
  std::vector<unsigned int> _out_machines;
  int _capacity; // if -1 then infinity
};

class Stock : public Dynamics<Stock, StockParameters> {
public:
  struct inputs {
    enum values {
      IN, IN_DEMAND = 1000, IN_STOCK_AVAILABLE = 2000
    };
  };

  struct outputs {
    enum values {
      OUT = 1000, OUT_STOCK_AVAILABLE = 2000
    };
  };

  struct vars {
    enum values {
      ENTRY_NUMBER
    };
  };

  Stock(const std::string &name, const Context<Stock, StockParameters> &context)
    : Dynamics<Stock, StockParameters>(name, context), _parameters(context.parameters()) {
    input_port({inputs::IN, "in"});
    for (const auto &m: _parameters._in_machines) {
      output_port({outputs::OUT + m, "out_" + std::to_string(m)});
      input_port({inputs::IN_DEMAND + m, "in_demand_" + std::to_string(m)});
    }
    for (const auto &m: _parameters._out_machines) {
      output_port({outputs::OUT_STOCK_AVAILABLE + m, "out_stock_available_" + std::to_string(m)});
      input_port({inputs::IN_STOCK_AVAILABLE + m, "in_stock_available_" + std::to_string(m)});
    }
    observables({{vars::ENTRY_NUMBER, "entry number"}});
  }

  ~Stock() 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:
  struct Phase {
    enum values {
      WAIT,
      SEND,
      SEND_AVAILABLE
    };

    static std::string to_string(const values &value) {
      switch (value) {
        case WAIT:
          return "WAIT";
        case SEND:
          return "SEND";
        case SEND_AVAILABLE:
          return "SEND AVAILABLE";
      }
      return "";
    }
  };

  struct Entry {
    Time _t;
    unsigned int _po_id;
    unsigned int _po_index;
    unsigned int _product_id;
    unsigned int _quantity;
  };

  struct AvailableDemand {
    unsigned int _machine_id;
    unsigned int _po_id;
    bool _available;
  };

  StockParameters _parameters;

  Phase::values _phase;
  std::vector<Entry> _entries;
  std::vector<std::pair<unsigned int, ProductDemand>> _demands;
  std::vector<AvailableDemand> _available_demands;
  std::deque<std::pair<unsigned int, unsigned int>> _waiting_machine_ids; // pair(machine_id,po_id)
};

} // namespace artis::factory

#endif