!# This source file is part of code Pégase.3.0.1 (2019-02-21).
!# Copyright: Michel Fioc (Michel.Fioc@iap.fr), Sorbonne université, 
!# Institut d'astrophysique de Paris/CNRS, France.
!# 
!# Pégase.3.0.1 is governed by the CeCILL license under French law and abides 
!# by the rules of distribution of free software. You can use, modify and/or 
!# redistribute this software under the terms of the CeCILL license as circulated 
!# by CEA, CNRS and INRIA at "http://www.cecill.info". The text of this license
!# is also available in French and in English in directory "doc_dir/" of this
!# code.
!# 
!# As a counterpart to the access to the source code and to the rights to copy,
!# modify and redistribute it granted by the license, users are provided only
!# with a limited warranty, and the software's author, the holder of the
!# economic rights, and the successive licensors have only limited
!# liability. 
!# 
!# The fact that you are presently reading this means that you have had
!# knowledge of the CeCILL license and that you accept its terms.
!#====================================================================== 

module mod_IMF

  use mod_types
  private
  public :: choose_IMF, IMF_d_n
  real(DPR) :: LN_C1, LN_C2
  public :: LN_C1, LN_C2

contains

!#======================================================================

  subroutine choose_IMF(IMF_file, IMF_dim_slope, IMF_bin_coeff, &
       IMF_bin_bottom_mass, IMF_bin_slope, IMF_mass_min, IMF_mass_max, &
       IMF_norm)

    use mod_types
    use mod_directories, only : IMFs_dir
    use mod_file_access, only : open_file, close_file, path_file, skip_comment_lines
    use mod_SSPs_constants, only : default_IMF_mass_min, default_IMF_mass_max, &
         default_LN_C1, default_LN_C2

    implicit none
    character(len=*), intent(in) :: IMF_file
    integer, intent(out) :: IMF_dim_slope
    real(DPR), dimension(:), pointer :: IMF_bin_coeff, IMF_bin_bottom_mass, &
         IMF_bin_slope
    real(DPR), intent(out) :: IMF_mass_min, IMF_mass_max, IMF_norm
!#......................................................................
    integer :: i_slope
    real(DPR) :: IMF_last_bin_top_mass
    integer :: error, unit
    character(len=std_string) :: string
!#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

    LN_C1 = default_LN_C1
    LN_C2 = default_LN_C2
    
    if ((IMF_file /= "IMF_log_normal.txt") .and. &
         (IMF_file /= "IMF_Rana_Basu.txt") .and. &
         (IMF_file /= "IMF_Ferrini.txt") .and. &
         (IMF_file /= "IMF_Chabrier_2005.txt")  .and. &
         (IMF_file /= "IMF_Chabrier_2003.txt")) then
       call open_file(unit, path_file(IMFs_dir, IMF_file))
       read(unit,*) string
       call skip_comment_lines(unit)
       read(unit, *) IMF_dim_slope
       if (associated(IMF_bin_coeff)) deallocate(IMF_bin_coeff)
       allocate(IMF_bin_coeff(IMF_dim_slope))
       if (associated(IMF_bin_bottom_mass)) deallocate(IMF_bin_bottom_mass)
       allocate(IMF_bin_bottom_mass(IMF_dim_slope))
       if (associated(IMF_bin_slope)) deallocate(IMF_bin_slope)
       allocate(IMF_bin_slope(IMF_dim_slope))
       do i_slope = 1, IMF_dim_slope
          read(unit, *) IMF_bin_bottom_mass(i_slope), IMF_bin_slope(i_slope)
       end do
       read(unit, *) IMF_last_bin_top_mass
       IMF_bin_coeff(1) = 1
       call close_file(unit)
       do i_slope = 2, IMF_dim_slope
          IMF_bin_coeff(i_slope) = IMF_bin_coeff(i_slope-1)* &
               IMF_bin_bottom_mass(i_slope)**(IMF_bin_slope(i_slope-1)-IMF_bin_slope(i_slope))
       end do
       IMF_mass_min = IMF_bin_bottom_mass(1)
       IMF_mass_max = IMF_last_bin_top_mass
    else
       IMF_mass_min = default_IMF_mass_min
       IMF_mass_max = default_IMF_mass_max

       if (IMF_file == "IMF_log_normal.txt") then
          error = 1
          do while (error /= 0)
             write(*, "(a)") "Log-normal C1 parameter?"
             write(*, "(a, es9.2, a)") "Default: ", LN_C1, "."
             read(*, "(a)") string !# Not `read(*, *) string`!
             if (string /= "") then
                read(string, *, iostat = error) LN_C1
             else
                error = 0
             end if
             if (error /= 0) write(*, "(a)") "Invalid input!"
          end do

          error = 1
          do while (error /= 0)
             write(*, "(a)") "Log-normal C2 parameter?"
             write(*, "(a, es9.2, a)") "Default: ", LN_C2, "."
             read(*, "(a)") string !# Not `read(*, *) string`!
             if (string /= "") then
                read(string, *, iostat = error) LN_C2
             else
                error = 0
             end if
             if (error /= 0) write(*, "(a)") "Invalid input!"
          end do
       endif
    end if

!# Modify the lower mass:
    error = 1
    do while (error /= 0)
       write(*, "(a)") "Lower mass of the IMF (in solar masses)?"
       write(*, "(a, es8.2, a)") "Default: ", IMF_mass_min, " solar masses."
       read(*, "(a)") string !# Not `read(*, *) string`!
       if (string /= "") then
          read(string, *, iostat = error) IMF_mass_min
       else
          error = 0
       end if
       if (error /= 0) write(*, "(a)") "Invalid input!"
    end do

!# Modify the upper mass:
    error = 1
    do while (error /= 0)
       write(*, "(a)") "Upper mass of the IMF (in solar masses)?"
       write(*, "(a, es8.2, a)") "Default: ", IMF_mass_max, " solar masses."
       read(*, "(a)") string !# Not `read(*, *) string`!
       if (string /= "") then
          read(string, *, iostat = error) IMF_mass_max
       else
          error = 0
       end if
       if (error /= 0) write(*, "(a)") "Invalid input!"
    end do

!# Compute the normalization:
    IMF_norm = IMF_normalization(IMF_file, IMF_dim_slope, IMF_bin_coeff, &
         IMF_bin_bottom_mass, IMF_bin_slope, IMF_mass_min, IMF_mass_max)

  end subroutine choose_IMF

!#======================================================================

  function IMF_d_n(l10_mass, d_l10_mass, IMF_file, IMF_dim_slope, IMF_bin_coeff, &
       IMF_bin_bottom_mass, IMF_bin_slope, &
       IMF_mass_min, IMF_mass_max, IMF_norm)

    use mod_types
    use mod_interp, only : bracket

    implicit none
    real(DPR), intent(in) :: l10_mass, d_l10_mass
    real(DPR), intent(in), optional :: IMF_norm
    character(len=*), intent(in) :: IMF_file
    integer, intent(in) :: IMF_dim_slope
    real(DPR), dimension(:), intent(in) :: IMF_bin_coeff, IMF_bin_bottom_mass, IMF_bin_slope
    real(DPR), intent(in) :: IMF_mass_min, IMF_mass_max
    real(DPR) :: IMF_d_n
!#......................................................................
    real(DPR) :: mass, d_n
    integer :: i_slope
!#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

    mass = 10**l10_mass
    if (mass > IMF_mass_max .or. mass < IMF_mass_min) then
       d_n = 0
    else
       if (IMF_file == "IMF_log_normal.txt") then
          d_n = d_l10_mass * exp(-LN_C1*(l10_mass-LN_C2)**2)

!# Note: `exp(-LN_C1*(l10_mass-LN_C2)**2)` = 
!#           $k_0 10^{k_1 `l10_mass`} 10^{k_2 `l10_mass`^2}$,
!# with $k_0 = \exp(-`LN_C1` `LN_C2`^2)$,
!#      $k_1 = 2 `LN_C1` `LN_C2`/\ln 10$ and
!#      $k_2 = -`LN_C1`/\ln 10$.
!#
!# For `LN_C1 = 1.09` and `LN_C2 = -1.02`, 
!# $k_1$ = -0.96 ~= -1 and $k_2$ = -0.47 ~= -1/2, 
!# leading to the formula used in Pégase.2:
!# `d_n = d_l10_mass * 10**(-l10_mass**2/2)/mass` 
!# (up to the $k_0$ factor which does not matter since the IMF is normalized).

       else if (IMF_file == "IMF_Rana_Basu.txt") then
          d_n = d_l10_mass * 10**(1.548_DPR - 1.513_DPR*l10_mass &
               - 0.395_DPR*l10_mass**2 + 0.502_DPR*l10_mass**3 &
               - 0.169_DPR*l10_mass**4)

       else if (IMF_file == "IMF_Ferrini.txt") then
          d_n = d_l10_mass * 2.01_DPR*mass**(-0.52_DPR)* &
               10**(-sqrt(2.07_DPR*l10_mass**2 + 1.92_DPR*l10_mass &
               + 0.73_DPR))

       else if (IMF_file == "IMF_Chabrier_2005.txt") then
          if (mass <= 1) then
             d_n = d_l10_mass * 0.093_DPR*exp(-(l10_mass+0.69897_DPR)**2/0.605_DPR)
          else
             d_n = d_l10_mass * 0.04147_DPR*mass**(-1.35_DPR)
          endif

       else if (IMF_file == "IMF_Chabrier_2003.txt") then
          if (mass <= 1) then
             d_n = d_l10_mass * 0.158_DPR*exp(-(l10_mass+1.1024_DPR)**2/0.9522_DPR)
          else !# Factor "0.9953" to ensure continuity at 1 solar mass.
             d_n = d_l10_mass * 0.0443_DPR*mass**(-1.3_DPR)*0.9953_DPR
          endif

       else
          i_slope = 1
          if (mass > IMF_bin_bottom_mass(1)) then
             if (mass >= IMF_bin_bottom_mass(IMF_dim_slope)) then
                i_slope = IMF_dim_slope
             else
                i_slope = 0
                call bracket(IMF_dim_slope, IMF_bin_bottom_mass, mass, i_slope)
             end if
          else
             i_slope = 1
          end if
          d_n = IMF_bin_coeff(i_slope) * d_l10_mass * mass**IMF_bin_slope(i_slope)
       end if
    end if
    if (present(IMF_norm)) then
       IMF_d_n = d_n/IMF_norm
    else
       IMF_d_n = d_n
    endif

    if (IMF_d_n < 0) then
       if (present(IMF_norm)) then
          write(*,*) l10_mass, d_l10_mass, IMF_norm
          write(*, "(a)") "`IMF_d_n` < 0. Stopped."
          stop
       else
          write(*,*) l10_mass, d_l10_mass
          write(*, "(a)") "`IMF_d_n` < 0. Stopped."
          stop
       endif
       stop
    endif

  end function IMF_d_n

!#======================================================================

  function IMF_normalization(IMF_file, IMF_dim_slope, IMF_bin_coeff, &
       IMF_bin_bottom_mass, IMF_bin_slope, &
       IMF_mass_min, IMF_mass_max)

    use mod_types
    use mod_SSPs_constants, only : d_l10_mass0
    use mod_interp, only : bracket, interp_lin_lin

    implicit none
    character(len=*), intent(in) :: IMF_file
    integer, intent(in) :: IMF_dim_slope
    real(DPR), dimension(:), intent(in) :: IMF_bin_coeff, IMF_bin_bottom_mass, &
         IMF_bin_slope
    real(DPR), intent(in) :: IMF_mass_min, IMF_mass_max
    real(DPR) :: IMF_normalization
!#......................................................................
    real(DPR) :: l10_mass_min, l10_mass_max, d_l10_mass, l10_mass, d_n
    integer :: n_bins, i_bin
!#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

    l10_mass_min = log10(IMF_mass_min)
    l10_mass_max = log10(IMF_mass_max)
    n_bins = ceiling((l10_mass_max-l10_mass_min)/d_l10_mass0)
    d_l10_mass = (l10_mass_max-l10_mass_min)/n_bins
    IMF_normalization = 0
    do i_bin = 0, n_bins-1
       l10_mass = l10_mass_min+(i_bin+0.5_DPR)*d_l10_mass
       d_n = IMF_d_n(l10_mass, d_l10_mass, IMF_file, IMF_dim_slope, IMF_bin_coeff, &
            IMF_bin_bottom_mass, IMF_bin_slope, &
            IMF_mass_min, IMF_mass_max)
       IMF_normalization = IMF_normalization + d_n * 10**l10_mass
    end do

  end function IMF_normalization

end module mod_IMF
