module classes_parallelepiped_domain

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 classes_periodic_box, only: Abstract_Periodic_Box
use classes_visitable_walls, only: Abstract_Visitable_Walls
use procedures_parallelepiped_domain_micro, only: point_is_inside_box, is_inside_included, &
    is_inside_excluded

implicit none

private

    type, abstract, public :: Abstract_Parallelepiped_Domain
    private
        class(Abstract_Periodic_Box), pointer :: periodic_box
    contains
        procedure(Abstract_destroy), deferred :: destroy
        procedure(Abstract_get_origin), deferred :: get_origin
        procedure(Abstract_get_size), deferred :: get_size
        procedure :: is_boxed => Abstract_is_boxed
        procedure :: is_inside => Abstract_is_inside
    end type Abstract_Parallelepiped_Domain

    abstract interface

        subroutine Abstract_destroy(this)
        import :: Abstract_Parallelepiped_Domain
            class(Abstract_Parallelepiped_Domain), intent(inout) :: this
        end subroutine Abstract_destroy

        pure function Abstract_get_origin(this) result(origin)
        import :: Abstract_Parallelepiped_Domain, DP, num_dimensions
            class(Abstract_Parallelepiped_Domain), intent(in) :: this
            real(DP) :: origin(num_dimensions)
        end function Abstract_get_origin

        pure function Abstract_get_size(this) result(size)
        import :: Abstract_Parallelepiped_Domain, DP, num_dimensions
            class(Abstract_Parallelepiped_Domain), intent(in) :: this
            real(DP) :: size(num_dimensions)
        end function Abstract_get_size

    end interface

    type, extends(Abstract_Parallelepiped_Domain), public :: Concrete_Parallelepiped_Domain
    private
        real(DP), dimension(num_dimensions) :: origin = 0._DP, size = 0._DP
    contains
        procedure :: construct => Parallelepiped_construct
        procedure :: destroy => Parallelepiped_destroy
        procedure :: get_origin => Parallelepiped_get_origin
        procedure :: get_size => Parallelepiped_get_size
    end type Concrete_Parallelepiped_Domain

    type, extends(Abstract_Parallelepiped_Domain), public :: Boxed_Parallelepiped_Domain
    contains
        procedure :: construct => Boxed_construct
        procedure :: destroy => Boxed_destroy
        procedure :: get_origin => Boxed_get_origin
        procedure :: get_size => Boxed_get_size
    end type Boxed_Parallelepiped_Domain

    type, extends(Abstract_Parallelepiped_Domain), public :: Walled_Parallelepiped_Domain
    private
        real(DP) :: size_3
    contains
        procedure :: construct => Walled_construct
        procedure :: destroy => Walled_destroy
        procedure :: get_origin => Walled_get_origin
        procedure :: get_size => Walled_get_size
    end type Walled_Parallelepiped_Domain

    type, extends(Abstract_Parallelepiped_Domain), public :: Null_Parallelepiped_Domain
    contains
        procedure :: construct => Null_construct
        procedure :: destroy => Null_destroy
        procedure :: get_origin => Null_get_origin
        procedure :: get_size => Null_get_size
        procedure :: is_boxed => Null_is_boxed
        procedure :: is_inside => Null_is_inside
    end type Null_Parallelepiped_Domain

contains

!implementation Abstract_Parallelepiped_Domain

    pure logical function Abstract_is_boxed(this) result(is_boxed)
        class(Abstract_Parallelepiped_Domain), intent(in) :: this

        real(DP), dimension(num_dimensions) :: box_origin, corner_m, corner_p

        box_origin = 0._DP
        corner_m = this%get_origin() - this%get_size()/2._DP
        corner_p = this%get_origin() + this%get_size()/2._DP
        is_boxed = point_is_inside_box(box_origin, this%periodic_box%get_size(), corner_m, &
            is_inside_included) .and. point_is_inside_box(box_origin, this%periodic_box%get_size(),&
            corner_p, is_inside_included)
    end function Abstract_is_boxed

    pure logical function Abstract_is_inside(this, position) result(is_inside)
        class(Abstract_Parallelepiped_Domain), intent(in) :: this
        real(DP), intent(in) :: position(:)

        is_inside = point_is_inside_box(this%get_origin(), this%get_size(), position, &
            is_inside_excluded)
    end function Abstract_is_inside

!end implementation Abstract_Parallelepiped_Domain

!implementation Concrete_Parallelepiped_Domain

    subroutine Parallelepiped_construct(this, periodic_box, origin, size)
        class(Concrete_Parallelepiped_Domain), intent(out) :: this
        class(Abstract_Periodic_Box), target, intent(in) :: periodic_box
        real(DP), intent(in) :: origin(:), size(:)

        this%periodic_box => periodic_box
        call check_array_size("Concrete_Parallelepiped_Domain", "origin", origin, num_dimensions)
        this%origin = origin
        call check_array_size("Concrete_Parallelepiped_Domain", "size", size, num_dimensions)
        call check_positive("Concrete_Parallelepiped_Domain", "size", size)
        this%size = size
    end subroutine Parallelepiped_construct

    subroutine Parallelepiped_destroy(this)
        class(Concrete_Parallelepiped_Domain), intent(inout) :: this

        this%periodic_box => null()
    end subroutine Parallelepiped_destroy

    pure function Parallelepiped_get_origin(this) result(origin)
        class(Concrete_Parallelepiped_Domain), intent(in) :: this
        real(DP) :: origin(num_dimensions)

        origin = this%periodic_box%folded(this%origin)
    end function Parallelepiped_get_origin

    pure function Parallelepiped_get_size(this) result(size)
        class(Concrete_Parallelepiped_Domain), intent(in) :: this
        real(DP) :: size(num_dimensions)

        size = this%size
    end function Parallelepiped_get_size

!end implementation Concrete_Parallelepiped_Domain

!implementation Boxed_Parallelepiped_Domain

    subroutine Boxed_construct(this, periodic_box)
        class(Boxed_Parallelepiped_Domain), intent(out) :: this
        class(Abstract_Periodic_Box), target, intent(in) :: periodic_box

        this%periodic_box => periodic_box
    end subroutine Boxed_construct

    subroutine Boxed_destroy(this)
        class(Boxed_Parallelepiped_Domain), intent(inout) :: this

        this%periodic_box => null()
    end subroutine Boxed_destroy

    pure function Boxed_get_origin(this) result(origin)
        class(Boxed_Parallelepiped_Domain), intent(in) :: this
        real(DP) :: origin(num_dimensions)

        origin = 0._DP
    end function Boxed_get_origin

    pure function Boxed_get_size(this) result(size)
        class(Boxed_Parallelepiped_Domain), intent(in) :: this
        real(DP) :: size(num_dimensions)

        size = this%periodic_box%get_size()
    end function Boxed_get_size

!end implementation Boxed_Parallelepiped_Domain

!implementation Walled_Parallelepiped_Domain

    subroutine Walled_construct(this, periodic_box, visitable_walls)
        class(Walled_Parallelepiped_Domain), intent(inout) :: this
        class(Abstract_Periodic_Box), target, intent(in) :: periodic_box
        class(Abstract_Visitable_Walls), intent(in) :: visitable_walls

        this%periodic_box => periodic_box
        this%size_3 = visitable_walls%get_gap_radii()
    end subroutine Walled_construct

    subroutine Walled_destroy(this)
        class(Walled_Parallelepiped_Domain), intent(inout) :: this

        this%periodic_box => null()
    end subroutine Walled_destroy

    pure function Walled_get_origin(this) result(origin)
        class(Walled_Parallelepiped_Domain), intent(in) :: this
        real(DP) :: origin(num_dimensions)

        origin = 0._DP
    end function Walled_get_origin

    pure function Walled_get_size(this) result(size)
        class(Walled_Parallelepiped_Domain), intent(in) :: this
        real(DP) :: size(num_dimensions)

        size = [reshape(this%periodic_box%get_size(), [2]), this%size_3]
    end function Walled_get_size

!end implementation Walled_Parallelepiped_Domain

!implementation Null_Parallelepiped_Domain

    subroutine Null_construct(this, periodic_box, origin, size)
        class(Null_Parallelepiped_Domain), intent(out) :: this
        class(Abstract_Periodic_Box), target, intent(in) :: periodic_box
        real(DP), intent(in) :: origin(:), size(:)
    end subroutine Null_construct

    subroutine Null_destroy(this)
        class(Null_Parallelepiped_Domain), intent(inout) :: this
    end subroutine Null_destroy

    pure function Null_get_origin(this) result(origin)
        class(Null_Parallelepiped_Domain), intent(in) :: this
        real(DP) :: origin(num_dimensions)
        origin = 0._DP
    end function Null_get_origin

    pure function Null_get_size(this) result(size)
        class(Null_Parallelepiped_Domain), intent(in) :: this
        real(DP) :: size(num_dimensions)
        size = 0._DP
    end function Null_get_size

    pure logical function Null_is_boxed(this) result(is_boxed)
        class(Null_Parallelepiped_Domain), intent(in) :: this
        is_boxed = .true.
    end function Null_is_boxed

    pure function Null_is_inside(this, position) result(is_inside)
        class(Null_Parallelepiped_Domain), intent(in) :: this
        real(DP), intent(in) :: position(:)
        logical :: is_inside
        is_inside = .false.
    end function Null_is_inside

!end implementation Null_Parallelepiped_Domain

end module classes_parallelepiped_domain
