# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/environments/10_calculation_functions.ipynb.

# %% auto 0
__all__ = ['eoq_qr_calculations_single_action', 'eoq_qr_calculations_qr_action', 'nv_calculations', 'get_fixed_ordering_cost']

# %% ../../nbs/environments/10_calculation_functions.ipynb 4
# General libraries:
import numpy as np

# %% ../../nbs/environments/10_calculation_functions.ipynb 7
def eoq_qr_calculations_single_action(self, action):

    # TODO Think about naming of the function

    fixed_ordering_cost_step = get_fixed_ordering_cost(self, action, positive_only = True)

    # todo: add variable ordering cost:
        # variable cost positive order ...
        # variable cost negative order would build on overage cost

    if self.use_order_pipeline:
        orders_arriving = self.order_pipeline[:,-1]
            # Move elements one slot to the right in order_pipeline
        self.order_pipeline[:, 1:] = self.order_pipeline[:, :-1]
        self.order_pipeline[:, 0] = action
    else:
        orders_arriving = action

    self.inventory += orders_arriving # first, the order will be added to inventory (meaning lead-time is 0)

    self.inventory = np.maximum(self.inventory, 0) # inventory cannot be negative

    self.inventory = np.minimum(self.inventory, self.inventory_cap) # inventory cannot be higher than inventory_cap

    self.inventory -= self.demand[self.period, :] # Then demand is subtracted from inventory

    outages = np.where(self.inventory < 0, np.abs(self.inventory), 0)
    penalty_cost_step = self.underage_cost * outages.sum()

    self.inventory = np.maximum(self.inventory, 0) # inventory cannot be negative
    holding_cost_step = self.holding_cost * self.inventory

    total_cost_step = fixed_ordering_cost_step + penalty_cost_step + holding_cost_step
    reward = - total_cost_step.sum() # maximize negative cost (sum over all products)

    info = dict()

    self.period += 1

    return reward, info

# %% ../../nbs/environments/10_calculation_functions.ipynb 8
def eoq_qr_calculations_qr_action(self, action):

    # TODO Think about naming of the function
    
    r = action[0]
    q = action[1]

    order = np.where(self.inventory <= r, q, 0)

    fixed_ordering_cost_step = get_fixed_ordering_cost(self, order, positive_only = True)

    if self.use_order_pipeline:
        orders_arriving = self.order_pipeline[:,-1].copy()
            # Move elements one slot to the right in order_pipeline
        self.order_pipeline[:, 1:] = self.order_pipeline[:, :-1]
        self.order_pipeline[:, 0] = order
    else:
        orders_arriving = order

    # todo: add variable ordering cost:
        # variable cost positive order ...
        # variable cost negative order would build on overage cost

    self.inventory += orders_arriving # first, the order will be added to inventory (meaning lead-time is 0)

    self.inventory = np.maximum(self.inventory, 0) # inventory cannot be negative

    self.inventory = np.minimum(self.inventory, self.inventory_cap) # inventory cannot be higher than inventory_cap

    self.inventory -= self.demand[self.period, :] # Then demand is subtracted from inventory

    outages = np.where(self.inventory < 0, np.abs(self.inventory), 0)
    penalty_cost_step = self.underage_cost * outages.sum()

    self.inventory = np.maximum(self.inventory, 0) # inventory cannot be negative
    holding_cost_step = self.holding_cost * self.inventory

    total_cost_step = fixed_ordering_cost_step + penalty_cost_step + holding_cost_step
    reward = - total_cost_step.sum() # maximize negative cost (sum over all products)

    info = dict()

    self.period += 1

    return reward, info

# %% ../../nbs/environments/10_calculation_functions.ipynb 9
def nv_calculations(self, action):

    demand = self.demand[self.period, :]
    remaining_demand = demand.copy() - action

    cost = np.zeros(self.num_products)

    cost[remaining_demand > 0] = self.underage_cost * remaining_demand[remaining_demand > 0]
    cost[remaining_demand < 0] = self.overage_cost * np.abs(remaining_demand[remaining_demand < 0])

    reward = -cost.sum()
    info = dict(
        demand = demand,
        cost = cost
    )

    self.period += 1

    return reward, info

# %% ../../nbs/environments/10_calculation_functions.ipynb 11
def get_fixed_ordering_cost(self, action, positive_only = True):
    """ 
    Returns the fixed ordering cost for the period.

    Parameters
    ----------
    action : array
        The action taken in the period.
    positive_only : bool
        If True, only positive orders will be charged the fixed ordering cost. Otherwise, all non-zero orders will be charged the fixed ordering cost.

    Returns
    -------
    fixed_ordering_cost : array
        The fixed ordering cost for the period per product.
    
    """

    if positive_only:
        positive_order = np.where(action > 0, 1, 0)
    else:
        # nonzero = 1, zero = 0
        positive_order = np.where(action != 0, 1, 0)
    return self.fixed_ordering_cost * positive_order
