/**
 * @file cart.cpp
 * @author Jan Holáň, xholan11 (xholan11@vutbr.cz)
 * @brief translated with gcc version 12.2.1
 * @version 0.1
 * @date 2023-05-01
 * 
 * @copyright Copyright (c) 2023
 * 
 */

#include "WarehousePickingOpt.h"
#include "bin_packing_solver.h"

// THIS IS THE BASE SETUP
Cart::Cart()
{
    // init the shelves
    CartShelf default_shelf(90.0, 50.0, 45.0);
    // init defaultly three shelves of same dimensions
    this->cart_shelves_vect = {default_shelf, default_shelf, default_shelf};

    // free space counted as 9 boxes times volume of one box - 29 400 cm3
    // FIXME ... should be computed somehow else
    this->cart_free_space = 9 * 28*29*45;
}

Cart::Cart(vector<CartShelf> cart_shelf_vect)
{
    // load up the cart shelves
    this->cart_shelves_vect = cart_shelf_vect;
    // free space counted as 9 boxes times volume of one box - 29 400 cm3
    // FIXME ... should be computed somehow else
    this->cart_free_space = 9 * 28*29*45;
}

Cart::~Cart() {}

void Cart::addOItemWithQty(OItem oitem, int qty)
{
    // when duplicate found, return
    for (auto &item : this->cartoitem_vect)
        if (item.getOItem().Customer == oitem.Customer && item.getOItem().Product == oitem.Product)
        {
            item.addToRealQty(qty);
            return;
        }

    // the oitem hasn't been registered yet, add it
    CartOItem coi(oitem);
    coi.addToRealQty(qty);
    this->cartoitem_vect.push_back(coi);
}

bool Cart::loadingCustomer_old(Customer &customer)
{
    //* FIRST VERSION OF LOADING ALGORITHM - ONE ITEMIZED BOXES AREN't TRIED TO PUT TOGETHER

    vector<Box> box_vect = customer.getBoxesDecomp();
    int num_of_boxes = box_vect.size();

    // if the completely free boxes number is less then number of full boxes from customer then return false
    if (num_of_boxes > this->free_boxes)
        return false;

    this->cart_free_space -= num_of_boxes * Box(25.0, 28.0, 42.0, 0, "").getCapacity();
    this->free_boxes -= num_of_boxes;
    this->cart_shelves_vect[0].getBoxes()->full_box_vect.insert(this->cart_shelves_vect[0].getBoxes()->full_box_vect.end(), box_vect.begin(), box_vect.end());
    // this->items_decomp_into_boxes.one_item_box_vect.insert(this->items_decomp_into_boxes.one_item_box_vect.end(), curr_cust_decomp.one_item_box_vect.begin(), curr_cust_decomp.one_item_box_vect.end());

    // insert all items to the cart vector of them
    this->item_vect.insert(item_vect.end(), customer.item_vect.begin(), customer.item_vect.end());
    this->customer_vect.push_back(customer);
    return true;
}

bool Cart::loadingCustomer_new(SolverConfiguration &sc, Customer &customer, bool can_be_partly_loaded, vector<Box> *non_loaded_boxes)
{
    vector<Box> current_customer_boxes = customer.getBoxesDecomp();
    bool is_one_pieced = false;
    if (current_customer_boxes.size() == 1 && current_customer_boxes[0].getItemsInTheBox().size() == 1 &&
        current_customer_boxes[0].getItemsInTheBox().begin()->second.getCountOfUnits() == 1)
    {
        // the customer is one-pieced, so it can be load to either separate box or to box with such customer in it
        is_one_pieced = true;
        for (auto &shelf : this->cart_shelves_vect)
        {
            for (auto &one_item_box : shelf.getBoxes()->one_item_box_vect)
            {
                // select the proper algorithm according to how have been items packed to boxes (bins)
                bool result;
                switch (sc.getBPAlgorithm())
                {
                case BPAlgorithms::PYLIB_3D_FFDSEnhance_ORDER_ITEMS_SPLIT_MULTI_BOX_TYPE:
                case BPAlgorithms::PYLIB_3D_ORDER_ITEMS_SPLIT_MULTI_BOX_TYPE_COUNT_DEFINED:
                case BPAlgorithms::PYLIB_3D_ORDER_ITEMS_SPLIT_ONE_BOX_TYPE:
                    result = bp_solver::addBoxItemToExisting_oneItemized_Box_3D(one_item_box, current_customer_boxes[0].getItemsInTheBox().begin()->second);
                    break;
                case BPAlgorithms::FFD_1D_ORDER_ITEMS_NO_SPLIT:
                case BPAlgorithms::FFD_1D_ORDER_ITEMS_SPLIT_ONE_BOX_TYPE:
                case BPAlgorithms::FFDS_1D_ORDER_ITEMS_SPLIT_MULTI_BOX_TYPE:
                default:
                    result = bp_solver::addBoxItemToExisting_oneItemized_Box_1D(one_item_box, current_customer_boxes[0].getItemsInTheBox().begin()->second);
                    break;
                }

                if (result)
                {
                    // the boxitem can fit the currently taken one-item box, so add it for real
                    one_item_box.addItem(current_customer_boxes[0].getItemsInTheBox().begin()->second);

                    // this->item_vect.push_back(customer.item_vect[0]);
                    addOItemWithQty(customer.item_vect[0], 1);

                    // add customer
                    this->customer_vect.push_back(customer);
                    // lower the free space
                    this->cart_free_space -= customer.getTotalVolume();
                    return true;
                }
            }
        }
    }

    // otherwise try to load the boxes normally
    if (bp_solver::bpSolver_1D_fitBoxesToShelvesInCart(current_customer_boxes, &this->cart_shelves_vect, is_one_pieced))
    {
        // the boxes has been handled, so change the properties of the cart properly
        for (auto box : current_customer_boxes)
        {
            for (auto item : box.getItemsInTheBox())
            {
                // load all order_items of items in boxes - for path computation
                // this->item_vect.push_back(item.second.order_item);
                addOItemWithQty(item.second.order_item, item.second.getCountOfUnits());
            }
            // lower the free space
            this->cart_free_space -= box.getUsedCapacity();
        }
        // add customer
        this->customer_vect.push_back(customer);

        return true;
    }
    else
    {
        // the boxes cannot be handled all together, try to load them partly, it this option is allowed
        if (can_be_partly_loaded && non_loaded_boxes != NULL)
        {
            vector<Box> loaded_box_ptr_vect;
            if (!bp_solver::bpSolver_1D_partlyLoadBoxesToShelvesInCart(current_customer_boxes, &this->cart_shelves_vect, non_loaded_boxes, &loaded_box_ptr_vect))
            {
                // if any box can be loaded then add three of them by force (one per each shelf)
                // this can be done thus when partly loading option is enabled, then it means that the cart is empty
                for (size_t i = 0; i < (current_customer_boxes.size() <= this->cart_shelves_vect.size() ? current_customer_boxes.size() : this->cart_shelves_vect.size()); i++)
                {
                    // we are sure here that each box belong as full box
                    this->cart_shelves_vect[i].addBox_fullItemized(*current_customer_boxes.begin().base());
                    loaded_box_ptr_vect.push_back(*current_customer_boxes.begin().base());
                    current_customer_boxes.erase(current_customer_boxes.begin());
                }
                *non_loaded_boxes = current_customer_boxes;
                perror("WARNING: 3 boxes have been loaded to cart by force!\n");
            }

            // change values for the cart according to the loaded part of the customer
            for (auto box : loaded_box_ptr_vect)
            {
                for (auto item : box.getItemsInTheBox())
                {
                    // load all order_items of items in boxes - for path computation
                    // this->item_vect.push_back(item.second.order_item);
                    addOItemWithQty(item.second.order_item, item.second.getCountOfUnits());
                }
                // lower the free space
                this->cart_free_space -= box.getUsedCapacity();
            }
            this->customer_vect.push_back(customer);
        }
        // return false, because the customer hasn't been fully loaded
        return false;
    }
}

bool Cart::loadCustomer(SolverConfiguration &sc, Customer &customer, bool can_be_partly_loaded, vector<Box> *non_loaded_boxes)
{
    // if (this->customer_vect.size() >= 8)
    //     return false;
    return loadingCustomer_new(sc, customer, can_be_partly_loaded, non_loaded_boxes);
}

float Cart::getCartFreeSpace()
{
    return this->cart_free_space;
}

vector<Customer> Cart::getCustomerVector()
{
    return this->customer_vect;
}

vector<OItem> Cart::getItemVector()
{
    return this->item_vect;
}

vector<CartShelf> Cart::getCartShelvesVector()
{
    return this->cart_shelves_vect;
}

void Cart::setCartOItemVector(vector<CartOItem> _cartoitem_vect)
{
    this->cartoitem_vect = _cartoitem_vect;
}

void Cart::addToEstimatedWalkedDistance(int dist)
{
    this->estimated_walked_distance += dist;
}

int Cart::getEstimatedWalkedDistance()
{
    return this->estimated_walked_distance; // this->estimated_walked_distance;
}

vector<CartOItem> Cart::getCartOItemVector()
{
    return this->cartoitem_vect;
}
