import scipy.ndimage

from veros import veros_routine, logger
from veros.core import utilities
from veros.core.operators import numpy as npx

# fall back to vanilla NumPy for some operations
import numpy as onp


def _compute_isleperim(kmt, enable_cyclic_x):
    # TODO: remove this check after jax#6907 has landed
    if enable_cyclic_x:
        kmt = utilities.enforce_boundaries(kmt, enable_cyclic_x)

    kmt = onp.asarray(kmt)

    structure = onp.ones((3, 3))  # merge diagonally connected land masses

    # find all land masses
    labelled, _ = scipy.ndimage.label(kmt == 0, structure=structure)

    # find and set perimeter
    land_masses = labelled > 0
    inner = scipy.ndimage.binary_dilation(land_masses, structure=structure)

    perimeter = onp.logical_xor(inner, land_masses)
    labelled[perimeter] = -1

    # match wrapping periodic land masses
    if enable_cyclic_x:
        west_slice = onp.array(labelled[2])
        east_slice = onp.array(labelled[-2])

        for west_label in onp.unique(west_slice[west_slice > 0]):
            east_labels = onp.unique(east_slice[west_slice == west_label])
            east_labels = east_labels[~onp.isin(east_labels, [west_label, -1])]
            if not east_labels.size:
                # already labelled correctly
                continue
            assert len(onp.unique(east_labels)) == 1, (west_label, east_labels)
            labelled[labelled == east_labels[0]] = west_label

    # TODO: remove this check after jax#6907 has landed
    if enable_cyclic_x:
        labelled = utilities.enforce_boundaries(labelled, enable_cyclic_x)

    labelled = onp.asarray(labelled)

    # label landmasses in a way that is consistent with pyom
    labels = onp.unique(labelled[labelled > 0])

    label_idx = {}
    for label in labels:
        # find index of first island cell, scanning west to east, north to south
        label_idx[label] = onp.argmax(labelled[:, ::-1].T == label)

    sorted_labels = list(sorted(labels, key=lambda i: label_idx[i]))

    # ensure labels are numbered consecutively
    relabelled = onp.array(labelled)
    for new_label, label in enumerate(sorted_labels, 1):
        if label == new_label:
            continue
        relabelled[labelled == label] = new_label

    return npx.asarray(relabelled)


@veros_routine(dist_safe=False, local_variables=("kbot", "land_map"))
def isleperim(state):
    vs = state.variables
    settings = state.settings

    logger.debug(" Determining number of land masses")
    vs.land_map = _compute_isleperim(vs.kbot, settings.enable_cyclic_x)

    if vs.land_map.size < 10_000:
        logger.debug(_ascii_map(vs.land_map))


def _ascii_map(boundary_map):
    def _get_char(c):
        if c == 0:
            return "."
        if c < 0:
            return "#"
        return str(c % 10)

    boundary_map = onp.array(boundary_map)
    nx, ny = boundary_map.shape

    map_string = ""
    linewidth = 100
    iremain = nx
    istart = 0
    map_string += "\n"
    map_string += " " * (5 + min(linewidth, nx) // 2 - 13) + "Land mass and perimeter"
    map_string += "\n"
    for _ in range(1, nx // linewidth + 2):
        iline = min(iremain, linewidth)
        iremain = iremain - iline
        if iline > 0:
            map_string += "\n"
            map_string += "".join([f"{istart + i + 1 - 2:5d}" for i in range(1, iline + 1, 5)])
            map_string += "\n"
            for j in range(ny - 1, -1, -1):
                map_string += f"{j:3d} "
                map_string += "".join([_get_char(boundary_map[istart + i - 2, j]) for i in range(2, iline + 2)])
                map_string += "\n"
            map_string += "".join([f"{istart + i + 1 - 2:5d}" for i in range(1, iline + 1, 5)])
            map_string += "\n"
            istart = istart + iline
    map_string += "\n"

    return map_string
