! CSIRO Open Source Software License Agreement (variation of the BSD / MIT License)
! Copyright (c) 2015, Commonwealth Scientific and Industrial Research Organisation
! (CSIRO) ABN 41 687 119 230.

module cable_array_utils_mod
  !! Utility procedures for working with arrays.
  use iso_fortran_env, only: int32
  implicit none
  private

  public array_offset
  public array_index
  public array_partition
  public array_eq

  interface array_eq
    module procedure array_eq_int32
  end interface

contains

  !> Calculate the memory offset corresponding to a given index in a multi-dimensional array.
  function array_offset(index, shape) result(offset)
    integer, intent(in) :: index(:) !! The multi-dimensional index for which to calculate the offset.
    integer, intent(in) :: shape(:) !! The shape of the multi-dimensional array.
    integer :: offset !! The calculated memory offset corresponding to the given index.
    integer :: i, scale
    offset = 1; scale = 1
    do i = 1, size(index)
      offset = offset + (index(i) - 1) * scale
      scale = scale * shape(i)
    end do
  end function

  !> Calculate the index corresponding to a given memory offset in a multi-dimensional array.
  subroutine array_index(offset_in, shape, index)
    integer, intent(in) :: offset_in !! The memory offset for which to calculate the multi-dimensional index.
    integer, intent(in) :: shape(:) !! The shape of the multi-dimensional array.
    integer, intent(inout) :: index(:) !! The calculated multi-dimensional index corresponding to the given offset.
    integer :: i, offset
    offset = offset_in
    do i = 1, size(shape)
      index(i) = mod(offset - 1, shape(i)) + 1
      offset = (offset - 1) / shape(i) + 1
    end do
  end subroutine

  subroutine array_partition(n, k, p, start, count)
    !* Compute start and count for the `p`'th partition of an array of size `n`
    ! where `p = 0, 1, ... , k - 1`.
    !
    ! For `k` partitions, an array of `n` elements can be partitioned into `r`
    ! partitions of length `q + 1`, and `k - r` partitions of length `q` where `q`
    ! and `r` are the quotient and remainder of `n` divided by `k` (i.e. `n = q *
    ! k + r`).  Note, we assume that the `r` partitions of length `q + 1` precede
    ! the `k - r` partitions of length `q` in the array.
    integer, intent(in) :: n !! The total number of elements in the array to be partitioned.
    integer, intent(in) :: k !! The total number of partitions.
    integer, intent(in) :: p !! The index of the partition for which to compute the start and count (0-based).
    integer, intent(out) :: start !! The starting index (1-based) of the `p`'th partition in the array.
    integer, intent(out) :: count !! The number of elements in the `p`'th partition.
    integer :: q, r

    q = n / k
    r = mod(n, k)

    if (p < r) then
      count = q + 1
      start = 1 + (q + 1) * p
    else
      count = q
      start = 1 + (q + 1) * r + q * (p - r)
    end if

  end subroutine array_partition

  !> Check if two integer arrays are equal.
  logical function array_eq_int32(a, b)
    integer(kind=int32), dimension(:), intent(in) :: a, b
    if (size(a) /= size(b)) then
      array_eq_int32 = .false.
    else
      array_eq_int32 = all(a == b)
    end if
  end function array_eq_int32

end module cable_array_utils_mod
