module TreeClass
   use, intrinsic :: iso_fortran_env
   use ArrayClass
   implicit none

   type :: Nodep_
      type(Node_), pointer :: Nodep
   end type

   type :: Node_
      type(Node_), pointer    :: Parent
      type(Nodep_), allocatable :: Child(:)
      real(real64)         :: coord(3)
      real(real64)         :: vector(3)
      character*200   :: Name
      real(real64) :: fpval
      integer(int32)         :: intval
      integer(int32)         :: ID
   contains
      procedure, public :: Init => InitializeNode
      procedure, public :: create => CreateNode
   end type

   type :: Tree_
      type(Nodep_), allocatable :: Node(:)
      integer(int32) :: SortedUntil
   contains
      procedure, public :: Init => InitializeTree
      procedure, public :: Add => AddNodeInTree
      procedure, public :: cut => cutNodeInTree
      procedure, public :: show => showTree
      procedure, public :: NumOfTree => NumOfTree
      procedure, public :: parentNodeID => parentNodeIDTree
      procedure, public :: countIfParentIDis => countIfParentIDis
      procedure, public :: setVisualMap => setVisualMapTree
   end type

contains

! #######################################################
   subroutine InitializeNode(obj)
      class(Node_), intent(inout) :: obj

      allocate (obj%Child(1))
      obj%coord(:) = 0.0d0
      obj%vector(:) = 0.0d0

   end subroutine InitializeNode
! #######################################################

! #######################################################
   subroutine CreateNode(obj, parent, Name)
      class(Node_), target, intent(inout) :: obj
      class(Node_), target, optional, intent(inout) :: parent
      character(*), intent(in)    :: Name

      call obj%init()
      if (present(parent)) then
         obj%Parent => parent
      else
         obj%Parent => obj
      end if
      obj%Name = Name

   end subroutine
! #######################################################

! #######################################################
! #######################################################

! #######################################################
   subroutine InitializeTree(obj, NumOfNode)
      class(Tree_), intent(inout)::obj
      integer(int32), optional, intent(in)::NumOfNode
      integer(int32) :: i, n, num

      num = input(default=10000, option=NumOfNode)
      if (.not. allocated(obj%Node)) then
         allocate (obj%Node(num))
      end if

      obj%SortedUntil = 0

   end subroutine
! #######################################################

! #######################################################
   subroutine AddNodeInTree(obj, NodeObj)
      class(Tree_), intent(inout)::obj
      class(Node_), target, intent(in)::NodeObj

      obj%SortedUntil = obj%SortedUntil + 1
      obj%Node(obj%SortedUntil)%Nodep => NodeObj

      print *, "A Node is imported. now number of node is ", obj%SortedUntil &
         , "| Name = ", obj%Node(obj%SortedUntil)%Nodep%Name

   end subroutine
! #######################################################

! #######################################################
   subroutine cutNodeInTree(obj, NodeObj)
      class(Tree_), intent(inout)::obj
      class(Node_), target, intent(in)::NodeObj
      integer(int32) :: i, num

      num = obj%SortedUntil
      do i = 1, obj%SortedUntil
         if (obj%Node(i)%Nodep%Name == NodeObj%Name) then

            print *, "A Node is cut. now number of node is ", obj%SortedUntil &
               , "cut node is : ", obj%Node(i)%Nodep%Name, "Node id : ", i
            nullify (obj%Node(i)%Nodep)
            num = num - 1
         end if

      end do
      obj%SortedUntil = num

   end subroutine
! #######################################################

! #######################################################
   subroutine showTree(obj)
      class(Tree_), intent(in)::obj

      integer(int32) :: i, n
      real(real64) :: x, y, vx, vy

      print *, "Num of Tree = ", obj%NumOfTree()
      do i = 1, obj%NumOfTree()
         print *, "Parent Node ID = ", obj%parentNodeID(ParentID=i)
      end do
      call obj%setVisualMap()

      do i = 1, obj%SortedUntil
         print *, "child = ", obj%Node(i)%Nodep%Name &
            , " | parent = ", obj%Node(i)%Nodep%parent%Name
      end do

      do i = 1, obj%SortedUntil
         print *, obj%Node(i)%Nodep%coord(:), obj%Node(i)%Nodep%vector(:)
      end do

   end subroutine
! #######################################################

! #######################################################
   function NumOfTree(obj) result(num)
      class(Tree_), intent(in)::obj
      integer(int32) :: i, n, num

      num = 0
      do i = 1, obj%SortedUntil
         if (obj%Node(i)%Nodep%Name == obj%Node(i)%Nodep%parent%Name) then
            num = num + 1
         end if
      end do

   end function
! #######################################################

! #######################################################
   function countIfParentIDis(obj, ParentID) result(num)
      class(Tree_), intent(in)::obj
      integer(int32), intent(in)::ParentID
      integer(int32) :: i, n, num

      num = 0
      do i = 1, obj%SortedUntil
         if (obj%Node(ParentID)%Nodep%Name == obj%Node(i)%Nodep%parent%Name) then
            num = num + 1
         end if
      end do

   end function
! #######################################################

! #######################################################
   function parentNodeIDTree(obj, ParentID) result(NodeID)
      class(Tree_), intent(in)::obj
      integer(int32), optional, intent(in)::ParentID
      integer(int32) :: i, n, num, pid, NodeID

      pid = input(default=1, option=ParentID)
      num = 0
      do i = 1, obj%SortedUntil
         if (obj%Node(i)%Nodep%Name == obj%Node(i)%Nodep%parent%Name) then
            num = num + 1
            if (pid == num) then
               NodeID = i
               return
            end if
         end if
      end do
   end function
! #######################################################

! #######################################################
   subroutine setVisualMapTree(obj)
      class(Tree_), intent(in)::obj

      integer(int32) :: i, j, n, num, num_i, num_of_node
      real(real64) :: vec(3), pi, theta, dtheta
      real(real64), allocatable :: rotate(:, :)

      allocate (rotate(3, 3))

      num = 0
      pi = 3.14159d0
      do i = 1, obj%SortedUntil
         if (obj%Node(i)%Nodep%Name == obj%Node(i)%Nodep%parent%Name) then
            num = num + 1
            num_i = 0
            ! primary node
            ! set x(:)=0
            obj%Node(i)%Nodep%coord(:) = 0.0d0
            num_of_node = obj%countIfParentIDis(parentID=i)
            dtheta = pi/dble(num_of_node)/2.0d0
            rotate(:, :) = 0.0d0
            theta = 0.0d0
            do j = 1, obj%SortedUntil
               if (obj%Node(i)%Nodep%Name == obj%Node(j)%Nodep%parent%Name) then
                  theta = theta + dtheta
                  vec(:) = 0.0d0
                  vec(1) = 1.0d0
                  rotate(3, 3) = 1.0d0
                  rotate(1, 1) = cos(theta)
                  rotate(1, 2) = -sin(theta)
                  rotate(2, 1) = sin(theta)
                  rotate(2, 2) = cos(theta)
                  vec(:) = matmul(rotate, vec)
                  obj%Node(j)%Nodep%coord(:) = obj%Node(i)%Nodep%parent%coord(:) + vec(:)
                  obj%Node(j)%Nodep%vector(:) = vec(:)
               end if
            end do
         else
            cycle
         end if
      end do

   end subroutine
! #######################################################

end module

