module procedures_environment_factory

use data_input_prefixes, only: environment_prefix
use json_module, only: json_file
use procedures_errors, only: error_exit, warning_continue
use classes_periodic_box, only: Abstract_Periodic_Box
use procedures_boxes_factory, only: boxes_create => create, boxes_destroy => destroy
use procedures_beta_pressure_factory, only: beta_pressure_create => create, &
    beta_pressure_destroy => destroy
use procedures_temperature_factory, only: temperature_create => create, &
    temperature_destroy => destroy
use classes_field_expression, only: Abstract_Field_Expression
use classes_parallelepiped_domain, only: Abstract_Parallelepiped_Domain
use procedures_fields_factory, only: fields_create => create, fields_destroy => destroy
use procedures_permittivity_factory, only: permittivity_create => create, &
    permittivity_destroy => destroy
use classes_floor_penetration, only: Abstract_Floor_Penetration
use classes_visitable_walls, only: Abstract_Visitable_Walls
use procedures_walls_factory, only: walls_create => create, walls_destroy => destroy
use types_environment_wrapper, only: Environment_Wrapper
use procedures_environment_inquirers, only: periodicity_is_xyz, periodicity_is_xy, &
    total_volume_can_change, apply_external_field, use_walls
use procedures_hard_core_factory, only: hard_core_create => create, hard_core_destroy => destroy

implicit none

private
public :: create, destroy

contains

    subroutine create(environment, generating_data, unique_box)
        type(Environment_Wrapper), intent(out) :: environment
        type(json_file), intent(inout) :: generating_data
        logical, optional, intent(in) :: unique_box

        class(Abstract_Field_Expression), allocatable :: field_expression
        class(Abstract_Floor_Penetration), allocatable :: floor_penetration
        logical :: field_applied

        call boxes_create(environment%periodic_boxes, generating_data, environment_prefix, &
            unique_box)
        call beta_pressure_create(environment%beta_pressure, &
            total_volume_can_change(generating_data, environment_prefix), generating_data, &
                environment_prefix)
        call temperature_create(environment%temperature, generating_data, environment_prefix)
        field_applied = apply_external_field(generating_data, environment_prefix)
        call permittivity_create(environment%permittivity, generating_data, environment_prefix)
        call walls_create(floor_penetration, generating_data, environment_prefix)
        call hard_core_create(environment%wall_min_distance, use_walls(floor_penetration), &
            generating_data, environment_prefix//"Walls.")
        call walls_create(environment%visitable_walls, environment%periodic_boxes, &
            floor_penetration,environment%wall_min_distance, generating_data, environment_prefix)
        call walls_destroy(floor_penetration)
        call fields_create(field_expression, environment%permittivity, field_applied, &
            generating_data, environment_prefix)
        call boxes_create(environment%fields_domain, environment%periodic_boxes, environment%&
            visitable_walls, field_applied, generating_data, environment_prefix//&
            "External Field.")
        call fields_create(environment%external_fields, environment%fields_domain, &
            field_expression, field_applied)
        call fields_destroy(field_expression)
        call boxes_create(environment%reciprocal_lattices, environment%periodic_boxes, &
            generating_data, environment_prefix)
        if (all(periodicity_is_xyz(environment%periodic_boxes))) then
            call boxes_create(environment%accessible_domains, environment%periodic_boxes, &
                needed=.true.)
        else if (all(periodicity_is_xy(environment%periodic_boxes))) then
            call boxes_create(environment%accessible_domains, environment%periodic_boxes, &
                environment%visitable_walls, needed=.true.)
        else
            call error_exit("procedures_environment_factory: create: "//&
                "box periodicity is unknown.")
        end if

        call check(environment%periodic_boxes, environment%visitable_walls)
    end subroutine create

    subroutine check(periodic_boxes, visitable_walls)
        class(Abstract_Periodic_Box), intent(in) :: periodic_boxes(:)
        class(Abstract_Visitable_Walls), intent(in) :: visitable_walls(:)

        if (all(periodicity_is_xyz(periodic_boxes)) .and. all(use_walls(visitable_walls))) then
            call warning_continue("procedures_environment_factory: check: "//&
                "periodicity is XYZ but walls are used.")
        end if
        if (all(periodicity_is_xy(periodic_boxes)) .and. all(.not.use_walls(visitable_walls))) then
            call warning_continue("procedures_environment_factory: check: "//&
                "periodicity is XY but walls are not used.")
        end if
    end subroutine check

    subroutine destroy(environment)
        type(Environment_Wrapper), intent(inout) :: environment

        call boxes_destroy(environment%accessible_domains)
        call boxes_destroy(environment%reciprocal_lattices)
        call fields_destroy(environment%external_fields)
        call boxes_destroy(environment%fields_domain)
        call walls_destroy(environment%visitable_walls)
        call hard_core_destroy(environment%wall_min_distance)
        call permittivity_destroy(environment%permittivity)
        call temperature_destroy(environment%temperature)
        call beta_pressure_destroy(environment%beta_pressure)
        call boxes_destroy(environment%periodic_boxes)
    end subroutine destroy

end module procedures_environment_factory
