# # Tutorial 2: Reading Meshes with Gmsh
#
# Real FEM problems need meshes with many elements. Creating them manually
# would be tedious! This tutorial shows how to:
# - Read mesh files generated by Gmsh
# - Extract nodes and elements
# - Organize elements by physical groups (for boundary conditions)
#
# ## Prerequisites
#
# - Tutorial 1 (Creating Elements and Fields)
# - Basic understanding of mesh concepts (nodes, elements, connectivity)

using JuliaFEM
using Gmsh  # Mesh generation and reading
using Test

# ## The Mesh File
#
# We're reading a pre-generated mesh file: `reading_gmsh_meshes.msh`
# 
# This mesh was created using the Gmsh.jl recipe in `reading_gmsh_meshes_recipe.jl`.
# The recipe shows how to programmatically generate meshes - very useful for 
# parametric studies!
#
# **Mesh specifications:**
# - Domain: Unit square [0,1] × [0,1]  
# - Elements: 10 Quad4 elements (2×5 structured mesh)
# - Nodes: 18 nodes
# - Physical groups: DOMAIN (volume), BOTTOM/RIGHT/TOP/LEFT (boundaries)

mesh_file = joinpath(@__DIR__, "reading_gmsh_meshes.msh")

# ## Reading the Mesh with Gmsh.jl
#
# Gmsh.jl provides a Julia interface to the Gmsh API. We can read mesh files
# and extract all the data we need.

# Initialize Gmsh
gmsh.initialize()

# Open and read the mesh file
gmsh.open(mesh_file)

# ## Extracting Nodes
#
# Nodes are the points in space where we'll compute field values.
# In Gmsh, nodes are returned as:
# - `nodeTags`: Node IDs (integers)
# - `coord`: Coordinates (x, y, z) as a flat vector

nodeTags, coord, parametricCoord = gmsh.model.mesh.getNodes()

# The `coord` vector is flat: [x1, y1, z1, x2, y2, z2, ...]
# Let's reshape it into a more convenient format:

num_nodes = length(nodeTags)
nodes = Dict{Int,Vector{Float64}}()

for i in 1:num_nodes
    node_id = nodeTags[i]
    x = coord[3*(i-1)+1]
    y = coord[3*(i-1)+2]
    z = coord[3*(i-1)+3]
    nodes[node_id] = [x, y, z]
end

# Now we have a dictionary mapping node IDs to coordinates!

# ## Extracting Elements
#
# Elements connect nodes and define the interpolation within each region.
# In Gmsh, we need to query elements by entity (surfaces, volumes, etc.).

# Get all 2D entities (surfaces in our case)
entities = gmsh.model.getEntities(2)  # 2 = dimension for surfaces

# For each entity, get the elements
all_elements = Element[]

for entity in entities
    dim, tag = entity

    # Get element data for this entity
    elemTypes, elemTags, nodeTags_elem = gmsh.model.mesh.getElements(dim, tag)

    # Loop over element types (we have Quad4 = type 3)
    for (elemType, elemTag, elemNodeTags) in zip(elemTypes, elemTags, nodeTags_elem)

        # Get element properties
        elemName, elemDim, order, numNodes, localNodeCoord, numPrimaryNodes =
            gmsh.model.mesh.getElementProperties(elemType)

        # Create JuliaFEM elements
        for i in 1:length(elemTag)
            # Extract connectivity for this element
            start_idx = (i - 1) * numNodes + 1
            end_idx = i * numNodes
            # Convert UInt64 to Int for JuliaFEM compatibility
            connectivity = Tuple(Int(tag) for tag in elemNodeTags[start_idx:end_idx])

            # Map Gmsh element type to JuliaFEM element type
            if elemType == 3  # Gmsh Quad4
                local elem = Element(Quad4, connectivity)
            else
                @warn "Unknown element type: $elemType ($elemName)"
                continue
            end

            # Add geometry field
            update!(elem, "geometry", nodes)

            push!(all_elements, elem)
        end
    end
end

# ## Physical Groups
#
# Physical groups let us organize elements by region (for materials)
# and boundaries (for boundary conditions).

# Get all physical groups
physicalGroups = gmsh.model.getPhysicalGroups()

# We can query which elements belong to each physical group
# (This would be used to create separate Problems for different regions)

# ## Cleanup
gmsh.finalize()

# ## Validation Tests
#
# Let's verify the mesh was read correctly:

@testset "Gmsh Mesh Reading" begin
    # Correct number of nodes (2×5 structured quad mesh has 3×6 nodes)
    @test length(nodes) == 18

    # Correct number of elements (2 × 5 = 10 quads)
    @test length(all_elements) == 10

    # All elements should be Quad4
    @test all(e -> typeof(e.properties) == Quad4, all_elements)

    # Node coordinates should be in [0,1] × [0,1]
    for (node_id, coord) in nodes
        @test 0.0 <= coord[1] <= 1.0  # x ∈ [0,1]
        @test 0.0 <= coord[2] <= 1.0  # y ∈ [0,1]
        @test coord[3] == 0.0         # z = 0 (2D mesh)
    end

    # Each element should have 4 nodes
    for element in all_elements
        @test length(element.connectivity) == 4
    end
end

# ## What We Learned
#
# ✅ How to read Gmsh .msh files using Gmsh.jl  
# ✅ Extract nodes (IDs and coordinates)  
# ✅ Extract elements (type, connectivity)  
# ✅ Map Gmsh element types to JuliaFEM types  
# ✅ Physical groups organize elements for BCs and materials  
# ✅ Mesh generation recipes document mesh creation
#
# ## Why This Approach?
#
# **Pre-generated mesh files** (not generating in tests):
# - Tests are reliable (don't fail due to mesh generation)
# - Tests run faster (no mesh generation overhead)
# - Mesh is version-controlled (reproducible)
#
# **Recipe files** (showing how mesh was made):
# - Educational (teach Gmsh.jl)
# - Documented (can regenerate if needed)
# - Transparent (know exactly what the mesh is)
#
# ## Next Steps
#
# - **Tutorial 3:** Basis functions and shape function evaluation
# - **Tutorial 4:** Solving 1D elasticity (1-element validation)
# - **Tutorial 5:** Solving 2D elasticity (realistic 10-element problem)
#
# ## Real-World Impact
#
# JuliaFEM is being used to **validate other FEM software**! 
# See [Issue #265](https://github.com/JuliaFEM/JuliaFEM.jl/issues/265) where
# a user validated their FEM code against JuliaFEM results. This is beautiful -
# we're useful as a reference implementation even after years!
#
# That's why we include **1-element validation tests** with analytical solutions
# and **nice round numbers** (parameters chosen so results are integers). Makes
# it easy to verify by hand calculation or compare with other codes.
