# distutils: language=c++
cimport cython
import numpy as np
cimport numpy as np

from libcpp.queue cimport priority_queue
from libcpp.pair cimport pair

ctypedef pair[float, int] step
ctypedef priority_queue[step] pp_t


@cython.boundscheck(False)
@cython.wraparound(False)
cdef hex_neighbors(const int i, const int j, int[:,:] out):
    cdef int ii, jj
    out[0][0], out[0][1] = i, j+1  # UU
    out[1][0], out[1][1] = i, j-1  # DD
    if i % 2 == 0:  # even rows
        out[2][0], out[2][1] = i+1, j    # UR
        out[3][0], out[3][1] = i+1, j-1  # DR
        out[4][0], out[4][1] = i-1, j-1  # DL
        out[5][0], out[5][1] = i-1, j    # UL
    else:  # odd rows
        out[2][0], out[2][1] = i+1, j+1  # UR
        out[3][0], out[3][1] = i+1, j    # DR
        out[4][0], out[4][1] = i-1, j    # DL
        out[5][0], out[5][1] = i-1, j+1  # UL

@cython.boundscheck(True)
@cython.wraparound(False)
@cython.cdivision(True)
cdef _trade_distance(int init_i, int init_j, float trade_range, const float[:, :] trade_impedance,
                     const float[:, :] trade_value, float[:, :] distance):

    cdef int width = trade_impedance.shape[0]
    cdef int height = trade_impedance.shape[1]
    cdef pp_t pp
    cdef step top
    cdef int i, j, ii, jj
    cdef int exp_i = -1, exp_j = -1
    cdef float exp_val = 0
    cdef float dist, dist_tmp
    cdef int[:, :] neighbors = np.zeros((6,2), dtype=np.int32)

    cdef char[:, :] visited = np.full((trade_impedance.shape[0], trade_impedance.shape[1]),
                                      False, dtype=np.int8)
    distance[init_i][init_j] = 0.0
    pp.push(step(0.0, init_j*width+init_i))

    while not pp.empty():
        top = pp.top()
        i = top.second % width
        j = top.second // width
        dist = -top.first
        pp.pop()

        if trade_value[i][j] > exp_val:
            exp_i, exp_j = i, j
            exp_val = trade_value[i][j]

        hex_neighbors(i, j, neighbors)
        for idx in range(6):
            ii = neighbors[idx][0]
            jj = neighbors[idx][1]
            if 0 <= ii < width and 0 <= jj < height and not visited[ii][jj]:
                visited[ii][jj] = True
                dist_tmp = dist + trade_impedance[ii][jj]

                if dist_tmp <= trade_range:
                    distance[ii][jj] = dist_tmp
                    pp.push(step(-dist_tmp, jj*width+ii))
    return exp_i, exp_j



def trade_distance(int i, int j, float trade_range, trade_impedance: np.ndarray, trade_value: np.ndarray):
    distance = np.full_like(trade_impedance, -1)
    exp = _trade_distance(i, j, trade_range, trade_impedance, trade_value, distance)
    return exp, distance


@cython.boundscheck(False)
@cython.wraparound(False)
@cython.cdivision(True)
cpdef update_export_partner(trade_range: np.ndarray, trade_distance: np.ndarray, trade_value: np.ndarray, export_partner: np.ndarray):
    cdef int width = trade_range.shape[0]
    cdef int height = trade_range.shape[1]
    cdef int exp_i, exp_j
    print(trade_range)

    cdef float[:, :] distance
    for i in range(width):
        for j in range(height):
            distance = np.full_like(trade_range, -1)
            (exp_i, exp_j) = _trade_distance(i, j, trade_range[i, j], trade_distance, trade_value, distance)
            export_partner[i, j] = [exp_i, exp_j]


@cython.boundscheck(True)
@cython.wraparound(False)
@cython.cdivision(True)
cpdef share_food(food_produced: np.ndarray, food_consumed: np.ndarray, food_stored: np.ndarray,
                 food_stored_capacity: np.ndarray, export_partner: np.ndarray,
                 spread_factor_neighbor: float):
    """
    If a province produces more food that it consumes *and* it's food storage is full, a portion of it's excess
    can be transferred to neighboring hex's provided that they don't also have excess production. Any leftover excess
    will get shipped to the export partner. 
    """
    cdef int width = food_produced.shape[0]
    cdef int height = food_produced.shape[1]
    cdef float[:, :] food_excess = food_produced - food_consumed
    cdef int ii, jj

    cdef int[:, :] neighbors = np.zeros((6,2), dtype=np.int32)

    food_stored_new = np.copy(food_stored)
    cdef float[:, :] food_stored_new_ = food_stored_new

    cdef float spread_amount
    cdef int spread_count

    for i in range(width):
        for j in range(height):
            if food_excess[i][j] <= 0 or food_stored[i][j] < food_stored_capacity[i][j]:
                continue
            hex_neighbors(i, j, neighbors)
            spread_amount_neighbor = spread_factor_neighbor * food_excess[i][j]
            spread_count = 0

            for idx in range(6):
                ii = neighbors[idx][0]
                jj = neighbors[idx][1]
                if not (0 <= ii < width and 0 <= jj < height):
                    continue
                if food_excess[ii][jj] < food_excess[i][j] and food_stored[ii][jj] < food_stored_capacity[ii][jj]:
                    food_stored_new_[ii][jj] += spread_amount_neighbor
                    food_stored_new_[i][j] -= spread_amount_neighbor
                    spread_count += 1
            spread_amount = food_excess[i][j] * (1 - spread_amount_neighbor*spread_count)
            try:
                food_stored_new_[export_partner[i][j][0], export_partner[i][j][1]] += spread_amount
            except IndexError:
                print(export_partner[i][j])
            food_stored_new_[i][j] -= spread_amount


    np.copyto(food_stored, food_stored_new)

