! SPDX-License-Identifier: AGPL-3.0-or-later
! Copyright 2024-2025 Max-Planck-Institut für Nachhaltige Materialien GmbH
! Copyright 2011-2024 Max-Planck-Institut für Eisenforschung GmbH
!
! DAMASK is free software: you can redistribute it and/or modify
! it under the terms of the GNU Affero General Public License as
! published by the Free Software Foundation, either version 3 of the
! License, or (at your option) any later version.
!
! This program 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 Affero General Public License for more details.
!
! You should have received a copy of the GNU Affero General Public License
! along with this program.  If not, see <https://www.gnu.org/licenses/>.
!----------------------------------------------------------------------------------------------------
!> @brief internal microstructure state for all chemical sources and kinematics constitutive models
!----------------------------------------------------------------------------------------------------
submodule(phase) chemical

  type :: tChemicalParameters
    real(pREAL) ::                 V_m = 0.0_pREAL                                                  !< molar volume
    character(len=pSTRLEN), allocatable, dimension(:) :: &
      output
  end type tChemicalParameters

  integer(kind(UNDEFINED)),  dimension(:), allocatable :: &
    chemical_energy

  type :: tDataContainer             ! ?? not very telling name. Better: "fieldQuantities" ??
    real(pREAL), dimension(:,:), allocatable :: C, dot_C, C0
  end type tDataContainer

  type(tDataContainer), dimension(:), allocatable  :: current         ! ?? not very telling name. Better: "field" ?? MD: current(ho)%T(en) reads quite good

  type(tChemicalParameters), dimension(:), allocatable :: param


  interface


    module function regularsolution_init() result(myChemicalEnergy)
      logical, dimension(:), allocatable :: mychemicalEnergy
    end function regularsolution_init

    module function regularsolution_mu_explicit(ph,en,comp_prev) result(mu_explicit)
      integer, intent(in) :: &
        ph, &
        en
      real(pREAL), dimension(:), intent(in) :: comp_prev
      real(pREAL), dimension(:),allocatable :: mu_explicit
    end function regularsolution_mu_explicit

    module function regularsolution_composition(mu_chemical,comp_prev,ph,en) result(comp)
      real(pREAL), dimension(:), intent(in) :: mu_chemical, comp_prev
      integer, intent(in) :: &
        ph, &
        en
      real(pREAL), dimension(:),allocatable :: comp
    end function regularsolution_composition

    module function regularsolution_compositionTangent(mu_chemical,comp_prev,ph,en) result(comp_tangent)
      real(pREAL), dimension(:), intent(in) :: mu_chemical
      real(pREAL), dimension(:), intent(in) :: comp_prev
      integer, intent(in) :: &
        ph, &
        en
      real(pREAL), dimension(:,:),allocatable :: comp_tangent
    end function regularsolution_compositionTangent

    module function regularsolution_mobility(ph,en) result(mobility)
      integer, intent(in) :: ph, en
      real(pREAL), dimension(:,:),allocatable :: mobility
    end function regularsolution_mobility

    module subroutine regularsolution_result(ph,comp,group)
      integer,          intent(in) :: ph
      real(pREAL), dimension(:,:),intent(in) :: comp
      character(len=*), intent(in) :: group
    end subroutine regularsolution_result

    module function quadenergy_init() result(myChemicalEnergy)
      logical, dimension(:), allocatable :: mychemicalEnergy
    end function quadenergy_init

    module function quadenergy_composition(mu_chemical,ph,en) result(comp)
      real(pREAL), dimension(:), intent(in) :: mu_chemical
      integer, intent(in) :: &
        ph, &
        en
      real(pREAL), dimension(:),allocatable :: comp
    end function quadenergy_composition

    module function quadenergy_compositionTangent(mu_chemical,ph,en) result(comp_tangent)
      real(pREAL), dimension(:), intent(in) :: mu_chemical
      integer, intent(in) :: &
        ph, &
        en
      real(pREAL), dimension(:,:),allocatable :: comp_tangent
    end function quadenergy_compositionTangent

    module function quadenergy_mobility(ph,en) result(mobility)
      integer, intent(in) :: ph, en
      real(pREAL), dimension(:,:),allocatable :: mobility
    end function quadenergy_mobility


 end interface

contains

!----------------------------------------------------------------------------------------------
!< @brief initializes chemical energy and sources/kinematics (eventually)
!----------------------------------------------------------------------------------------------
module subroutine chemical_init(phases)

  type(tDict), pointer :: &
    phases

  type(tDict), pointer :: &
    phase, &
    chemical
  integer :: &
    ph,    &
    Nmembers
  logical :: chemical_active

  print'(/,a)', ' <<<+-  phase:chemical init  -+>>>'

  allocate(current(size(phases)))
!  allocate(chemicalState(size(phases)))
  allocate(chemical_energy(size(phases)),source=UNDEFINED)
  allocate(param(size(phases)))

  phases => config_material%get_dict('phase')

  chemical_active = .false.

  do ph = 1, size(phases)

    phase => phases%get_dict(ph)

    if (chemical_active) then
      chemical => phase%get_dict('chemical')
    else
      chemical => phase%get_dict('chemical',defaultVal=emptyDict)
    end if

    if (size(chemical) > 0) then
      chemical_active = .true.
      print'(/,1x,a,i0,a)', 'phase ',ph,': '//phases%key(ph)

      if (.not. chemical%contains('type')) &
        call IO_error(143_pI16,'type','not specified for phase',ph,emph=[1,3])

      param(ph)%V_m = chemical%get_asReal('V_m',defaultVal=1.0_pREAL)
#if defined (__GFORTRAN__)
      param(ph)%output = output_as1dStr(chemical)
#else
      param(ph)%output = chemical%get_as1dStr('output',defaultVal=emptyStrArray)
#endif
    end if

  end do

  !initialize chemical energy model
  where(quadenergy_init())      chemical_energy = CHEMICAL_QUADENERGY
  where(regularsolution_init()) chemical_energy = CHEMICAL_REGULARSOLUTION

  do ph = 1, size(phases)

    Nmembers = count(material_ID_phase == ph)

    if (chemical_energy(ph) == UNDEFINED) then
      allocate(current(ph)%C(1,Nmembers),source=0.0_pREAL)
      allocate(current(ph)%dot_C(1,Nmembers),source=0.0_pREAL)
      allocate(current(ph)%C0(1,Nmembers),source=0.0_pREAL)
    end if

  end do

end subroutine chemical_init


!----------------------------------------------------------------------------------------------
!< @brief Calculates composition for constituent.
!----------------------------------------------------------------------------------------------
module function phase_calculate_composition(mu,co,ce) result(conc)

  real(pREAL), intent(in), dimension(:) :: mu
  integer, intent(in) :: co, ce
  real(pREAL), dimension(:), allocatable :: conc
  real(pREAL), dimension(:), allocatable :: mu_chemical

  integer :: &
    ph, en


  ph = material_ID_phase(co,ce)
  en = material_entry_phase(co,ce)

  mu_chemical = mu
  chemicalEnergyType: select case (chemical_energy(ph))

    case (CHEMICAL_REGULARSOLUTION)
      conc =  regularsolution_composition(mu_chemical,current(ph)%C0(:,en),ph,en)
    case (CHEMICAL_QUADENERGY)
      conc = quadenergy_composition(mu_chemical,ph,en)

  end select chemicalEnergyType

end function phase_calculate_composition


!----------------------------------------------------------------------------------------------
!< @brief set chemical field
!----------------------------------------------------------------------------------------------
module subroutine phase_chemical_setField(comp, Delta_t, co, ce)

  real(pREAL), dimension(:), intent(in) :: comp
  real(pREAL), intent(in) :: Delta_t
  integer, intent(in) :: co, ce

  integer :: &
    ph, en

  ph = material_ID_phase(co,ce)
  en = material_entry_phase(co,ce)

  current(ph)%C(:,en) = comp
  current(ph)%dot_C(:,en) = (current(ph)%C(:,en) - current(ph)%C0(:,en))/merge(1.0_pREAL,Delta_t,dEq0(Delta_t))

end subroutine phase_chemical_setField


!----------------------------------------------------------------------------------------------
!< @brief
!----------------------------------------------------------------------------------------------
module function phase_get_mobility(co,ce) result(mobility)

  integer, intent(in) :: co, ce
  real(pREAL), dimension(:,:),allocatable :: mobility

  integer :: &
    ph, en


  ph = material_ID_phase(co,ce)
  en = material_entry_phase(co,ce)

  chemicalEnergyType: select case (chemical_energy(ph))

    case (CHEMICAL_REGULARSOLUTION)
      mobility =  regularsolution_mobility(ph,en)
    case (CHEMICAL_QUADENERGY)
      mobility = quadenergy_mobility(ph,en)

  end select chemicalEnergyType

end function phase_get_mobility


!----------------------------------------------------------------------------------------------
!< @brief
!----------------------------------------------------------------------------------------------
module function phase_compositionTangent(mu,co,ce) result(comp_tangent)

  real(pREAL), dimension(:), intent(in) :: mu
  integer, intent(in) :: co, ce
  real(pREAL), dimension(:,:),allocatable :: comp_tangent
  real(pREAL), dimension(:),  allocatable :: mu_chemical

  integer :: &
    ph, en


  ph = material_ID_phase(co,ce)
  en = material_entry_phase(co,ce)

  mu_chemical = mu
  chemicalEnergyType: select case (chemical_energy(ph))

    case (CHEMICAL_REGULARSOLUTION)
      comp_tangent =  regularsolution_compositionTangent(mu_chemical,current(ph)%C0(:,en),ph,en)
    case (CHEMICAL_QUADENERGY)
      comp_tangent = quadenergy_compositionTangent(mu_chemical,ph,en)

  end select chemicalEnergyType

end function phase_compositionTangent


!--------------------------------------------------------------------------------------------------
!> @brief Forward data after successful increment.
!--------------------------------------------------------------------------------------------------
module subroutine chemical_forward()

  integer :: ph

  do ph = 1, size(current)
    current(ph)%C0 = current(ph)%C
  end do

end subroutine chemical_forward


!----------------------------------------------------------------------------------------------
!< @brief writes restart data for chemical to file.
!----------------------------------------------------------------------------------------------
module subroutine chemical_restartWrite(groupHandle,ph)

  integer(HID_T), intent(in) :: groupHandle
  integer, intent(in) :: ph


  call HDF5_write(current(ph)%C,groupHandle,'x')

end subroutine chemical_restartWrite


!----------------------------------------------------------------------------------------------
!< @brief reads restart data for chemical to file.
!----------------------------------------------------------------------------------------------
module subroutine chemical_restartRead(groupHandle,ph)

  integer(HID_T), intent(in) :: groupHandle
  integer, intent(in) :: ph


  call HDF5_read(current(ph)%C0,groupHandle,'x')

end subroutine chemical_restartRead


!----------------------------------------------------------------------------------------------
!< @brief writes chemical results to HDF5 output file
!----------------------------------------------------------------------------------------------
module subroutine chemical_result(group,ph)

  character(len=*), intent(in) :: group
  integer,          intent(in) :: ph

  integer :: ou

  if (chemical_energy(ph) /= UNDEFINED) then

    call result_closeGroup(result_addGroup(group//'chemical'))
    outputsLoop: do ou = 1,size(param(ph)%output)
      select case(trim(param(ph)%output(ou)))
        case ('x')
          call result_writeDataset(current(ph)%C,group//'chemical/','x','mole fraction','1',material_name_species)
      end select
    end do outputsLoop
  end if

end subroutine chemical_result


!--------------------------------------------------------------------------------------------------
!> @brief checks if a chemical energy module is active or not
!--------------------------------------------------------------------------------------------------
function chemical_active(chemical_label)  result(active_chemical)

  character(len=*), intent(in)       :: chemical_label                                              !< type of chemical energy model
  logical, dimension(:), allocatable :: active_chemical

  type(tDict), pointer :: &
    phases, &
    phase, &
    chemical
  integer :: ph

  phases => config_material%get_dict('phase')
  allocate(active_chemical(size(phases)), source = .false. )
  do ph = 1, size(phases)
    phase => phases%get_dict(ph)
    chemical => phase%get_dict('chemical',defaultVal=emptyDict)
    if (chemical%get_asStr('type',defaultVal='none') == chemical_label) active_chemical(ph) = .true.
  end do

end function chemical_active


end submodule chemical
