export Physics
export AbstractMomentumModel
export Momentum
export AbstractTimeModel
export Transient, Steady

"""
    struct Physics{T,F,M,Tu,E,D,BI}
        time::T
        fluid::F
        momentum::M 
        turbulence::Tu 
        energy::E
        domain::D
        boundary_info::BI
    end

XCALibre's parametric Physics type for user-level API. Also used to dispatch flow solvers.

### Fields
- 'time::Union{Steady, Transient}'   -- User-provided time model.
- 'fluid::AbstractFluid'  -- User-provided `Fluid`` model.
- 'momentum'  -- Momentum model. Currently this is auto-generated by the `Physics` constructor
- 'turbulence::AbstractTurbulenceModel'  -- User-provided `Turbulence`` model.
- 'energy:AbstractEnergyModel'  -- User-provided `Energy` model.
- 'domain::AbstractMesh '  -- User-provided `Mesh`. Must be adapted to target device before constructing a Physics object.
- 'boundary_info::boundary_info'  -- Mesh boundary information.  Auto-generated by the `Physics` constructor

"""
struct Physics{T,F,M,Tu,E,D,BI}
    time::T
    fluid::F
    momentum::M 
    turbulence::Tu 
    energy::E
    domain::D
    boundary_info::BI
end 
Adapt.@adapt_structure Physics

abstract type AbstractTimeModel end

"""
    Transient

Transient model for Physics model API.

### Examples
- `Transient()`
"""
struct Transient <: AbstractTimeModel end
Adapt.@adapt_structure Transient

"""
    Steady

Steady model for Physics model API.

### Examples
- `Steady()`
"""
struct Steady <: AbstractTimeModel end
Adapt.@adapt_structure Steady

abstract type AbstractMomentumModel end
Base.show(io::IO, model::AbstractMomentumModel) = print(io, typeof(model).name.wrapper)


"""
    struct Momentum{V,S,SS} <: AbstractMomentumModel
        U::V 
        p::S 
        sources::SS
    end 

Momentum model containing key momentum fields.

### Fields
- 'U'        -- Velocity VectorField.
- 'p'        -- Pressure ScalarField.
- 'sources'  -- Momentum model sources.

### Examples
- `Momentum(mesh::AbstractMesh)
"""
struct Momentum{V,S,SS} <: AbstractMomentumModel
    U::V 
    p::S 
    sources::SS
end 
Adapt.@adapt_structure Momentum 


Momentum(mesh::AbstractMesh) = begin
    U = VectorField(mesh)
    p = ScalarField(mesh)
    Momentum(U, p, nothing)
end

"""
    Physics(; time, fluid, turbulence, energy, domain)::Physics{T,F,M,Tu,E,D,BI}

`Physics` constructor part of the top-level API. It can be used to define the Physics and models relevant to a simulation. This constructor uses keyword arguments to allow users to fine-tune their simulations, whilst some fields are auto-generated behind the scenes for convenience (`Momentum` and `boundary_info`). Where:

- `time` - specified the time model (Steady or Transient)
- `fluid` - specifies the type of fluid (Incompressible, etc.)
- `turbulence` - specified the Turbulence model
- `energy` - specifies the Energy treatment
- `domain` - provides the mesh to used (must be adapted to the target backend device)

"""
Physics(; time, fluid, turbulence, energy, domain) = begin
    momentum = Momentum(domain)
    fluid = fluid(domain)
    # turbulence = typeof(turbulence)(domain)
    turbulence = turbulence(domain)
    energy = energy(domain, fluid)
    boundary_info = boundary_map(domain)
    Physics(
        time,
        fluid,
        momentum, 
        turbulence, 
        energy,
        domain, 
        boundary_info
    )
end

Base.show(io::IO, model::Physics) = begin
    typeInfo = typeof(model)
    modelDefinition = IOBuffer()
    println(modelDefinition, typeInfo.name.wrapper)
    for params âˆˆ typeInfo.parameters
        println(modelDefinition, "-> ", params.name.wrapper)
    end
    print(io, String(take!(modelDefinition)))
end
