!-*- mode: F90 -*-!
!------------------------------------------------------------!
! Copyright (C) 2026 Wannier Developer Group                 !
!                                                            !
! This library 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 2.1 of the License, or (at your !
! option) any later version.                                 !
!                                                            !
! This library 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 this library; if not, see        !
! <https://www.gnu.org/licenses/>.                           !
!                                                            !
! The webpage of the Wannier90 code is                       !
! <https://www.wannier.org>.                                 !
!                                                            !
! The Wannier90 code is hosted on GitHub                     !
! <https://github.com/wannier-developers/wannier90>          !
!------------------------------------------------------------!
!                                                            !
!  w90spn2spn: reformat spn files                            !
!                                                            !
!------------------------------------------------------------!

module w90spn_parameters

  use w90_types

  implicit none

  public

  integer, save :: num_kpts !BGS put in k_point_type?
  integer, save :: num_bands !! Number of bands

end module w90spn_parameters

module w90_conv_spn
  !! Module to convert spn files from formatted to unformmated
  !! and vice versa - useful for switching between computers
  use w90_constants, only: dp

  implicit none

  logical, save :: export_flag
  ! header length is identical to that of pw2wannier90.f90
  character(len=60), save :: header
  complex(kind=dp), allocatable, save :: spn_o(:, :, :, :)

contains

  subroutine io_error(error_msg, stdout)
    !================================================
    !
    !! Abort the code giving an error message
    !
    !================================================

    implicit none

    character(len=*), intent(in) :: error_msg
    integer :: stdout

    close (stdout)
    write (*, '(1x,a)') trim(error_msg)
    write (*, '(A)') "Error: examine the output/error file for details"
    stop 1
  end subroutine io_error

  !================================================!
  subroutine print_usage(stdout)
    !================================================!
    !
    !! Writes the usage of the program to stdout
    !
    !================================================!

    implicit none

    integer, intent(in) :: stdout

    write (stdout, '(A)') "Usage:"
    write (stdout, '(A)') "  w90spn2spn.x ACTION [SEEDNAME]"
    write (stdout, '(A)') "where ACTION can be one of the following:"
    write (stdout, '(A)') "  -export"
    write (stdout, '(A)') "  -u2f"
    write (stdout, '(A)') "      Convert from unformatted (standard) format to formatted format, to export"
    write (stdout, '(A)') "      the spn file on a different machine."
    write (stdout, '(A)') "      The seedname.spn file is read and the seedname.spn.fmt file is generated."
    write (stdout, '(A)') "  -import"
    write (stdout, '(A)') "  -f2u"
    write (stdout, '(A)') "      Convert from formatted format to unformatted (standard) format, to import"
    write (stdout, '(A)') "      the spn file seedname.spn.fmt from a different machine."
    write (stdout, '(A)') "      The seedname.spn.fmt file is read and the seedname.spn file is generated."
  end subroutine print_usage

  !================================================!
  subroutine conv_get_seedname(stdout, seedname)
    !================================================!
    !
    !! Set the seedname from the command line
    !
    !================================================!
    implicit none

    integer, intent(in) :: stdout
    character(len=50), intent(inout)  :: seedname

    integer :: num_arg
    character(len=50) :: ctemp

    num_arg = command_argument_count()
    if (num_arg == 1) then
      seedname = 'wannier'
    elseif (num_arg == 2) then
      call get_command_argument(2, seedname)
    else
      call print_usage(stdout)
      call io_error('Wrong command line arguments, see logfile for usage', stdout)
    end if

    ! If on the command line the whole seedname.win was passed, I strip the last ".win"
    if (len(trim(seedname)) .ge. 5) then
      if (seedname(len(trim(seedname)) - 4 + 1:) .eq. ".win") then
        seedname = seedname(:len(trim(seedname)) - 4)
      end if
    end if

    call get_command_argument(1, ctemp)
    if (index(ctemp, '-import') > 0) then
      export_flag = .false.
    elseif (index(ctemp, '-f2u') > 0) then
      export_flag = .false.
    elseif (index(ctemp, '-export') > 0) then
      export_flag = .true.
    elseif (index(ctemp, '-u2f') > 0) then
      export_flag = .true.
    else
      write (stdout, '(A)') 'Wrong command line action: '//trim(ctemp)
      call print_usage(stdout)
      call io_error('Wrong command line arguments, see logfile for usage', stdout)
    end if

  end subroutine conv_get_seedname

  !================================================!
  subroutine conv_read_spn(stdout, seedname)
    !================================================!
    !
    !! Read unformatted spn file
    !
    !================================================!

    use w90_constants, only: eps6, dp
    use w90spn_parameters, only: num_bands, num_kpts

    implicit none

    integer, intent(in) :: stdout
    character(len=50), intent(in)  :: seedname

    integer :: spn_unit, m, n, ik, ierr, s, counter
    complex(kind=dp), allocatable :: spn_temp(:, :)

    write (stdout, '(3a)') 'Reading information from unformatted file ', trim(seedname), '.spn :'

    open (newunit=spn_unit, file=trim(seedname)//'.spn', status='old', form='unformatted', err=109)

    ! Read comment line
    read (spn_unit, err=110, end=110) header
    header = ADJUSTL(header)
    write (stdout, '(1x,a)') trim(header)

    ! Consistency checks
    read (spn_unit, err=110, end=110) num_bands, num_kpts
    write (stdout, '(1x,a,i0)') "Number of bands: ", num_bands
    write (stdout, '(1x,a,i0)') "Number of k-points: ", num_kpts

    allocate (spn_o(num_bands, num_bands, num_kpts, 3), stat=ierr)
    if (ierr /= 0) call io_error('Error in allocating spm_temp in conv_read_spn', stdout)

    allocate (spn_temp(3, (num_bands*(num_bands + 1))/2), stat=ierr)
    if (ierr /= 0) call io_error('Error in allocating spm_temp in conv_read_spn', stdout)
    do ik = 1, num_kpts
      read (spn_unit) ((spn_temp(s, m), s=1, 3), m=1, (num_bands*(num_bands + 1))/2)
      counter = 0
      do m = 1, num_bands
        do n = 1, m
          counter = counter + 1
          spn_o(n, m, ik, 1) = spn_temp(1, counter)
          spn_o(n, m, ik, 2) = spn_temp(2, counter)
          spn_o(n, m, ik, 3) = spn_temp(3, counter)
          ! Although each diagonal element of spn_o should be a real number,
          ! actually it has a very small imaginary part.
          ! We skip the conjugation on the diagonal elements so that
          ! the file after formatted <==> unformatted conversions is exactly
          ! the same as the original file, otherwise the diagonal elements
          ! are the conjugations of those of the original file.
          if (m == n) cycle
          spn_o(m, n, ik, 1) = conjg(spn_temp(1, counter))
          spn_o(m, n, ik, 2) = conjg(spn_temp(2, counter))
          spn_o(m, n, ik, 3) = conjg(spn_temp(3, counter))
        end do
      end do
    end do

    close (spn_unit)
    write (stdout, '(1x,a)') "spn: read."

    deallocate (spn_temp, stat=ierr)
    if (ierr /= 0) call io_error('Error in deallocating spm_temp in conv_read_spn', stdout)

    write (stdout, '(1x,a)') 'read done.'

    return

109 call io_error('Error opening '//trim(seedname)//'.spn.fmt in conv_read_spn', stdout)
110 call io_error('Error reading '//trim(seedname)//'.spn.fmt in conv_read_spn', stdout)

  end subroutine conv_read_spn

  !================================================!
  subroutine conv_read_spn_fmt(stdout, seedname)
    !================================================!
    !
    !! Read formatted spn file
    !
    !================================================!

    use w90_constants, only: eps6, dp
    use w90spn_parameters, only: num_bands, num_kpts

    implicit none

    integer, intent(in) :: stdout
    character(len=50), intent(in)  :: seedname

    integer :: spn_unit, m, n, ik, ierr
    real(kind=dp) :: s_real, s_img

    write (stdout, '(3a)') 'Reading information from formatted file ', trim(seedname), '.spn.fmt :'

    open (newunit=spn_unit, file=trim(seedname)//'.spn.fmt', status='old', position='rewind', form='formatted', err=109)

    ! Read comment line
    read (spn_unit, '(a)') header
    header = ADJUSTL(header)
    write (stdout, '(1x,a)') trim(header)

    ! Consistency checks
    read (spn_unit, *, err=110, end=110) num_bands, num_kpts
    write (stdout, '(1x,a,i0)') "Number of bands: ", num_bands
    write (stdout, '(1x,a,i0)') "Number of k-points: ", num_kpts

    allocate (spn_o(num_bands, num_bands, num_kpts, 3), stat=ierr)
    if (ierr /= 0) call io_error('Error in allocating spn_o in conv_read_spn_fmt', stdout)

    do ik = 1, num_kpts
      do m = 1, num_bands
        do n = 1, m
          read (spn_unit, *, err=110, end=110) s_real, s_img
          spn_o(n, m, ik, 1) = cmplx(s_real, s_img, dp)
          read (spn_unit, *, err=110, end=110) s_real, s_img
          spn_o(n, m, ik, 2) = cmplx(s_real, s_img, dp)
          read (spn_unit, *, err=110, end=110) s_real, s_img
          spn_o(n, m, ik, 3) = cmplx(s_real, s_img, dp)
          ! Although each diagonal element of spn_o should be a real number,
          ! actually it has a very small imaginary part.
          ! We skip the conjugation on the diagonal elements so that
          ! the file after formatted <==> unformatted conversions is exactly
          ! the same as the original file, otherwise the diagonal elements
          ! are the conjugations of those of the original file.
          if (m == n) cycle
          ! Read upper-triangular part, now build the rest
          spn_o(m, n, ik, 1) = conjg(spn_o(n, m, ik, 1))
          spn_o(m, n, ik, 2) = conjg(spn_o(n, m, ik, 2))
          spn_o(m, n, ik, 3) = conjg(spn_o(n, m, ik, 3))
        end do
      end do
    end do

    close (spn_unit)

    write (stdout, '(1x,a)') 'read done.'

    return

109 call io_error('Error opening '//trim(seedname)//'.spn.fmt in conv_read_spn_fmt', stdout)
110 call io_error('Error reading '//trim(seedname)//'.spn.fmt in conv_read_spn_fmt', stdout)

  end subroutine conv_read_spn_fmt

  !================================================!
  subroutine conv_write_spn(stdout, seedname)
    !================================================!
    !
    !! Write unformatted spn file
    !
    !================================================!

    use w90_io, only: io_date
    use w90spn_parameters, only: num_bands, num_kpts

    implicit none

    integer, intent(in) :: stdout
    character(len=50), intent(in)  :: seedname

    integer :: spn_unit, m, n, ik, counter, s, ierr
    complex(kind=dp), allocatable :: spn_temp(:, :)

    write (stdout, '(3a)') 'Writing information to unformatted file ', trim(seedname), '.spn :'

    open (newunit=spn_unit, file=trim(seedname)//'.spn', form='unformatted')

    allocate (spn_temp(3, (num_bands*(num_bands + 1))/2), stat=ierr)
    if (ierr /= 0) call io_error('Error in allocating spm_temp in conv_write_spn', stdout)

    write (spn_unit) header
    write (spn_unit) num_bands, num_kpts

    do ik = 1, num_kpts
      counter = 0
      do m = 1, num_bands
        do n = 1, m
          counter = counter + 1
          do s = 1, 3
            spn_temp(s, counter) = spn_o(n, m, ik, s)
          end do
        end do
      end do
      write (spn_unit) ((spn_temp(s, m), s=1, 3), m=1, ((num_bands*(num_bands + 1))/2))
    end do

    close (spn_unit)

    write (stdout, '(1x,a)') 'write done.'

  end subroutine conv_write_spn

  !================================================!
  subroutine conv_write_spn_fmt(stdout, seedname)
    !================================================!
    !
    !! Write formatted spn file
    !
    !================================================!

    use w90_io, only: io_date
    use w90spn_parameters, only: num_bands, num_kpts

    implicit none

    integer, intent(in) :: stdout
    character(len=50), intent(in)  :: seedname

    integer :: spn_unit, m, n, ik, s

    write (stdout, '(3a)') 'Writing information to formatted file ', trim(seedname), '.spn.fmt :'

    open (newunit=spn_unit, file=trim(seedname)//'.spn.fmt', form='formatted', status='replace', position='rewind')

    write (spn_unit, *) header
    write (spn_unit, *) num_bands, num_kpts

    do ik = 1, num_kpts
      do m = 1, num_bands
        do n = 1, m
          do s = 1, 3
            write (spn_unit, '(2es26.16)') spn_o(n, m, ik, s)
          end do
        end do
      end do
    end do

    close (spn_unit)

    write (stdout, '(1x, a)') 'write done.'

  end subroutine conv_write_spn_fmt

end module w90_conv_spn

program w90spn2spn
  !! Program to convert spn files from formatted to unformmated
  !! and vice versa - useful for switching between computers
  use w90_constants, only: dp
  use w90_conv_spn

  implicit none

  ! Export mode:
  !  TRUE:  create formatted .spn.fmt from unformatted .spn ('-export')
  !  FALSE: create unformatted .spn from formatted .spn.fmt ('-import')

  !logical :: file_found
  !integer :: file_unit
  integer :: stdout !, ierr, num_nodes
  character(len=50) :: seedname

  open (newunit=stdout, file='w90spn2spn.log')

  call conv_get_seedname(stdout, seedname)

  if (export_flag .eqv. .true.) then
    call conv_read_spn(stdout, seedname)
    write (stdout, '(a)') ''
    call conv_write_spn_fmt(stdout, seedname)
  else
    call conv_read_spn_fmt(stdout, seedname)
    write (stdout, '(a)') ''
    call conv_write_spn(stdout, seedname)
  end if

  close (unit=stdout)

end program w90spn2spn
