/**
 * @file ProductionOrder.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_PRODUCTION_ORDER_HPP
#define ARTIS_FACTORY_PRODUCTION_ORDER_HPP

#include <cstdint>
#include <memory>
#include <string>
#include <vector>

namespace artis::factory {

// HEADER = [bit number (16 bits)][ID (16 bits)][Product ID (16 bits)][index (8 bits)][operation number (8 bits)]
const std::size_t POOL_SIZE = 7;    // bit number
const std::size_t MACHINE_SIZE = 4; // bit number
const std::size_t TYPE_SIZE = 2;    // bit number
const std::size_t HEADER_SIZE = 8;  // byte number

const std::size_t BIT_NUMBER_POSITION = 0;
const std::size_t ID_POSITION = BIT_NUMBER_POSITION + 2;
const std::size_t PRODUCT_ID_POSITION = ID_POSITION + 2;
const std::size_t INDEX_POSITION = PRODUCT_ID_POSITION + 2;
const std::size_t OPERATION_NUMBER_POSITION = INDEX_POSITION + 1;

const uint8_t CHANGE_POOL_TYPE = 0x00;
const uint8_t CHANGE_MACHINE_TYPE = 0x01;

// pool type
const uint8_t FLOW_SHOP = 0x00;
const uint8_t JOB_SHOP = 0x01;
const uint8_t OPEN_SHOP = 0x02;

// machine type
const uint8_t PROCESSOR = 0x00;
const uint8_t SINK = 0x01;
const uint8_t SEPARATOR = 0x02;
const uint8_t COMBINER = 0x03;
const uint8_t CONVEYOR = 0x04;

struct Operation {
  virtual uint8_t get_type() const = 0;

  virtual uint8_t get_loc_index() const = 0;

  virtual std::string to_string() const = 0;

  virtual ~Operation() = default;
};

struct ChangePool : Operation {
  uint8_t _pool_id;

  explicit ChangePool(uint8_t pool_id) : _pool_id(pool_id) {}

  explicit ChangePool() { _pool_id = 0; }

  uint8_t get_type() const { return CHANGE_POOL_TYPE; }

  uint8_t get_loc_index() const { return _pool_id; }

  std::string to_string() const {
    return "ChangePool(" + std::to_string(_pool_id) + ")";
  }
};

struct ChangeMachine : Operation {
  uint8_t _machine_id;

  explicit ChangeMachine(uint8_t machine_id) : _machine_id(machine_id) {}

  explicit ChangeMachine() { _machine_id = 0; }

  uint8_t get_type() const { return CHANGE_MACHINE_TYPE; }

  uint8_t get_loc_index() const { return _machine_id; }

  std::string to_string() const {
    return "ChangeMachine(" + std::to_string((int) _machine_id) + ")";
  }
};

struct PoolMachineSequence {
  ChangePool _pool;
  std::vector<ChangeMachine> _machines;

  PoolMachineSequence() = default;

  PoolMachineSequence(uint8_t pool_index, const std::vector<uint8_t> &machines_indexes) {
    _pool._pool_id = pool_index;
    for (auto machinesIndex: machines_indexes) {
      _machines.emplace_back(machinesIndex);
    }
  }
};

typedef std::vector<PoolMachineSequence> Program;

struct ProductionOrder {
  uint8_t *_buffer;
  std::size_t _size;
  std::size_t _bsize;
  std::unique_ptr<Operation> _current_operation;
  bool _is_finish;

  struct Reference {
    uint8_t _byte_index;
    uint8_t _bit_index;

    void operator+=(uint8_t p);
  };

  ProductionOrder() : _buffer(nullptr), _size(0), _bsize(0), _current_operation(nullptr), _is_finish(true) {}

  ProductionOrder(uint8_t *buffer, std::size_t size);

  ~ProductionOrder() { delete[] _buffer; }

  ProductionOrder(uint16_t ID, uint16_t product_ID, const Program &program);

  const Operation &current_operation() const { return *_current_operation; }

  uint16_t getID() const {
    return (_buffer[ID_POSITION] << 8) + _buffer[ID_POSITION + 1];
  }

  std::unique_ptr<Operation> get_operation(uint8_t index) const;

  uint16_t get_productID() const {
    return (_buffer[PRODUCT_ID_POSITION] << 8) + _buffer[PRODUCT_ID_POSITION + 1];
  }

  bool is_finish() const { return _is_finish; }

  std::unique_ptr<Operation> last_operation() const;

  void next();

  std::size_t operation_index() const { return _buffer[INDEX_POSITION]; }

  std::size_t operation_number() const {
    return _buffer[OPERATION_NUMBER_POSITION];
  }

  std::unique_ptr<Operation> previous_operation() const;

  std::string to_string() const;

  void update_operation(uint8_t index);
};

} // namespace artis::factory

#endif