def max_smoothing(road: Road, maxh: int):
    """
    Local max smoothing method. Takes all the grains above a certain threshold and puts them onto
    neighbouring lower positions.
    :param road: Road class instance
    :param maxh: positive integer, threshold value, above this value all grains are removed
    :return:
    """
    # maxh = 7
    while max(road) > maxh:
        for i in range(road.size):
            if road[i] > maxh:
                for n in range(1, road.size):
                    right = (i + n) % road.size
                    left = (i - n) % road.size
                    rightr = road[right]
                    leftr = road[left]
                    if rightr < leftr and rightr < maxh:
                        road.add_grain(right)
                        road.remove_grain(i)
                        break
                    elif rightr > leftr and leftr < maxh:
                        road.add_grain(left)
                        road.remove_grain(i)
                        break
                    elif rightr == leftr and rightr < maxh:
                        uniran = np.random.uniform(0, 1)
                        if uniran >= 0.5:
                            road.add_grain(right)
                            road.remove_grain(i)
                            break
                        else:
                            road.add_grain(left)
                            road.remove_grain(i)
                            break
def general_slope_smoothing(road: Road, slope_inverse=3):
    """
    TODO DOES NOT WORK YET
    General slope smoothing function with an aimed slope of 1/slope_inverse for the heapes.
    I.e. if slope_inverse=3 the heaps should a an incline of 1/3.
    :param road: Road class instance
    :param slope_inverse: positive integer, define the slope as 1/slope_inverse
    :return:
    """
    if slope_inverse < 1:  # do nothing if the slope is not a positive integer
        return
    for i in range(0, road.size, slope_inverse):
        end_segm_slice_point = i + 2 * slope_inverse
        sum_over_next_segment = np.sum(road.piles[i:end_segm_slice_point])
        average_over_next_segment = sum_over_next_segment / (2 * slope_inverse)
        average_floor_int = int(average_over_next_segment)
        start_segm_height = road[i]
        end_segm_index = end_segm_slice_point - 1
        end_segm_height = road.piles[end_segm_index]
        road.piles[i:end_segm_slice_point] = average_floor_int

        rest_to_distribute = sum_over_next_segment - average_floor_int * 2 * slope_inverse
        if start_segm_height == end_segm_height:
            if rest_to_distribute > 0:
                road.add_random_irregularities(rest_to_distribute,
                                               (i, end_segm_slice_point))
        elif start_segm_height < end_segm_height:
            if rest_to_distribute == 0:
                road.remove_grain(start_segm_height)
                road.add_grain(end_segm_height)
            else:
                k = int(rest_to_distribute / 3)
                road.add_grains(
                    range(end_segm_slice_point - k, end_segm_slice_point),
                    [2] * k)
                rest_to_distribute -= 2 * k
                road.add_grains(
                    range(end_segm_slice_point - k - rest_to_distribute,
                          end_segm_slice_point - k), [1] * rest_to_distribute)
        elif start_segm_height > end_segm_height:
            if rest_to_distribute == 0:
                road.add_grain(start_segm_height)
                road.remove_grain(end_segm_height)
            else:
                k = int(rest_to_distribute / 3)
                road.add_grains(range(i, i + k), [2] * k)
                rest_to_distribute -= 2 * k
                road.add_grains(range(i + k, i + k + rest_to_distribute),
                                [1] * rest_to_distribute)
def random_wind_smoothing(road: Road, maxh: int):
    """
    Global max/wind smoothing function similar to wind_smoothing.
    The difference is that the grains are randomly distributed over all lowest positions with the lowest height
    and not to the first position in the road with the lowest height.
    :param road: Road class instance
    :param maxh: positive integer, threshold value, above this value all grains are removed
    :return:
    """
    while max(road) > maxh:
        for i in range(road.size):
            if road[i] > maxh:
                temp_minimum = min(road)
                temp_minimum_indices = np.where(road.piles == temp_minimum)
                chosen_index = np.random.choice(temp_minimum_indices[0])
                road.remove_grain(i)
                road.add_grain(chosen_index)
def wind_smoothing(road: Road, maxh: int):
    """
    Global max/wind smoothing function.
    Takes all the grains above a threshold maxh and puts them to the lowest heights (globally).
    This methods puts the grains to the first position with the lowest height.
    :param road: Road class instance
    :param maxh: positive integer, threshold value, above this value all grains are removed
    :return:
    """
    while max(road) > maxh:
        for i in range(road.size):
            if road[i] > maxh:
                temp_minimum = min(road)
                for j in range(road.size):
                    if road[j] == temp_minimum:
                        road.remove_grain(i)
                        road.add_grain(j)
                        break
def slope_smoothing(road: Road, slope=1):
    """
    Pairwise comparison of neighbouring elements in the road.
    If their height difference is bigger than one they grains from the bigger one are removed
    and put on the lower position.
    :param road: Road class
    :return:
    """
    for i in range(road.size):
        i2 = (i + 1) % road.size
        height1 = road[i]
        height2 = road[i2]
        diff = height1 - height2
        if diff > slope:
            road.add_grain(i2)
            road.remove_grain(i)
        elif diff < -slope:
            road.add_grain(i)
            road.remove_grain(i2)