!> @file
!!   Basic routines for converting between CheSS and NTPoly.
!! @author
!!   Copyright (C) 2016 CheSS developers
!!
!!   This file is part of CheSS.
!!   
!!   CheSS is free software: you can redistribute it and/or modify
!!   it under the terms of the GNU Lesser General Public License as published by
!!   the Free Software Foundation, either version 3 of the License, or
!!   (at your option) any later version.
!!   
!!   CheSS is distributed in the hope that it will be useful,
!!   but WITHOUT ANY WARRANTY; without even the implied warranty of
!!   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
!!   GNU Lesser General Public License for more details.
!!   
!!   You should have received a copy of the GNU Lesser General Public License
!!   along with CheSS.  If not, see <http://www.gnu.org/licenses/>.

!> Convert a CheSS matrix type to NTPoly type
subroutine chess_to_ntpoly(chess_mat, chess_val, ntpolymat)
  use futile
  use psmatrixmodule, only : matrix_ps, fillmatrixfromtripletlist, &
     constructemptymatrix
  use tripletlistmodule, only : tripletlist_r, constructtripletlist, &
     destructtripletlist, appendtotripletList
  use tripletmodule, only : triplet_r
  use sparsematrix_base
  use sparsematrix_init, only : sparsebigdft_to_ccs
  use sparsematrix_timing
  !> Input CheSS sparse matrix type
  type(sparse_matrix), intent(in):: chess_mat
  !> Input CheSS matrix of data.
  type(matrices), intent(in) :: chess_val
  !> The output matrix.
  type(matrix_ps), intent(inout) :: ntpolymat
  ! CCS Matrix
  integer, dimension(:), allocatable :: row_ind, col_ptr
  type(tripletlist_r) :: triplet_list
  type(triplet_r) :: trip
  integer :: ii, jj
  integer :: inner_end
  integer :: iptr

  call f_routine(id='chess_to_ntpoly')
  call f_timing(TCAT_NTPOLY_CTN, 'ON')

  ! Convert to a CCS Matrix
  col_ptr = f_malloc(chess_mat%nfvctr, id='col_ptr')
  row_ind = f_malloc(chess_mat%nvctr, id='row_ind')
  call sparsebigdft_to_ccs(chess_mat%nfvctr, chess_mat%nvctr, chess_mat%nseg, &
                           chess_mat%keyg, row_ind, col_ptr)

  ! Initialize the empty matrix
  call constructemptymatrix(ntpolymat, chess_mat%nfvctr)

  ! We will build the NTPoly matrix from the triplet list.
  call constructtripletlist(triplet_list)

  ! Loop over columns of the CheSS matrix that match the NTPoly columns
  iptr = col_ptr(ntpolymat%start_column)
  do ii = ntpolymat%start_column, ntpolymat%end_column - 1
    ! This early exit condition is needed because of NTPoly's padding.
    if (ii .gt. chess_mat%nfvctr) then
      exit
    end if
    ! This check is because chess's CSR datastructure doesn't have the
    ! extra column index showing the total length.
    if (ii .eq. chess_mat%nfvctr) then
      inner_end = chess_mat%nvctr
    else
      inner_end = col_ptr(ii+1)-1
    end if

    ! Loop over values in a row.
    trip%index_column = ii
    do jj = col_ptr(ii), inner_end
      trip%index_row = row_ind(iptr)
      trip%point_value = chess_val%matrix_compr(iptr)
      if (trip%index_row .ge. ntpolymat%start_row .and. &
          trip%index_row .lt. ntpolymat%end_row) then
        call appendtotripletList(triplet_list, trip)
      end if
      iptr = iptr + 1
    end do

  end do

  ! Prepartitioned set to true since we have properly accounted for
  ! the location of everything.
  call fillmatrixfromtripletlist(ntpolymat, triplet_list, &
                                 prepartitioned_in=.TRUE.)

  ! Cleanup
  call destructtripletlist(triplet_list)
  call f_free(col_ptr)
  call f_free(row_ind)

  call f_timing(TCAT_NTPOLY_CTN, 'OFF')
  call f_release_routine()

end subroutine chess_to_ntpoly

!> Convert an ntpoly matrix to CheSS.
subroutine ntpoly_to_chess(ntpolymat, chess_mat, chess_val)
  use futile
  use matrixconversionmodule, only : snapmatrixtosparsitypattern
  use psmatrixmodule, only : matrix_ps, gathermatrixtoprocess, copymatrix, &
     destructmatrix
  use smatrixmodule, only : matrix_lsr, destructmatrix
  use sparsematrix_base
  use sparsematrix_timing
  !> The input matrix to convert.
  type(matrix_ps), intent(inout) :: ntpolymat
  !> Output sparse matrix.
  type(sparse_matrix), intent(inout):: chess_mat
  !> Output matrix of values.
  type(matrices), intent(inout) :: chess_val
  ! Local variables
  type(matrix_ps) :: pattern, filtered
  type(matrix_lsr) :: ntlocal
  integer :: nnz

  call f_routine(id='ntpoly_to_chess')
  call f_timing(TCAT_NTPOLY_NTC, 'ON')

  ! Build the sparsity pattern.
  call chess_to_ntpoly(chess_mat, chess_val, pattern)
  call copymatrix(ntpolymat, filtered)

  ! Snap to the sparsity pattern.
  call snapmatrixtosparsitypattern(filtered, pattern)

  ! Gather NTPoly matrix to each process.
  call gathermatrixtoprocess(filtered, ntlocal)

  ! Copy the values.
  nnz = size(ntlocal%values)
  chess_val%matrix_compr(:nnz) = ntlocal%values(:)

  ! Cleanup
  call destructmatrix(ntlocal)
  call destructmatrix(pattern)
  call destructmatrix(filtered)

  call f_timing(TCAT_NTPOLY_NTC, 'OFF')
  call f_release_routine()

end subroutine ntpoly_to_chess
