#! /usr/bin/env python

import os
import pathlib
import sys

import waves

# Accept command line options with fall back default values
AddOption(
    "--build-dir",
    dest="build_directory",
    default="build",
    nargs=1,
    type="string",
    action="store",
    metavar="DIR",
    help="SCons build (variant) root directory. Relative or absolute path. (default: '%default')",
)
AddOption(
    "--unconditional-build",
    dest="unconditional_build",
    default=False,
    action="store_true",
    help="Boolean to force building of conditionally ignored targets. (default: '%default')",
)
AddOption(
    "--solve-cpus",
    dest="solve_cpus",
    default=1,
    nargs=1,
    type="int",
    action="store",
    metavar="N",
    help="Run the Abaqus Solve task using N CPUs. (default: '%default')",
)
AddOption(
    "--cache-dir",
    dest="cache_directory",
    default=None,
    action="store",
    metavar="DIR",
    help="SCons cache directory. Relative or absolute path. (default: '%default')",
)
AddOption(
    "--shared-cache-dir",
    dest="shared_cache_directory",
    action="store_true",
    default=False,
    help=(
        "Override the ``--cache-dir`` with the shared project cache. Must also use SCons native "
        "``--cache-readonly`` option to access the shared project cache (default: '%default')."
    ),
)
AddOption(
    "--shared-cache-override",
    dest="shared_cache_override",
    action="store_true",
    default=False,
    help=(
        "WARNING: MAKES THE PROJECT SHARED BUILD CACHE WRITABLE."
        " Allow write access to the project shared build cache. MUST NOT SPECIFY ``--cache-readonly`` or SCons will"
        " override this option when configuring the build cache directory."
    ),
)
# Python optparse appends to the default list instead of overriding. Must implement default/override ourselves.
default_abaqus_commands = [
    "/apps/abaqus/Commands/abq2025",
    "/usr/projects/ea/abaqus/Commands/abq2025",
    "abq2025",
    "abaqus",
]
AddOption(
    "--abaqus-command",
    dest="abaqus_command",
    nargs=1,
    type="string",
    action="append",
    metavar="COMMAND",
    help=f"Override for the Abaqus command. Repeat to specify more than one (default: {default_abaqus_commands})",
)

# Inherit user's full environment and set project options
build_directory = pathlib.Path(GetOption("build_directory")).resolve()
parameter_study_output = build_directory / "parameter_studies"
env = waves.scons_extensions.WAVESEnvironment(
    ENV=os.environ.copy(),
    unconditional_build=GetOption("unconditional_build"),
    solve_cpus=GetOption("solve_cpus"),
    TARFLAGS="-c -j",
    TARSUFFIX=".tar.bz2",
)
env.PrintBuildFailures()

# Empty defaults list to avoid building all simulation targets by default
env.Default()

# Make the modsim package importable for: (1) SConscript files and (2) Python and Abaqus Python environments
project_directory = pathlib.Path(Dir("#").abspath)
sys.path.insert(0, str(project_directory))
env.PrependENVPath("PYTHONPATH", project_directory)

import modsim_package.rectangle_compression  # noqa: E402
import modsim_package.utilities  # noqa: E402
from modsim_package import __version__ as version  # noqa: E402

# Set project internal variables and variable substitution dictionaries
project_name = project_directory.name
author_list = [
    "Kyle Brindley",
    "Prabhu S. Khalsa",
    "Thomas Roberts",
    "Sergio Cordova",
    "Matthew Fister",
    "Chris Johnson",
    "Scott Ouellette",
]
author_latex = r" \and ".join(author_list)
latex_project_name = project_name.replace("_", "-")
modsim_package_dir = pathlib.Path("modsim_package")
test_source_dir = "modsim_package/tests"
# The shared cache should be hardcoded to point to the project's shared cache on a shared file system
shared_cache_dir = project_directory / "shared_cache"
project_variables = {
    "project_directory": project_directory,
    "project_name": project_name,
    "version": version,
    "author_list": author_list,
    "author_html": ", ".join(author for author in author_list),
    "author_latex": author_latex,
    "documentation_pdf": f"{latex_project_name}-{version}.pdf",
    "report_pdf": f"{latex_project_name}-{version}-report.pdf",
    "test_source_abspath": project_directory / test_source_dir,
}
for key, value in project_variables.items():
    env[key] = value

# Set cache properties
if GetOption("shared_cache_directory"):
    cache_dir = shared_cache_dir
    print(
        (f"WARNING: Overriding ``--cache-dir`` with the project's shared build cache directory {shared_cache_dir}"),
        file=sys.stderr,
    )
else:
    cache_dir = GetOption("cache_directory")
build_cache_directory = env.ProjectCacheDirectory(
    shared_cache_dir,
    cache_dir,
    GetOption("cache_readonly"),
    GetOption("shared_cache_override"),
)

# Find required programs for conditional target ignoring and absolute path for use in target actions
env["SPHINX_BUILD"] = env.AddProgram(["sphinx-build"])
abaqus_commands = GetOption("abaqus_command")
env["ABAQUS_PROGRAM"] = env.AddProgram(abaqus_commands if abaqus_commands is not None else default_abaqus_commands)

# Add WAVES builders and scanners
env.Append(
    BUILDERS={
        "AbaqusExtract": waves.scons_extensions.abaqus_extract(program=env["ABAQUS_PROGRAM"]),
        "CondaEnvironment": waves.scons_extensions.conda_environment(),
    }
)
env.Append(SCANNERS=waves.scons_extensions.sphinx_scanner())
env.Append(SCANNERS=waves.scons_extensions.abaqus_input_scanner())
env.AddMethod(
    waves.scons_extensions.QOIPseudoBuilder(
        collection_dir=(build_directory / "qoi").resolve(),
        build_dir=build_directory.resolve(),
    ),
    "QOI",
)

# Dump the Conda environment as documentation of as-built target environment
environment_target = env.CondaEnvironment(
    target=[build_directory / "environment.yaml"],
    source=[],
)
env.AlwaysBuild(environment_target)
env.Alias("environment", environment_target)

# Add documentation target(s)
# Project documentation
SConscript(
    "docs/docs.scons",
    variant_dir=build_directory / "docs",
    exports={"env": env, "project_variables": project_variables},
)

# Analysis report
SConscript(
    "docs/report.scons",
    variant_dir=build_directory / "report",
    exports={"env": env, "project_variables": project_variables},
)

# Add simulation targets
workflow_artifacts: dict = {}
# Marker: workflow-configurations-start-do-not-remove
workflow_configurations = {
    "rectangle_compression-nominal": (
        "rectangle_compression.scons",
        waves.parameter_generators.OneAtATime(
            modsim_package.rectangle_compression.nominal(),
            output_file=parameter_study_output / "nominal.yaml",
            output_file_type="yaml",
        ),
    ),
    "rectangle_compression-mesh_convergence": (
        "rectangle_compression.scons",
        waves.parameter_generators.CartesianProduct(
            modsim_package.rectangle_compression.mesh_convergence(),
            output_file=parameter_study_output / "rectangle_compression-mesh_convergence.h5",
            previous_parameter_study=parameter_study_output / "rectangle_compression-mesh_convergence.h5",
        ),
    ),
}
for alias, (workflow, study_definition) in workflow_configurations.items():
    # Marker: workflow-configurations-end-do-not-remove
    # Marker: workflow-naming-conventions-start-do-not-remove
    # Project naming conventions
    workflow_build_directory = build_directory / alias
    workflow_file = modsim_package_dir / workflow
    # Marker: workflow-naming-conventions-end-do-not-remove

    # Write workflow parameter file
    study_definition.write()

    # Call workflow configuration
    workflow_artifacts[alias] = env.ParameterStudySConscript(
        workflow_file,
        variant_dir=workflow_build_directory,
        exports={"env": env, "alias": alias},
        study=study_definition,
        subdirectories=True,
        duplicate=True,
    )

# Post-processing: rectangle compression mesh convergence collective
alias = "rectangle_compression-mesh_convergence"
workflow_artifacts[alias].extend(
    SConscript(
        modsim_package_dir / f"{alias}-post_processing.scons",
        variant_dir=build_directory / alias,
        exports={"env": env, "alias": alias, "parameter_generator": workflow_configurations[alias][1]},
    )
)

# Archive workflow artifacts
for alias, artifacts in workflow_artifacts.items():
    SConscript(
        modsim_package_dir / "archive.scons",
        variant_dir=build_directory / alias,
        exports={"env": env, "alias": alias, "artifacts": [*artifacts, "#/SConstruct"]},
    )

# Add unit test target
test_workflow = "unit_testing.scons"
test_build_directory = build_directory / pathlib.Path(test_workflow).stem
SConscript(test_workflow, variant_dir=test_build_directory, exports={"env": env}, duplicate=False)

# Add default target list to help message
# Add aliases to help message so users know what build target options are available
# This must come *after* all expected Alias definitions and SConscript files.
env.ProjectHelp()
