"""
Cython implementation of columns grouping for finite difference Jacobian
estimation. Used by ._numdiff.group_columns.
"""

cimport cython

import numpy as np

cimport numpy as np

np.import_array()


@cython.boundscheck(False)
@cython.wraparound(False)
def group_dense(int m, int n, const int[:, :] A):
    cdef const int [:, :] B = A.T  # Transposed view for convenience.

    cdef int [:] groups = np.full(n, -1, dtype=np.int32)
    cdef int current_group = 0

    cdef int i, j, k

    union = np.empty(m, dtype=np.int32)
    cdef int [:] union_v = union

    # Loop through all the columns.
    for i in range(n):
        if groups[i] >= 0:  # A group was already assigned.
            continue

        groups[i] = current_group
        all_grouped = True

        union_v[:] = B[i]  # Here we store the union of grouped columns.

        for j in range(groups.shape[0]):
            if groups[j] < 0:
                all_grouped = False
            else:
                continue

            # Determine if j-th column intersects with the union.
            intersect = False
            for k in range(m):
                if union_v[k] > 0 and B[j, k] > 0:
                    intersect = True
                    break

            # If not, add it to the union and assign the group to it.
            if not intersect:
                union += B[j]
                groups[j] = current_group

        if all_grouped:
            break

        current_group += 1

    return groups.base


@cython.wraparound(False)
def group_sparse(int m, int n, const int[:] indices, const int[:] indptr):
    cdef int [:] groups = np.full(n, -1, dtype=np.int32)
    cdef int current_group = 0

    cdef int i, j, k

    union = np.empty(m, dtype=np.int32)
    cdef int [:] union_v = union

    for i in range(n):
        if groups[i] >= 0:
            continue

        groups[i] = current_group
        all_grouped = True

        union.fill(0)
        for k in range(indptr[i], indptr[i + 1]):
            union_v[indices[k]] = 1

        for j in range(groups.shape[0]):
            if groups[j] < 0:
                all_grouped = False
            else:
                continue

            intersect = False
            for k in range(indptr[j], indptr[j + 1]):
                if union_v[indices[k]] == 1:
                    intersect = True
                    break
            if not intersect:
                for k in range(indptr[j], indptr[j + 1]):
                    union_v[indices[k]] = 1
                groups[j] = current_group

        if all_grouped:
            break

        current_group += 1

    return groups.base
