/**
 * @file Base.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_JSON_READER_HPP
#define ARTIS_FACTORY_JSON_READER_HPP

#include "Combiner.hpp"
#include "Conveyor.hpp"
#include "Machine.hpp"
#include "Processor.hpp"
#include "ProductionOrderGenerator.hpp"
#include "Separator.hpp"
#include "Sink.hpp"

#include <nlohmann/json.hpp>

namespace artis::factory {

struct Location {
  int _pool_id;
  unsigned int _stock_id;
};

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

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

struct MachineJob {
  unsigned int _machine_id;
  std::vector<In> _ins;
  std::vector<Out> _outs;
};

struct PoolJob {
  unsigned int _pool_id;
  std::vector<MachineJob> _machine_jobs;
};

struct Product {
  unsigned int _id;
  std::vector<PoolJob> _pool_jobs;
};

struct Generator {
  uint _random_seed;
  uint _min_send_speed_rate;
  uint _max_send_speed_rate;
};

struct Stock {
  unsigned int _id;
  int _capacity; // if -1 then infinity
};

struct Item {
  unsigned int _id;
  int _quantity; // if -1 then infinity
};

struct ItemStock {
  unsigned int _id;
  std::vector<Item> _items;
};

struct Factory {
  Generator _generator;
  std::vector<std::tuple<unsigned int, unsigned int, std::vector<std::shared_ptr<MachineParameters>>, std::vector<Stock> >> _pools;
  std::map<unsigned int, Product> _products;
  std::vector<ItemStock> _stocks;
};

class JsonReader {
public:
  JsonReader() = default;

  const Factory &factory() const { return _factory; }

  Factory &factory() { return _factory; }

  void parse(const std::string &str) {
    nlohmann::json data = nlohmann::json::parse(str);

    parse_products(data["products"]);
    parse_generator(data["generator"]);
    parse_pools(data["pools"]);
    if (data.contains("stocks")) {
      parse_item_stocks(data["stocks"]);
    }
  }

private:
  void parse_generator(const nlohmann::json &data) {
    _factory._generator = Generator{data["random_seed"].get<unsigned int>(),
                                    data["min_send_speed_rate"].get<unsigned int>(),
                                    data["max_send_speed_rate"].get<unsigned int>()};
  }

  std::vector<In> parse_ins(const nlohmann::json &data) {
    std::vector<In> ins;

    for (const nlohmann::json &in: data) {
      int item_id = in.contains("itemID") ? in["itemID"].get<int>() : -1;
      int source_pool_id = in["source"].contains("poolID") ? in["source"]["poolID"].get<int>() : -1;
      unsigned int source_stock_id = in["source"]["stockID"].get<unsigned int>();
      unsigned int quantity = in.contains("quantity") ? in["quantity"].get<int>() : 1;

      ins.push_back(In{item_id, Location{source_pool_id, source_stock_id}, quantity});
    }
    return ins;
  }

  std::vector<std::shared_ptr<MachineParameters>> parse_machines(const nlohmann::json &data, unsigned int pool_id) {
    std::vector<std::shared_ptr<MachineParameters>> machines;

    for (const nlohmann::json &machine: data) {
      unsigned int machine_type = machine["type"].get<unsigned int>();

      switch (machine_type) {
        case PROCESSOR: {
          unsigned int load_time = machine.contains("load_time") ? machine["load_time"].get<unsigned int>() : 0;
          unsigned int processing_time = machine.contains("processing_time")
                                         ? machine["processing_time"].get<unsigned int>() : 0;
          unsigned int unload_time = machine.contains("unload_time") ? machine["unload_time"].get<unsigned int>() : 0;

          machines.push_back(
            std::make_shared<ProcessorParameters>(ProcessorParameters{{machine["id"].get<unsigned int>(), machine_type,
                                                                       pool_id}, load_time, processing_time,
                                                                      unload_time}));
          break;
        }
        case SINK: {
          machines.push_back(std::make_shared<SinkParameters>(
            SinkParameters{{machine["id"].get<unsigned int>(), machine_type, pool_id}}));
          break;
        }
        case SEPARATOR: {
          // TODO
          machines.push_back(std::make_shared<SeparatorParameters>(
            SeparatorParameters{{machine["id"].get<unsigned int>(), machine_type, pool_id}}));
          break;
        }
        case COMBINER : {
          // TODO
          machines.push_back(std::make_shared<CombinerParameters>(
            CombinerParameters{{machine["id"].get<unsigned int>(), machine_type, pool_id}}));
          break;
        }
        case CONVEYOR : {
          // TODO
          machines.push_back(std::make_shared<ConveyorParameters>(
            ConveyorParameters{{machine["id"].get<unsigned int>(), machine_type, pool_id}}));
          break;
        }
        default: {
        }
      }
    }
    return machines;
  }

  std::vector<Out> parse_outs(const nlohmann::json &data) {
    std::vector<Out> outs;

    for (const nlohmann::json &out: data) {
      int item_id = out.contains("itemID") ? out["itemID"].get<int>() : -1;
      int destination_pool_id = out["destination"].contains("poolID") ? out["destination"]["poolID"].get<int>() : -1;
      unsigned int destination_stock_id = out["destination"]["stockID"].get<unsigned int>();
      unsigned int quantity = out.contains("quantity") ? out["quantity"].get<int>() : 1;

      outs.push_back(Out{item_id, Location{destination_pool_id, destination_stock_id}, quantity});
    }
    return outs;
  }

  void parse_pools(const nlohmann::json &data) {
    for (const nlohmann::json &pool: data) {
      unsigned int pool_id = pool["id"].get<unsigned int>();
      unsigned int pool_type = pool["type"].get<unsigned int>();
      std::vector<Stock> stocks;

      if (pool.contains("stocks")) {
        for (const nlohmann::json &stock: pool["stocks"]) {
          unsigned int stock_id = stock["id"].get<unsigned int>();
          int capacity = stock["capacity"].get<int>();

          stocks.push_back({stock_id, capacity});
        }
      }
      _factory._pools.emplace_back(pool_id, pool_type, parse_machines(pool["machines"], pool_id), stocks);
    }
  }

  void parse_products(const nlohmann::json &data) {
    for (const nlohmann::json &product: data) {
      unsigned int product_id = product["id"].get<unsigned int>();

      _factory._products[product_id] = Product{product_id, parse_program(product["program"])};
    }
  }

  std::vector<PoolJob> parse_program(const nlohmann::json &data) {
    std::vector<PoolJob> program;

    for (const nlohmann::json &pool_machine: data) {
      unsigned int pool_id = pool_machine["poolID"].get<unsigned int>();

      program.push_back(PoolJob{pool_id, parse_sequence(pool_machine["sequence"])});
    }
    return program;
  }

  std::vector<MachineJob> parse_sequence(const nlohmann::json &data) {
    std::vector<MachineJob> sequence;

    for (const nlohmann::json &machine: data) {
      if (machine.is_object()) {
        std::vector<In> ins;
        std::vector<Out> outs;

        if (machine.contains("ins")) {
          ins = parse_ins(machine["ins"]);
        }
        if (machine.contains("outs")) {
          outs = parse_outs(machine["outs"]);
        }
        sequence.push_back(MachineJob{machine["machineID"].get<unsigned int>(), ins, outs});
      } else {
        sequence.push_back(MachineJob{machine.get<unsigned int>(), {}, {}});
      }
    }
    return sequence;
  }

  void parse_item_stocks(const nlohmann::json &data) {
    for (const nlohmann::json &stock: data) {
      unsigned int stock_id = stock["id"].get<unsigned int>();
      std::vector<Item> items;

      for (const nlohmann::json &item: stock["items"]) {
        unsigned int item_id = item["id"].get<unsigned int>();
        int quantity = item["quantity"].get<int>();

        items.push_back({item_id, quantity});
      }
      _factory._stocks.push_back({stock_id, items});
    }
  }

  Factory _factory;
};

}

#endif //ARTIS_FACTORY_JSON_READER_HPP
