/**
 * @file ProductionOrder.cpp
 * @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/>.
 */

#include "ProductionOrder.hpp"

#include <algorithm>
#include <cassert>
#include <cstring>
#include <memory>

namespace artis::factory {

void put_bits(uint8_t *buffer, ProductionOrder::Reference &reference, uint8_t data, uint8_t bit_number) {
  if (8 - reference._bit_index >= bit_number) {
    buffer[reference._byte_index] |= data << (8 - bit_number - reference._bit_index);
    reference._bit_index += bit_number;
    if (reference._bit_index == 8) {
      ++reference._byte_index;
      reference._bit_index = 0;
    }
  } else {
    buffer[reference._byte_index] |= data >> (bit_number - (8 - reference._bit_index));
    ++reference._byte_index;
    reference._bit_index = bit_number - (8 - reference._bit_index);
    data &= 0xFF >> (8 - reference._bit_index);
    buffer[reference._byte_index] |= data << (8 - reference._bit_index);
  }
}

uint8_t get_bits(uint8_t const *buffer,
                 const ProductionOrder::Reference &reference,
                 uint8_t bit_number) {
  uint8_t data = 0;

  if (8 - reference._bit_index >= bit_number) {
    data = (uint8_t) (buffer[reference._byte_index] << reference._bit_index) >>
                                                                             (8 - bit_number);
  } else {
    uint8_t mask = ~(uint8_t) (0xFF << (8 - reference._bit_index));

    data = (uint8_t) (buffer[reference._byte_index] & mask);
    data <<= bit_number - (8 - reference._bit_index);
    data |= (uint8_t) (buffer[reference._byte_index + 1] >>
                                                         (8 - (bit_number - (8 - reference._bit_index))));
  }
  return data;
}

void ProductionOrder::Reference::operator+=(uint8_t p) {
  _byte_index += (_bit_index + p) / 8;
  _bit_index += p;
  _bit_index %= 8;
}

ProductionOrder::ProductionOrder(uint8_t *buffer, std::size_t size)
  : _buffer(nullptr), _size(size), _bsize(0), _current_operation(nullptr) {
  _buffer = new uint8_t[size];
  std::memcpy(_buffer, buffer, size);
  _bsize = (_buffer[BIT_NUMBER_POSITION] << 8) + _buffer[BIT_NUMBER_POSITION + 1];
  update_operation(_buffer[INDEX_POSITION]);
  _is_finish = _buffer[INDEX_POSITION] + 1 >= _buffer[OPERATION_NUMBER_POSITION];
}

ProductionOrder::ProductionOrder(uint16_t ID, uint16_t product_ID, const Program &program) : _is_finish(false) {
  Reference reference{0, 0};
  std::size_t operation_number = 0;

  _bsize = HEADER_SIZE * 8; // size + index + operation number
  std::for_each(program.cbegin(), program.cend(),
                [this, &operation_number](const PoolMachineSequence &sequence) {
                  _bsize +=
                    sequence._machines.size() * (MACHINE_SIZE + TYPE_SIZE);
                  _bsize += POOL_SIZE + TYPE_SIZE;
                  operation_number += sequence._machines.size() + 1;
                });
  _size = _bsize / 8 + 1;
  _buffer = new uint8_t[_size];
  memset(_buffer, 0, _size);

  // bit number
  _buffer[reference._byte_index++] = (uint8_t) (_bsize >> 8);
  _buffer[reference._byte_index++] = (uint8_t) (_bsize & 0xFF);

  // ID
  _buffer[reference._byte_index++] = (uint8_t) (ID >> 8);
  _buffer[reference._byte_index++] = (uint8_t) (ID & 0xFF);

  // product ID
  _buffer[reference._byte_index++] = (uint8_t) (product_ID >> 8);
  _buffer[reference._byte_index++] = (uint8_t) (product_ID & 0xFF);

  // index
  _buffer[reference._byte_index++] = (uint8_t) (0);

  assert(operation_number < 256);

  // operation number
  _buffer[reference._byte_index++] = (int8_t) (operation_number);

  // operations {change pool ; N change machine} * M
  std::for_each(program.cbegin(), program.cend(), [this, &reference](const PoolMachineSequence &sequence) {
    const ChangePool &operation = sequence._pool;

    assert(operation._pool_id < (1 << POOL_SIZE));

    put_bits(_buffer, reference, CHANGE_POOL_TYPE, TYPE_SIZE);
    put_bits(_buffer, reference, operation._pool_id, POOL_SIZE);
    std::for_each(sequence._machines.cbegin(), sequence._machines.cend(),
                  [this, &reference](const ChangeMachine &operation) {
                    assert(operation._machine_id < (1 << MACHINE_SIZE));

                    put_bits(_buffer, reference, CHANGE_MACHINE_TYPE, TYPE_SIZE);
                    put_bits(_buffer, reference, operation._machine_id, MACHINE_SIZE);
                  });
  });

  // current operation
  _current_operation = std::make_unique<ChangePool>(program.begin()->_pool);
}

std::unique_ptr<Operation> ProductionOrder::get_operation(uint8_t index) const {
  Reference reference{HEADER_SIZE, 0};
  uint8_t operation_type = get_bits(_buffer, reference, TYPE_SIZE);

  reference += TYPE_SIZE;
  while (index > 0) {
    switch (operation_type) {
      case CHANGE_POOL_TYPE: {
        reference += POOL_SIZE;
        break;
      }
      case CHANGE_MACHINE_TYPE: {
        reference += MACHINE_SIZE;
        break;
      }
      default:
        assert(false);
    }
    operation_type = get_bits(_buffer, reference, TYPE_SIZE);
    reference += TYPE_SIZE;
    --index;
  }
  switch (operation_type) {
    case CHANGE_POOL_TYPE: {
      return std::make_unique<ChangePool>(get_bits(_buffer, reference, POOL_SIZE));
    }
    case CHANGE_MACHINE_TYPE: {
      return std::make_unique<ChangeMachine>(get_bits(_buffer, reference, MACHINE_SIZE));
    }
  }
  assert(false);
  return {};
}

std::unique_ptr<Operation> ProductionOrder::last_operation() const {
  return get_operation(operation_number() - 1);
}

void ProductionOrder::update_operation(uint8_t index) {
  _current_operation = get_operation(index);
}

void ProductionOrder::next() {
  if (_buffer[INDEX_POSITION] + 1 < _buffer[OPERATION_NUMBER_POSITION]) {
    update_operation(++_buffer[INDEX_POSITION]);
  } else {
    _current_operation.reset(nullptr);
    _is_finish = true;
  }
}

std::unique_ptr<Operation> ProductionOrder::previous_operation() const {
  return get_operation(_buffer[INDEX_POSITION] - 1);
}

std::string ProductionOrder::to_string() const {
  std::string str = "[ ";
  uint16_t ID = (_buffer[ID_POSITION] << 8) + _buffer[ID_POSITION + 1];

  str += std::to_string(ID) + " ][ ";
  for (uint8_t i = 0; i < _buffer[OPERATION_NUMBER_POSITION]; ++i) {
    if (_buffer[INDEX_POSITION] == i) {
      str += "<";
    }
    str += get_operation(i)->to_string();
    if (_buffer[INDEX_POSITION] == i) {
      str += ">";
    }
    str += " ";
  }
  str += "]";
  return str;
}

} // namespace artis::factory