! 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/>.
!--------------------------------------------------------------------------------------------------
!> @author Franz Roters, Max-Planck-Institut für Eisenforschung GmbH
!> @author Philip Eisenlohr, Max-Planck-Institut für Eisenforschung GmbH
!> @author Martin Diehl, Max-Planck-Institut für Eisenforschung GmbH
!> @brief Defines phase and homogenization
!--------------------------------------------------------------------------------------------------
module material
  use prec
  use config
  use result
  use math
  use IO
  use rotations
  use discretization
  use types

  implicit none(type,external)
  private

  type, public :: tRotationContainer
    type(tRotation), dimension(:), allocatable :: data
  end type tRotationContainer

  type, public :: tTensorContainer
    real(pREAL), dimension(:,:,:), allocatable :: data
  end type tTensorContainer


  type(tRotationContainer), dimension(:), allocatable, public, protected :: material_O_0
  type(tTensorContainer),   dimension(:), allocatable, public, protected :: material_V_e_0

  integer, dimension(:), allocatable, public, protected :: &
    homogenization_Nconstituents                                                                    !< number of grains in each homogenization
  integer, public, protected :: &
    homogenization_maxNconstituents                                                                 !< max number of grains in any homogenization

! --- BAD
  integer, dimension(:), allocatable, public :: &
    homogenization_chemical_Ncomponents

  integer, public:: &
    homogenization_chemical_maxNcomponents
! ---
  character(len=:), public, protected, allocatable, dimension(:) :: &
    material_name_phase, &                                                                          !< name of each phase
    material_name_homogenization                                                                    !< name of each homogenization

  integer, dimension(:),   allocatable, public, protected :: &                                      ! (cell)
    material_ID_homogenization, &                                                                   !< Number of the homogenization
    material_entry_homogenization                                                                   !< Position in array of used homogenization
  integer, dimension(:,:), allocatable, public, protected :: &                                      ! (constituent,cell)
    material_ID_phase, &                                                                            !< Number of the phase
    material_entry_phase                                                                            !< Position in array of used phase

  real(pREAL), dimension(:,:), allocatable, public, protected :: &
    material_v                                                                                      ! fraction

  character(len=:), dimension(:), public, allocatable :: &
    material_name_species

    public :: &
    material_init



contains

!--------------------------------------------------------------------------------------------------
!> @brief Parse material configuration file (material.yaml) and write mappings to result file.
!--------------------------------------------------------------------------------------------------
subroutine material_init()

  print'(/,1x,a)', '<<<+-  material init  -+>>>'; flush(IO_STDOUT)

  call parse()
  print'(/,1x,a)', 'parsed material.yaml'

  call result_openJobFile()
  if (.not. result_objectExists('cell_to/phase')) &
    call result_mapping_phase(material_ID_phase,material_entry_phase,material_name_phase)
  if (.not. result_objectExists('cell_to/homogenization')) &
    call result_mapping_homogenization(material_ID_homogenization,material_entry_homogenization,material_name_homogenization)
  call result_closeJobFile()

end subroutine material_init


!--------------------------------------------------------------------------------------------------
!> @brief Parse material.yaml to get the global structure.
!--------------------------------------------------------------------------------------------------
subroutine parse()

  type(tList), pointer :: materials, &                                                              !< all materials
                          constituents                                                              !< all constituents of a material
  type(tDict), pointer :: phases, &                                                                 !< all phases
                          homogenizations, &                                                        !< all homogenizations
                          material, &                                                               !< material definition
                          constituent, &                                                            !< constituent definition
                          homogenization

  type(tItem), pointer :: item
  integer, dimension(:), allocatable :: &
    counterPhase, &
    counterHomogenization, &
    ho_of
  integer, dimension(:,:), allocatable :: ph_of
  real(pREAL), dimension(:,:), allocatable :: v_of

  real(pREAL) :: v
  integer :: &
    el, ip, &
    ho, ph, &
    co, ce, &
    ma


  materials       => config_material%get_list('material')
  phases          => config_material%get_dict('phase')
  homogenizations => config_material%get_dict('homogenization')


  if (maxval(discretization_materialAt) > size(materials)) &
    call IO_error(155,ext_msg='More materials requested than found in material.yaml')

  material_name_phase          = phases%keys()
  material_name_homogenization = homogenizations%keys()
  material_name_species = get_chemical_species(phases)

  allocate(homogenization_Nconstituents(size(homogenizations)))
  do ho=1, size(homogenizations)
    homogenization => homogenizations%get_dict(ho)
    homogenization_Nconstituents(ho) = homogenization%get_asInt('N_constituents')
  end do
  homogenization_maxNconstituents = maxval(homogenization_Nconstituents)

  allocate(material_v(homogenization_maxNconstituents,discretization_Ncells),source=0.0_pREAL)

  allocate(material_O_0(size(materials)))
  allocate(material_V_e_0(size(materials)))

  allocate(ho_of(size(materials)))
  allocate(ph_of(size(materials),homogenization_maxNconstituents),source=-1)
  allocate( v_of(size(materials),homogenization_maxNconstituents),source=0.0_pREAL)

  ! Parse YAML structure. Manual loop over linked list to have O(n) instead of O(n^2) complexity
  item => materials%first
  do ma = 1, size(materials)
    material => item%node%asDict()
    ho_of(ma) = homogenizations%index(material%get_asStr('homogenization'))
    constituents => material%get_list('constituents')

    homogenization => homogenizations%get_dict(ho_of(ma))
    if (size(constituents) /= homogenization%get_asInt('N_constituents')) call IO_error(148)

    allocate(material_O_0(ma)%data(size(constituents)))
    allocate(material_V_e_0(ma)%data(1:3,1:3,size(constituents)))

    do co = 1, size(constituents)
      constituent => constituents%get_dict(co)
       v_of(ma,co) = constituent%get_asReal('v')
      ph_of(ma,co) = phases%index(constituent%get_asStr('phase'))

      call material_O_0(ma)%data(co)%fromQuaternion(constituent%get_as1dReal('O',requiredSize=4))
      material_V_e_0(ma)%data(1:3,1:3,co) = constituent%get_as2dReal('V_e',defaultVal=math_I3,requiredShape=[3,3])
      if (any(dNeq(material_V_e_0(ma)%data(1:3,1:3,co),transpose(material_V_e_0(ma)%data(1:3,1:3,co))))) &
        call IO_error(147)

    end do
    if (dNeq(sum(v_of(ma,:)),1.0_pREAL,1.e-9_pREAL)) call IO_error(153,ext_msg='constituent')

    item => item%next
  end do

  allocate(counterPhase(size(phases)),source=0)
  allocate(counterHomogenization(size(homogenizations)),source=0)

  allocate(material_ID_homogenization(discretization_Ncells),source=0)
  allocate(material_entry_homogenization(discretization_Ncells),source=0)

  allocate(material_ID_phase(homogenization_maxNconstituents,discretization_Ncells),source=0)
  allocate(material_entry_phase(homogenization_maxNconstituents,discretization_Ncells),source=0)


  ! build mappings
  do el = 1, discretization_Nelems

    ma = discretization_materialAt(el)
    ho = ho_of(ma)

    do ip = 1, discretization_nIPs
      ce = (el-1)*discretization_nIPs + ip
      material_ID_homogenization(ce) = ho
      counterHomogenization(ho) = counterHomogenization(ho) + 1
      material_entry_homogenization(ce) = counterHomogenization(ho)
    end do

    do co = 1, size(ph_of(ma,:)>0)

      v  =  v_of(ma,co)
      ph = ph_of(ma,co)

      do ip = 1, discretization_nIPs
        ce = (el-1)*discretization_nIPs + ip
        material_ID_phase(co,ce) = ph
        counterPhase(ph) = counterPhase(ph) + 1
        material_entry_phase(co,ce) = counterPhase(ph)
        material_v(co,ce) = v
      end do

    end do
  end do

end subroutine parse


#if defined (__GFORTRAN__)
!--------------------------------------------------------------------------------------------------
!> @brief %keys() is broken on gfortran
!--------------------------------------------------------------------------------------------------
function getKeys(dict)

  type(tDict), intent(in) :: dict
  character(len=:),       dimension(:), allocatable :: getKeys
  character(len=pSTRLEN), dimension(:), allocatable :: temp

  integer :: i,l

  allocate(temp(size(dict)))
  l = 0
  do i=1, size(dict)
    temp(i) = dict%key(i)
    l = max(len_trim(temp(i)),l)
  end do

  allocate(character(l)::getKeys(size(dict)))
  do i=1, size(dict)
    getKeys(i) = trim(temp(i))
  end do

end function getKeys
#endif

!--------------------------------------------------------------------------------------------------
!> @brief Get names of chemical species.
!--------------------------------------------------------------------------------------------------
function get_chemical_species(phases)

  type(tDict), intent(in) :: phases
  character(len=:),       dimension(:), allocatable :: get_chemical_species

  type(tDict), pointer :: phase, &
                          chemical, &
                          components


  ! SR: sanity check probably required if other phases have the same set of components defined or not
  phase => phases%get_dict(material_name_phase(1))
  if (phase%contains('chemical')) then
    chemical => phase%get_dict('chemical')
    components => chemical%get_dict('components')
    get_chemical_species = components%keys()
  else
    get_chemical_species = emptyStrArray
  end if

end function get_chemical_species


end module material
