module classes_floor_penetration

use, intrinsic :: iso_fortran_env, only: DP => REAL64
use data_constants, only: num_dimensions
use procedures_errors, only: error_exit
use procedures_checks, only: check_array_size, check_positive
use procedures_centered_block_micro, only: set_from_corner, set_from_wall

implicit none

private

    type, abstract, public :: Abstract_Floor_Penetration
    contains
        procedure(Abstract_get_height), deferred :: get_height
        procedure(Abstract_meet), deferred :: meet
    end type Abstract_Floor_Penetration

    abstract interface

        pure real(DP) function Abstract_get_height(this)
        import :: DP, Abstract_Floor_Penetration
            class(Abstract_Floor_Penetration), intent(in) :: this
        end function Abstract_get_height

        pure subroutine Abstract_meet(this, overlap, shortest_vector_from_floor, &
            position_from_floor)
        import :: DP, num_dimensions, Abstract_Floor_Penetration
            class(Abstract_Floor_Penetration), intent(in) :: this
            logical, intent(out) :: overlap
            real(DP), intent(out) :: shortest_vector_from_floor(num_dimensions)
            real(DP), intent(in) :: position_from_floor(num_dimensions)
        end subroutine Abstract_meet

    end interface

    type, extends(Abstract_Floor_Penetration), public :: Flat_Floor_Penetration
    contains
        procedure :: get_height => Flat_get_height
        procedure :: meet => Flat_meet
    end type Flat_Floor_Penetration

    !> This is a flat floor with a rounded block at the center, cf.
    !> modules/environment/walls/centered_block_penetration.tex which shows the right half.
    !> When using [[Block_meet]], if a position is in a blue area,
    !> shortestVectorFromFloor's origin will be on a rounder corner. Otherwise (i.e. white area),
    !> it will be on a flat portion.
    type, extends(Abstract_Floor_Penetration), public :: Centered_Block_Penetration
    private
        real(DP), dimension(2) :: size = 0._DP
        real(DP) :: radius = 0._DP
        real(DP), dimension(2) :: lower_in = 0._DP, lower_out = 0._DP, upper_in = 0._DP, &
            upper_out = 0._DP! right centers
    contains
        procedure :: set => Block_set
        procedure :: get_height => Block_get_height
        procedure :: meet => Block_meet
    end type Centered_Block_Penetration

    type, extends(Abstract_Floor_Penetration), public :: Null_Floor_Penetration
    contains
        procedure :: get_height => Null_get_height
        procedure :: meet => Null_meet
    end type Null_Floor_Penetration

contains

!implementation Flat_Floor_Penetration

    pure real(DP) function Flat_get_height(this) result(height)
        class(Flat_Floor_Penetration), intent(in) :: this

        height = 0._DP
    end function Flat_get_height

    pure subroutine Flat_meet(this, overlap, shortest_vector_from_floor, position_from_floor)
        class(Flat_Floor_Penetration), intent(in) :: this
        logical, intent(out) :: overlap
        real(DP), intent(out) :: shortest_vector_from_floor(num_dimensions)
        real(DP), intent(in) :: position_from_floor(num_dimensions)

        shortest_vector_from_floor = [0._DP, 0._DP, position_from_floor(3)]
        if (shortest_vector_from_floor(3) < 0._DP) then
            overlap = .true.
        else
            overlap = .false.
        end if
    end subroutine Flat_meet

!end implementation Flat_Floor_Penetration

!implementation Centered_Block_Penetration

    subroutine Block_set(this, size, radius)
        class(Centered_Block_Penetration), intent(out) :: this
        real(DP), intent(in) :: size(:), radius

        call check_array_size("Centered_Block_Penetration: set", "size", size, 2)
        call check_positive("Centered_Block_Penetration: set", "size", size)
        this%size = size
        call check_positive("Centered_Block_Penetration: set", "radius", radius)
        if (this%size(1) < 2.0_DP*radius) then
            call error_exit("Centered_Block_Penetration: set: 2*radius > size_x.")
        end if
        if (this%size(2) < 2.0_DP*radius) then
            call error_exit("Centered_Block_Penetration: set: 2*radius > size_z.")
        end if
        this%radius = radius

        this%upper_in = [this%size(1)/2._DP, this%size(2)]
        this%upper_out = [this%size(1)/2._DP - this%radius, this%size(2) - this%radius]
        this%lower_in  = [this%size(1)/2._DP + this%radius, this%radius]
        this%lower_out = [this%size(1)/2._DP, 0._DP]
    end subroutine Block_set

    pure real(DP) function Block_get_height(this) result(height)
        class(Centered_Block_Penetration), intent(in) :: this

        height = this%size(2)
    end function Block_get_height

    pure subroutine Block_meet(this, overlap, shortest_vector_from_floor, position_from_floor)
        class(Centered_Block_Penetration), intent(in) :: this
        logical, intent(out) :: overlap
        real(DP), intent(out) :: shortest_vector_from_floor(num_dimensions)
        real(DP), intent(in) :: position_from_floor(num_dimensions)

        real(DP), dimension(2) :: shortest_vector, position_13

        if (0._DP < position_from_floor(1)) then
            position_13 = [+position_from_floor(1), position_from_floor(3)]
        else
            position_13 = [-position_from_floor(1), position_from_floor(3)]
        end if

        if (all(this%upper_out < position_13)) then !
            call set_from_corner(shortest_vector, this%upper_out, this%radius, position_13)
        else if (all(position_13 < this%lower_in)) then
            call set_from_corner(shortest_vector, this%lower_in, this%radius, position_13)
        else if (position_13(2) < this%size(2)/2._DP) then
            !> Frame: (0, \vec{e}_x, \vec{e}_z)
            call set_from_wall(shortest_vector, this%lower_out, position_13)
        else
            !> Frame: (0^\prime, -\vec{e}_x, -\vec{e}_z)
            call set_from_wall(shortest_vector, -this%upper_in, -position_13)
            shortest_vector = -shortest_vector
        end if

        if (any(shortest_vector < 0._DP )) then
            overlap = .true.
        else
            overlap = .false.
        end if
        shortest_vector_from_floor = [shortest_vector(1), 0._DP, shortest_vector(2)]
    end subroutine Block_meet

!implementation Centered_Block_Penetration

!implementation Null_Floor_Penetration

    pure real(DP) function Null_get_height(this) result(height)
        class(Null_Floor_Penetration), intent(in) :: this
        height = 0._DP
    end function Null_get_height

    pure subroutine Null_meet(this, overlap, shortest_vector_from_floor, position_from_floor)
        class(Null_Floor_Penetration), intent(in) :: this
        logical, intent(out) :: overlap
        real(DP), intent(out) :: shortest_vector_from_floor(num_dimensions)
        real(DP), intent(in) :: position_from_floor(num_dimensions)
        shortest_vector_from_floor = 0._DP !Is it what I expect from a null object?
        overlap = .false.
    end subroutine Null_meet

!end implementation Null_Floor_Penetration

end module classes_floor_penetration
