!> @file
!!   Test of the eigenvalue calculation (using FOE) using the CCS or the BigDFT format
!! @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/>.


program driver_eigenvalues
  ! The following module are part of the sparsematrix library
  use sparsematrix_base
  use foe_base, only: foe_data, foe_data_deallocate, foe_data_get_real
  use foe_common, only: init_foe
  use sparsematrix_highlevel, only: sparse_matrix_and_matrices_init_from_file_ccs, &
                                    sparse_matrix_init_from_file_ccs, matrices_init, &
                                    matrices_get_values, matrices_set_values, &
                                    sparse_matrix_init_from_data_ccs, &
                                    ccs_data_from_sparse_matrix, ccs_matrix_write, &
                                    matrix_matrix_multiplication, matrix_fermi_operator_expansion, &
                                    trace_A, trace_AB, sparse_matrix_metadata_init_from_file, &
                                    sparse_matrix_and_matrices_init_from_file_bigdft
  use sparsematrix, only: write_matrix_compressed, transform_sparse_matrix, get_minmax_eigenvalues
  use sparsematrix_init, only: matrixindex_in_compressed, write_sparsematrix_info, &
                               get_number_of_electrons, distribute_on_tasks
  ! The following module is an auxiliary module for this test
  use utilities, only: get_ccs_data_from_file, calculate_error
  use futile
  use wrapper_MPI
  use wrapper_linalg
  use coeffs, only: get_coeffs_diagonalization, calculate_kernel_and_energy
  use fermi_level, only: eval_to_occ, SMEARING_DIST_ERF
  use highlevel_wrappers, only: calculate_eigenvalues, solve_eigensystem_lapack

  implicit none

  ! Variables
  type(sparse_matrix) :: smat_s, smat_h, smat_k
  type(matrices) :: mat_s, mat_h, mat_k, mat_ek
  type(matrices),dimension(1) :: mat_ovrlpminusonehalf
  type(sparse_matrix_metadata) :: smmd
  integer :: nfvctr, nvctr, ierr, iproc, nproc, nthread, ncharge, nfvctr_mult, nvctr_mult, scalapack_blocksize, icheck
  integer :: ispin, ihomo, imax, ntemp, npl_max, norbu, norbd, ii, info, norbp, isorb, norb, iorb
  integer :: iev_min, iev_max, iev, iev_minx, iev_maxx
  real(mp) :: fscale
  integer,dimension(:),pointer :: row_ind, col_ptr, row_ind_mult, col_ptr_mult
  real(mp),dimension(:),pointer :: kernel, overlap, overlap_large, evals, evals_check
  real(mp),dimension(:),allocatable :: charge, eval_min, eval_max, eval_all, eval_occup, occup
  real(mp),dimension(:,:),allocatable :: coeff
  real(mp) :: energy, tr_KS, tr_KS_check, ef, energy_fake, efermi, eTS
  type(foe_data) :: foe_obj, ice_obj
  real(mp) :: tr, fscale_lowerbound, fscale_upperbound
  type(dictionary),pointer :: dict_timing_info, options
  type(yaml_cl_parse) :: parser !< command line parser
  character(len=1024) :: metadata_file, overlap_file, hamiltonian_file, kernel_file, kernel_matmul_file
  character(len=1024) :: sparsity_format, matrix_format, kernel_method
  logical :: check_spectrum, do_cubic_check
  integer,parameter :: nthreshold = 10 !< number of checks with threshold
  real(mp),dimension(nthreshold),parameter :: threshold = (/ 1.e-1_mp, &
                                                             1.e-2_mp, &
                                                             1.e-3_mp, &
                                                             1.e-4_mp, &
                                                             1.e-5_mp, &
                                                             1.e-6_mp, &
                                                             1.e-7_mp, &
                                                             1.e-8_mp,&
                                                             1.e-9_mp,&
                                                             1.e-10_mp /) !< threshold for the relative errror

  external :: gather_timings
  !$ integer :: omp_get_max_threads

  ! Initialize flib
  call f_lib_initialize()

  ! MPI initialization; we have:
  ! iproc is the task ID
  ! nproc is the total number of tasks
  call mpiinit()
  iproc=mpirank()
  nproc=mpisize()

  call f_malloc_set_status(iproc=iproc)

  ! Initialize the sparsematrix error handling and timing.
  call sparsematrix_init_errors()
  call sparsematrix_initialize_timing_categories()


  if (iproc==0) then
      call yaml_new_document()
      !call print_logo()
  end if

  !Time initialization
  call f_timing_reset(filename='time.yaml',master=(iproc==0),verbose_mode=.false.)

  if (iproc==0) then
      call yaml_scalar('',hfill='~')
      call yaml_scalar('CHESS EIGENVALUES TEST DRIVER',hfill='~')
  end if

  if (iproc==0) then
      call yaml_map('Timestamp of the run',yaml_date_and_time_toa())
      call yaml_mapping_open('Parallel environment')
      call yaml_map('MPI tasks',nproc)
      nthread = 1
      !$ nthread = omp_get_max_threads()
      call yaml_map('OpenMP threads',nthread)
      call yaml_mapping_close()
  end if

  ! Read in the parameters for the run and print them.
  if (iproc==0) then
      parser=yaml_cl_parse_null()
      call commandline_options(parser)
      call yaml_cl_parse_cmd_line(parser,args=options)
      call yaml_cl_parse_free(parser)

      metadata_file = options//'metadata_file'
      overlap_file = options//'overlap_file'
      hamiltonian_file = options//'hamiltonian_file'
      kernel_file = options//'kernel_file'
      kernel_matmul_file = options//'kernel_matmul_file'
      sparsity_format = options//'sparsity_format'
      matrix_format = options//'matrix_format'
      kernel_method = options//'kernel_method'
      scalapack_blocksize = options//'scalapack_blocksize'
      check_spectrum = options//'check_spectrum'
      fscale_lowerbound = options//'fscale_lowerbound'
      fscale_upperbound = options//'fscale_upperbound'
      ntemp = options//'ntemp'
      ef = options//'ef'
      npl_max = options//'npl_max'
      do_cubic_check = options//'do_cubic_check'
      fscale = options//'fscale'
      iev_min = options//'iev_min'
      iev_max = options//'iev_max'
     
      call dict_free(options)

      call yaml_mapping_open('Input parameters')
      call yaml_map('Sparsity format',trim(sparsity_format))
      call yaml_map('Matrix format',trim(matrix_format))
      call yaml_map('Metadata file',trim(metadata_file))
      call yaml_map('Overlap matrix file',trim(overlap_file))
      call yaml_map('Hamiltonian matrix file',trim(hamiltonian_file))
      call yaml_map('Density kernel matrix file',trim(kernel_file))
      call yaml_map('Density kernel matrix multiplication file',trim(kernel_matmul_file))
      call yaml_map('Kernel method',trim(kernel_method))
      call yaml_map('Blocksize for ScaLAPACK',scalapack_blocksize)
      call yaml_map('Check the Hamiltonian spectrum',check_spectrum)
      call yaml_map('Lower bound for decay length',fscale_lowerbound)
      call yaml_map('Upper bound for decay length',fscale_upperbound)
      call yaml_map('Iterations with varying temperatures',ntemp)
      call yaml_map('Guess for Fermi energy',ef)
      call yaml_map('Maximal polynomial degree',npl_max)
      call yaml_map('Do a check with cubic scaling (Sca)LAPACK',do_cubic_check)
      call yaml_map('Minimal eigenvalue to be calculated',iev_min)
      call yaml_map('Maximal eigenvalue to be calculated',iev_max)
      call yaml_map('Decay length of the error function used to find the eigenvalue',fscale)
      call yaml_mapping_close()
  end if

  ! Send the input parameters to all MPI tasks
  call fmpi_bcast(sparsity_format)
  call fmpi_bcast(matrix_format)
  call fmpi_bcast(metadata_file)
  call fmpi_bcast(overlap_file)
  call fmpi_bcast(hamiltonian_file)
  call fmpi_bcast(kernel_file)
  call fmpi_bcast(kernel_matmul_file)
  call fmpi_bcast(kernel_method)
  call fmpi_bcast(scalapack_blocksize)
  call fmpi_bcast(kernel_matmul_file)
  call fmpi_bcast(fscale_lowerbound)
  call fmpi_bcast(fscale_upperbound)
  call fmpi_bcast(ntemp)
  call fmpi_bcast(ef)
  call fmpi_bcast(npl_max)
  call fmpi_bcast(iev_min)
  call fmpi_bcast(iev_max)
  call fmpi_bcast(fscale)
  ! Since there is no wrapper for logicals...
  if (iproc==0) then
      if (check_spectrum) then
          icheck = 1
      else
          icheck = 0
      end if
  end if
  call fmpi_bcast(icheck)
  if (icheck==1) then
      check_spectrum = .true.
  else
      check_spectrum = .false.
  end if
  if (iproc==0) then
      if (do_cubic_check) then
          icheck = 1
      else
          icheck = 0
      end if
  end if
  call fmpi_bcast(icheck)
  if (icheck==1) then
      do_cubic_check = .true.
  else
      do_cubic_check = .false.
  end if


  ! Calculate the eigenvalues
  call calculate_eigenvalues(iproc, nproc, matrix_format, metadata_file, &
       overlap_file, hamiltonian_file, kernel_file, kernel_matmul_file, &
       1, iev_min, iev_max, fscale, evals_out=evals)

  call fmpi_barrier()
  call f_timing_checkpoint(ctr_name='CALC',mpi_comm=mpiworld(),nproc=mpisize(), &
       gather_routine=gather_timings)

  if (do_cubic_check) then
      call solve_eigensystem_lapack(iproc, nproc, mpiworld(), 1, matrix_format, metadata_file, &
           overlap_file, hamiltonian_file, scalapack_blocksize, write_coeff=.false., write_eval=.false., &
           evals_out=evals_check)

      call f_timing_checkpoint(ctr_name='CHECK',mpi_comm=mpiworld(),nproc=mpisize(), &
           gather_routine=gather_timings)

      if (iproc==0) then
          call yaml_sequence_open('Comparing the eigenvalues')
          iev_minx = lbound(evals,1)
          iev_maxx = ubound(evals,1)
          do iev=iev_minx,iev_maxx
              call yaml_sequence(advance='no')
              call yaml_mapping_open(flow=.true.)
              call yaml_map('ID',iev)
              call yaml_map('CheSS',evals(iev),fmt='(es14.7)')
              call yaml_map('LAPACK',evals_check(iev),fmt='(es14.7)')
              call yaml_map('difference',evals(iev)-evals_check(iev),fmt='(es15.8)')
              call yaml_mapping_close()
          end do
          call yaml_sequence_close
      end if
      call f_free_ptr(evals_check)
  end if

  call f_free_ptr(evals)

  call fmpi_barrier()
  call f_timing_checkpoint(ctr_name='LAST',mpi_comm=mpiworld(),nproc=mpisize(), &
       gather_routine=gather_timings)

  call build_dict_info(dict_timing_info)
  call f_timing_stop(mpi_comm=mpi_comm_world,nproc=nproc,&
       gather_routine=gather_timings,dict_info=dict_timing_info)
  call dict_free(dict_timing_info)

  if (iproc==0) then
      call yaml_release_document()
  end if

  ! Finalize MPI
  call mpifinalize()

  ! Finalize flib
  call f_lib_finalize()

end program driver_eigenvalues


subroutine commandline_options(parser)
  use yaml_parse
  use dictionaries, only: dict_new,operator(.is.)
  implicit none
  type(yaml_cl_parse),intent(inout) :: parser

  call yaml_cl_parse_option(parser,'metadata_file','sparsematrix_metadata.dat',&
       'input file with the matrix metadata',&
       help_dict=dict_new('Usage' .is. &
       'Input file with the matrix metadata',&
       'Allowed values' .is. &
       'String'))

  call yaml_cl_parse_option(parser,'overlap_file','overlap_sparse.txt',&
       'input file with the overlap matrix',&
       help_dict=dict_new('Usage' .is. &
       'Input file with the overlap matrix',&
       'Allowed values' .is. &
       'String'))

  call yaml_cl_parse_option(parser,'hamiltonian_file','hamiltonian_sparse.txt',&
       'input file with the Hamiltonian matrix',&
       help_dict=dict_new('Usage' .is. &
       'Input file with the Hamiltonian matrix',&
       'Allowed values' .is. &
       'String'))

  call yaml_cl_parse_option(parser,'kernel_file','density_kernel_sparse.txt',&
       'input file with the density kernel matrix',&
       help_dict=dict_new('Usage' .is. &
       'Input file with the density kernel matrix',&
       'Allowed values' .is. &
       'String'))

  call yaml_cl_parse_option(parser,'kernel_matmul_file','density_kernel_sparse_matmul.txt',&
       'input file with the density kernel matrix multiplication matrix',&
       help_dict=dict_new('Usage' .is. &
       'Input file with the density kernel matrix multiplication matrix',&
       'Allowed values' .is. &
       'String'))

  call yaml_cl_parse_option(parser,'kernel_method','FOE',&
       'Indicate which kernel method should be used (FOE)',&
       help_dict=dict_new('Usage' .is. &
       'Indicate which kernel method should be used (FOE)',&
       'Allowed values' .is. &
       'String'))

  call yaml_cl_parse_option(parser,'sparsity_format','bigdft',&
       'indicate the sparsity format',&
       help_dict=dict_new('Usage' .is. &
       'Indicate the sparsity format of the input matrices',&
       'Allowed values' .is. &
       'String'))

  call yaml_cl_parse_option(parser,'matrix_format','serial_text',&
       'indicate the matrix format',&
       help_dict=dict_new('Usage' .is. &
       'Indicate the format of the input matrices',&
       'Allowed values' .is. &
       'String'))

  call yaml_cl_parse_option(parser,'scalapack_blocksize','-1',&
       'Indicate the blocksize for ScaLAPACK (negative for LAPACK)',&
       help_dict=dict_new('Usage' .is. &
       'Indicate the blocksize for ScaLAPACK (negative for LAPACK)',&
       'Allowed values' .is. &
       'Integer'))

  call yaml_cl_parse_option(parser,'check_spectrum','.false.',&
       'Indicate whether the spectral properties of the Hamiltonian shall be calculated',&
       help_dict=dict_new('Usage' .is. &
       'Indicate whether the spectral properties of the Hamiltonian shall be calculated by a diagonalization',&
       'Allowed values' .is. &
       'Logical'))

  call yaml_cl_parse_option(parser,'fscale_lowerbound','5.e-3',&
       'Indicate the minimal value for the Fermi function decay length',&
       help_dict=dict_new('Usage' .is. &
       'Indicate the minimal value for the Fermi function decay length that should be used to calculate the density kernel',&
       'Allowed values' .is. &
       'Double'))

  call yaml_cl_parse_option(parser,'fscale_upperbound','5.e-2',&
       'Indicate the maximal value for the Fermi function decay length',&
       help_dict=dict_new('Usage' .is. &
       'Indicate the maximal value for the Fermi function decay length that should be used to calculate the density kernel',&
       'Allowed values' .is. &
       'Double'))

  call yaml_cl_parse_option(parser,'ntemp','4',&
       'Indicate the maximal number of FOE iterations with various temperatures',&
       help_dict=dict_new('Usage' .is. &
       'Indicate the maximal number of FOE iterations with various temperatures, to determine dynamically the width of the gap',&
       'Allowed values' .is. &
       'Integer'))

  call yaml_cl_parse_option(parser,'ef','0.0',&
       'Indicate the initial guess for the Fermi energy',&
       help_dict=dict_new('Usage' .is. &
       'Indicate the initial guess for the Fermi energy, will then be adjusted automatically',&
       'Allowed values' .is. &
       'Double'))

  call yaml_cl_parse_option(parser,'npl_max','5000',&
       'Indicate the maximal polynomial degree',&
       help_dict=dict_new('Usage' .is. &
       'Indicate the maximal polynomial degree',&
       'Allowed values' .is. &
       'Integer'))

   call yaml_cl_parse_option(parser,'do_cubic_check','.true.',&
       'perform a check using cubic scaling dense (Sca)LAPACK',&
       help_dict=dict_new('Usage' .is. &
       'Indicate whether a cubic scaling check using dense (Sca)LAPACK should be performed',&
       'Allowed values' .is. &
       'Logical'))

   call yaml_cl_parse_option(parser,'iev_min','1',&
       'Lowest eigenvalue to be calculated',&
       help_dict=dict_new('Usage' .is. &
       'Indicate the lowest eigenvalue to be calculated',&
       'Allowed values' .is. &
       'Integer'))

   call yaml_cl_parse_option(parser,'iev_max','1000000',&
       'Higest eigenvalue to be calculated',&
       help_dict=dict_new('Usage' .is. &
       'Indicate the highest eigenvalue to be calculated',&
       'Allowed values' .is. &
       'Integer'))

   call yaml_cl_parse_option(parser,'fscale','5.e-3',&
       'Decay length of the error function used to find the eigenvalue',&
       help_dict=dict_new('Usage' .is. &
       'Indicate the decay length of the error function used to find the eigenvalue',&
       'Allowed values' .is. &
       'Double'))


end subroutine commandline_options
