! Copyright © 2021-2023 Dynare Team
!
! This file is part of Dynare.
!
! Dynare is free software: you can redistribute it and/or modify
! it under the terms of the GNU General Public License as published by
! the Free Software Foundation, either version 3 of the License, or
! (at your option) any later version.
!
! Dynare 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 General Public License for more details.
!
! You should have received a copy of the GNU General Public License
! along with Dynare.  If not, see <https://www.gnu.org/licenses/>.

module tensors
   use iso_c_binding
   use partitions
   ! use matlab_mat
   implicit none (type, external)

   ! A type to contain a folded or unfolded tensor [gᵥᵐ]
   type tensor
      real(real64), pointer, contiguous, dimension(:,:) :: m
      ! real(real64), dimension(:,:), allocatable :: m
   end type tensor

   ! A type to contain the unfolded tensor [gᵥᵐ]. For each offset j, ind(j)
   ! contains the associated unfolded index and s(j) stores the number of state
   ! variables in the index, i.e. if v = (x,u[,σ]) where x is the s-sized state
   ! variables vector and ind(j) = (α₁,…,αₘ), s(j) stores the number of
   ! coordinates αᵢ such that  αᵢ ≤ s.
   type unfolded_tensor
      type(index), dimension(:), allocatable :: ind
      integer, dimension(:), allocatable :: s
      real(real64), dimension(:,:), pointer, contiguous :: m
   end type unfolded_tensor

   ! A type to contain the folded tensor [gᵥᵐ]. For each offset j, ind(j)
   ! contains the associated folded index, count(j) contains the number of
   ! equivalent unfolded indices and s(j) stores the number of state variables
   ! in the index, i.e. if v = (x,u[,σ]) where x is the s-sized state variables
   ! vector and ind(j) = (α₁,…,αₘ), s(j) stores the number of coordinates αᵢ
   ! such that  αᵢ ≤ s.
   type folded_tensor
      type(index), dimension(:), allocatable :: ind
      integer, dimension(:), allocatable :: count
      integer, dimension(:), allocatable :: s
      real(real64), dimension(:,:), pointer, contiguous :: m
   end type folded_tensor

contains

   ! Fills the ind and s members of a list of unfolded tensors g
   ! with a number of state variables ns. More precisely, g contains the
   ! information associated with the unfolded tensors [gᵥᵐ] with m=1,...,q
   ! 1. Filling g%ind
   ! The unfolded indices of [gᵥᵐ⁺¹] are the unfolded indices of [gᵥᵐ]
   ! with all numbers 1,...,n appended upfront. In other words, any
   ! index of [gᵥᵐ⁺¹] writes (α₁,α) with α₁∈ { 1, ..., n } and α
   ! an index of [gᵥᵐ]
   ! 2. Filling g%s
   ! s(α₁,α) is s(α)+1 if α₁ ≤ ns and s(α) otherwise
   subroutine fill_list_unfolded_tensor(g, s, n)
      type(unfolded_tensor), dimension(:), intent(inout) :: g
      integer, intent(in) :: s, n ! number of state variables and size of v
      integer :: q, m, j, k, l, length
      q = size(g)
      ! Initialization
      m = 1
      length = n
      allocate(g(1)%ind(length), g(1)%s(length))
      do j=1,n
         g(1)%ind(j) = index(1,j)
         if (j<=s) then
            g(1)%s(j) = 1
         else
            g(1)%s(j) = 0
         end if
      end do
      ! Transmission
      do m=2,q
         length = n*length
         allocate(g(m)%ind(length), g(m)%s(length))
         l = 1
         do j=1,n
            do k=1,size(g(m-1)%ind)
               g(m)%ind(l) = index(m)
               g(m)%ind(l)%coor(1) = j
               g(m)%ind(l)%coor(2:m) = g(m-1)%ind(k)%coor
               if (j<=s) then
                  g(m)%s(l) = g(m-1)%s(k)+1
               else
                  g(m)%s(l) = g(m-1)%s(k)
               end if
               l = l+1
            end do
         end do
      end do
   end subroutine fill_list_unfolded_tensor

   ! Allocates the members ind, count and s of a tensor [gᵥᵐ] denoted g
   ! using the Pascal triangle p. n is the size of v.
   subroutine allocate_folded_tensor(g, m, n, p)
      type(folded_tensor), intent(inout) :: g
      integer, intent(in) :: m, n
      type(pascal_triangle),intent(in) :: p
      integer :: d
      d = get(m,n+m-1,p)
      allocate(g%ind(d), g%count(d), g%s(d))
   end subroutine allocate_folded_tensor

   ! Fills the already allocated members ind, count and s of a tensor [gᵥᵐ]
   ! denoted g using the Pascal triangle p. n is the size of v.
   ! 1. Filling g%ind
   ! The algorithm to get the folded index associated with a folded offset
   ! relies on the definition of the lexicographic order.
   ! Consideri appended upfront.ng α = (α₁,…,αₘ) with αᵢ ∈ { 1, ..., n },
   ! the next index α' is such that there exists i that verifies
   ! αⱼ = αⱼ' for all j < i, αᵢ' > αᵢ. Note that all the coordinates
   ! αᵢ', ... , αₘ' need to be as small as the lexicographic order allows
   ! for α' to immediately follow α.
   ! Suppose j is the latest incremented coordinate:
   ! if αⱼ < n, then αⱼ' = αⱼ + 1
   ! otherwise αⱼ = n, set αₖ' =  αⱼ₋₁ + 1 for all k ≥ j-1
   ! if αⱼ₋₁ = n, set j := j-1
   ! otherwise, set j := m
   ! 2. Filling g%count
   ! The algorithm to count the number of equivalent unfolded indices
   ! works as follows. A folded index can be written as
   ! α = (x₁, ..., x₁, ..., xₚ, ..., xₚ) such that x₁ < x₂ < ... < xₚ.
   ! Denote kᵢ the number of coordinates equal to xᵢ.
   ! The number of unfolded indices equivalent to α is
   ! c(α) = ⎛        m        ⎞
   !        ⎝ k₁, k₂, ..., kₚ ⎠
   ! k is an array such that k(ℓ,j) contains the number of coordinates
   ! equal to ℓ for the folded index asociated with offset j.
   ! Suppose j is the latest incremented coordinate.
   ! If αⱼ < n, then αⱼ' = αⱼ + 1, k(αⱼ) := k(αⱼ)-1, k(αⱼ') := k(αⱼ')+1.
   ! In this case, c(α') = c(α)*(k(αⱼ)+1)/k(αⱼ')
   ! otherwise, αⱼ = n: set αₖ' =  αⱼ₋₁ + 1 for all k ≥ j-1,
   ! k(αⱼ₋₁) := k(αⱼ₋₁)-1, k(n) := 0, k(αⱼ₋₁') = m-(j-1)+1
   ! In this case, we compute c(α') with the multinomial formula above
   ! 3. Filling g%s
   ! Suppose j is the latest incremented coordinate.
   ! If αⱼ < n, then αⱼ' = αⱼ + 1: s' = s-1 if αⱼ = ns and s'=s otherwise
   ! Otherwise, αⱼ = n: set αₖ' =  αⱼ₋₁ + 1 for all k ≥ j-1. Thus,
   ! s' = m if αⱼ₋₁ < ns; s'=s-1 if αⱼ₋₁ = ns; s'=s  otherwise
   subroutine fill_folded_tensor(g, k, m, s, n, p)
      type(folded_tensor), intent(inout) :: g
      integer, contiguous, intent(inout) :: k(:,:)
      integer, intent(in) :: m, s, n
      type(pascal_triangle) :: p
      integer :: j, lastinc
      g%ind(1) = index(m, 1)
      g%count(1) = 1
      g%s(1) = m
      k = 0
      k(:,1) = 0
      k(1,1) = m
      j = 2
      lastinc = m
      do while (j <= size(g%ind))
         g%ind(j) = index(g%ind(j-1)%coor)
         k(:,j) = k(:,j-1)
         if (g%ind(j-1)%coor(lastinc) == n) then
            g%ind(j)%coor(lastinc-1:m) = g%ind(j-1)%coor(lastinc-1)+1
            k(g%ind(j-1)%coor(lastinc-1),j) = k(g%ind(j-1)%coor(lastinc-1),j-1)-1
            k(n,j) = 0
            k(g%ind(j)%coor(lastinc-1),j) = m - (lastinc-1) + 1
            g%count(j) = multinomial(k(:,j),m,p)
            if (g%ind(j-1)%coor(lastinc-1) < s) then
               g%s(j) = m
            elseif (g%ind(j-1)%coor(lastinc-1) == s) then
               g%s(j) = g%s(j-1)-1
            else
               g%s(j) = g%s(j-1)
            end if
            if (g%ind(j)%coor(m) == n) then
               lastinc = lastinc-1
            else
               lastinc = m
            end if
         else
            g%ind(j)%coor(lastinc) = g%ind(j-1)%coor(lastinc)+1
            k(g%ind(j)%coor(lastinc),j) = k(g%ind(j)%coor(lastinc),j-1)+1
            g%count(j) = g%count(j-1)*k(g%ind(j-1)%coor(lastinc),j)/k(g%ind(j)%coor(lastinc),j)
            k(g%ind(j-1)%coor(lastinc),j) = k(g%ind(j-1)%coor(lastinc),j-1)-1
            if (g%ind(j-1)%coor(lastinc) == s) then
               g%s(j) = g%s(j-1)-1
            else
               g%s(j) = g%s(j-1)
            end if
         end if
         j = j+1
      end do
   end subroutine

   ! Fills a tensor [gᵥᵐ] given the tensor [gᵥᵐ⁺¹]
   ! 1. Filling g%ind
   ! If (α₁,…,αₘ₊₁) is a folded index for [gᵥᵐ⁺¹], then (α₂,…,αₘ₊₁) is a folded
   ! index of [gᵥᵐ]. The folded indices of [gᵥᵐ] are thus the tails of the first
   ! ⎛ m+n-1 ⎞ folded indices of [gᵥᵐ⁺¹]
   ! ⎝   m   ⎠
   ! 2. Filling g%count
   ! If α=(α₁,…,αₘ₊₁) is a folded index for [gᵥᵐ⁺¹], then α'=(α₂,…,αₘ₊₁) is a
   ! folded index of [gᵥᵐ]. We thus have c(α') = c(α)*k(α₁)/(m+1) and
   ! perform k(α₁):=k(α₁)-1.
   ! 3. Filling g%s
   ! If α=(α₁,…,αₘ₊₁) is a folded index for [gᵥᵐ⁺¹], then α'=(α₂,…,αₘ₊₁) is a
   ! folded index of [gᵥᵐ]. We thus have s(α') = s(α)-1 if α₁ ≤ s and
   ! s(α') = s(α) otherwise
   subroutine fill_folded_tensor_backward(g_m, k, g_mp1, m, s)
      type(folded_tensor), intent(inout) :: g_m ! tensor [gᵥᵐ]
      ! k array from previous fill_folded_tensor_backward or
      ! fill_folded_tensor call. See definition in fill_folded_tensor
      integer, contiguous, intent(inout) :: k(:,:)
      type(folded_tensor), intent(in) :: g_mp1 ! tensor [gᵥᵐ⁺¹]
      integer, intent(in) :: m, s ! s is the number of state variables
      integer :: j
      do j=1,size(g_m%ind)
         g_m%ind(j)%coor = g_mp1%ind(j)%coor(2:m+1)
         g_m%count(j) = g_mp1%count(j)*k(g_mp1%ind(j)%coor(1),j)/(m+1)
         k(g_mp1%ind(j)%coor(1),j) = k(g_mp1%ind(j)%coor(1),j)-1
         if (g_mp1%ind(j)%coor(1) <= s) then
            g_m%s(j) = g_mp1%s(j)-1
         else
            g_m%s(j) = g_mp1%s(j)
         end if
      end do
   end subroutine

   ! Fills the ind, count and s members of a list of folded tensors g
   ! with a number of state variables ns. More precisely, g contains the
   ! information associated with the folded tensors [gᵥᵐ] with m=1,...,q
   subroutine fill_list_folded_tensor(g, s, n, p)
      type(folded_tensor), dimension(:), intent(inout) :: g
      integer, intent(in) :: s, n ! number of state variables and size of v
      type(pascal_triangle) :: p
      integer :: q, m
      integer, dimension(:,:), allocatable :: k
      q = size(g)
      ! Case m = q
      m = q
      call allocate_folded_tensor(g(m), m, n, p)
      allocate(k(n,size(g(m)%ind)))
      call fill_folded_tensor(g(m), k, m, s, n, p)
      ! Case m < q
      do m=q-1,1,-1
         call allocate_folded_tensor(g(m), m, n, p)
         call fill_folded_tensor_backward(g(m), k, g(m+1), m, s)
      end do
   end subroutine fill_list_folded_tensor
end module tensors

! After putting a comment on MATLAB-related lines
! gfortran -o tensors tensors.f08 sort.f08 pascal.f08 partitions.f08
! ./tensors
! program test
!    use pascal
!    use tensors
!    implicit none (type, external)
!    type(pascal_triangle) :: p
!    type(folded_tensor) :: g, h
!    integer :: n, m, s, i, j
!    integer, allocatable, dimension(:,:) :: k

!    n = 3
!    m = 3
!    s = 2
!    p = pascal_triangle(n+m-1)

!    call allocate_folded_tensor(g, m, n, p)
!    allocate(k(n, size(g%ind)))
!    call fill_folded_tensor(g, k, c_null_ptr, m, s, n, p)

!    ! List of folded indices, counts of equivalent unfolded indices
!    ! and counts of state variables of [gᵥᵐ]
!    ! Folded indices and offsets
!    ! 0,0,0  1     1,1,1   7      2,2,2   10
!    ! 0,0,1  2     1,1,2   8
!    ! 0,0,2  3     1,2,2   9
!    ! 0,1,1  4
!    ! 0,1,2  5
!    ! 0,2,2  6
!    do i=1, size(k,2)
!       print '(3i2)', (g%ind(i)%coor(j), j=1,m)
!       print '(i2)', g%count(i)
!       print '(i2)', g%s(i)
!    end do

!    call allocate_folded_tensor(h, m-1, n, p)
!    call fill_folded_tensor_backward(h, k, g, c_null_ptr, m-1, s)
!    ! List of folded indices, counts of equivalent unfolded indices
!    ! and counts of state variables of [gᵥᵐ⁻¹]
!    do i=1, size(h%ind)
!       print '(2i2)', (h%ind(i)%coor(j), j=1,m-1)
!       print '(i2)', h%count(i)
!       print '(i2)', h%s(i)
!    end do

! end program test
