module table_m
    !! Implements a table with contiguously stored rows.

use constants_m
use vector_m

implicit none

private
public :: itable_t
public :: itbl_init

type itable_t
    integer :: num_rows = 0 !Must be > 0
    type(ivector_t) :: buffer
    integer, dimension(:), allocatable :: row_indx

    contains
        procedure :: delete   => itbl_delete
        procedure :: clear    => itbl_clear
        procedure :: append   => itbl_append
        procedure :: set_val  => itbl_set_val
        procedure :: is_in    => itbl_is_in
        procedure :: get_val  => itbl_get_val
        procedure :: get_row  => itbl_get_row
        procedure :: shrink_to_fit  => itbl_shrink_to_fit
        procedure :: print    => itbl_print
end type itable_t

contains

!******************************************************************************

subroutine itbl_init(this, num_rows, ierr)
    !! Creates an empty *itable_t* with *num_rows* rows and all rows having
    !! zero elements.

    type(itable_t), intent(in out) :: this
    integer, intent(in)             :: num_rows
        !! Must be > 0
    integer, intent(out), optional :: ierr
    integer :: istat

    if (present(ierr)) ierr = 0

    !Set initial size guess to be num_rows
    call ivector_init(this%buffer, num_rows)

    allocate(this%row_indx(num_rows+1), stat=istat)
    if (istat /= 0) then
        write(*, *) 'error: allocation failure for this%row_indx of size', num_rows
        if (present(ierr)) ierr = 1
        return
    end if

    !Set the number of rows
    this%num_rows = num_rows

    !Empty table: All rows have zero elements
    this%row_indx = 1

    end subroutine

!******************************************************************************

subroutine itbl_delete(this)
    !! Deletes an *itable_t*. No access is allowed to this object after this call.

    class(itable_t), intent(in out) :: this

    call this%buffer%delete()
    if (allocated(this%row_indx)) deallocate(this%row_indx)
    this%num_rows = 0

    end subroutine

!******************************************************************************

subroutine itbl_clear(this)
    !! Clears all rows. Does not deallocate memory.

    class(itable_t), intent(in out) :: this

    call this%buffer%clear()
    this%row_indx = 1

    end subroutine

!******************************************************************************

subroutine itbl_append(this, irow, val)
    !! Appends an element `val` to row *irow*.

    class(itable_t), intent(in out) :: this
    integer, intent(in) :: irow
    integer, intent(in) :: val

    call this%buffer%append(val)

    this%row_indx(irow+1:) = this%row_indx(irow+1:) + 1

    end subroutine

!******************************************************************************

subroutine itbl_set_val(this, irow, j, val)
    !! Sets the value of the *j*th element of row *irow*.

    class(itable_t), intent(in out) :: this
    integer, intent(in) :: irow
    integer, intent(in) :: j
    integer, intent(in) :: val
    integer :: k, n

    n = this%row_indx(irow+1) - this%row_indx(irow)
    if (j > n) then
        write(*,*) 'error: out-of-bounds index ', j
        write(*,*) 'for row length', n
        stop
    else
        k = this%row_indx(irow) + j - 1
        call this%buffer%set_val(k, val)
    end if

    end subroutine

!******************************************************************************

function itbl_is_in(this, irow, val) result(res)
    !! Returns .true. if *val* is in row *irow*, .false. otherwise.

    class(itable_t), intent(in) :: this
    integer, intent(in) :: irow
    integer, intent(in) :: val
    logical :: res
    integer :: i

    res = .false.

    do i = this%row_indx(irow), this%row_indx(irow+1)-1
        if (this%buffer%get_val(i) == val) then
            res = .true.
            exit
        end if
    end do
    
    end function

!******************************************************************************

function itbl_get_val(this, irow, j) result(res)
    !! Returns the *j*th element of row *irow*

    class(itable_t), intent(in) :: this
    integer, intent(in) :: irow
    integer, intent(in) :: j
    integer :: res
    integer :: k, n

    n = this%row_indx(irow+1) - this%row_indx(irow)
    if (j > n) then
        write(*,*) 'error: out-of-bounds index ', j
        write(*,*) 'for row length', n
        stop
    else
        k = this%row_indx(irow) + j - 1
        res = this%buffer%get_val(k)
    end if
    
    end function

!******************************************************************************

subroutine itbl_get_row(this, irow, res)
    !! Returns a pointer to the row data of *irow*. No bounds checking is performed.

    class(itable_t), target, intent(in) :: this
    integer, intent(in) :: irow
    integer, dimension(:), pointer, intent(out) :: res
    integer :: ibeg, iend

    res => null()
    ibeg = this%row_indx(irow)
    iend = this%row_indx(irow+1) - 1

    call this%buffer%get_data(res, ibeg, iend)

    end subroutine

!******************************************************************************

subroutine itbl_shrink_to_fit(this, ierr)
    !! Releases additional memory to fit underlying data.

    class(itable_t), intent(in out) :: this
    integer, intent(out), optional  :: ierr

    if (present(ierr)) then
        call this%buffer%shrink_to_fit(ierr)
    else
        call this%buffer%shrink_to_fit()
    end if

    end subroutine

!******************************************************************************

subroutine itbl_print(this)
    !! Prints an *itable_t*.

    class(itable_t), intent(in) :: this
    integer :: i, j

    do i = 1, this%num_rows
        write(*,'(i0,": ",2x)', advance='no') i
        do j = this%row_indx(i), this%row_indx(i+1)-1
            write(*, '(i0,2x)', advance='no') this%buffer%get_val(j)
        end do
        write(*,*)
    end do
    
    end subroutine

!******************************************************************************

end module table_m
