from collections import namedtuple

Setting = namedtuple("setting", ("default", "type", "description"))


def optional(type_):
    def wrapped(arg):
        if arg is None:
            return arg

        return type_(arg)

    return wrapped


PI = 3.14159265358979323846264338327950588

SETTINGS = {
    "identifier": Setting("UNNAMED", str, "Identifier of the current simulation"),
    "description": Setting("", str, "Description of the current simulation"),
    # Model parameters
    "nx": Setting(0, int, "Grid points in zonal (x) direction"),
    "ny": Setting(0, int, "Grid points in meridional (y,j) direction"),
    "nz": Setting(0, int, "Grid points in vertical (z,k) direction"),
    "dt_mom": Setting(0.0, float, "Time step in seconds for momentum"),
    "dt_tracer": Setting(0.0, float, "Time step for tracers, can be larger than dt_mom"),
    "runlen": Setting(0.0, float, "Length of simulation in seconds"),
    "AB_eps": Setting(0.1, float, "Deviation from Adam-Bashforth weighting"),
    "x_origin": Setting(0, float, "Grid origin in x-direction"),
    "y_origin": Setting(0, float, "Grid origin in y-direction"),
    # Physical constants
    "pi": Setting(PI, float, "Pi"),
    "radius": Setting(6370e3, float, "Earth radius in m"),
    "degtom": Setting(6370e3 / 180.0 * PI, float, "Conversion factor from degrees latitude to meters"),
    "omega": Setting(PI / 43082.0, float, "Earth rotation frequency in 1/s"),
    "rho_0": Setting(1024.0, float, "Boussinesq reference density in :math:`kg/m^3`"),
    "grav": Setting(9.81, float, "Gravitational constant in :math:`m/s^2`"),
    # Logical switches for general model setup
    "coord_degree": Setting(False, bool, "either spherical (True) or cartesian (False) coordinates"),
    "enable_cyclic_x": Setting(False, bool, "enable cyclic boundary conditions"),
    "eq_of_state_type": Setting(1, int, "equation of state: 1: linear, 3: nonlinear with comp., 5: TEOS"),
    "enable_implicit_vert_friction": Setting(False, bool, "enable implicit vertical friction"),
    "enable_explicit_vert_friction": Setting(False, bool, "enable explicit vertical friction"),
    "enable_hor_friction": Setting(False, bool, "enable horizontal friction"),
    "enable_hor_diffusion": Setting(False, bool, "enable horizontal diffusion"),
    "enable_biharmonic_friction": Setting(False, bool, "enable biharmonic horizontal friction"),
    "enable_biharmonic_mixing": Setting(False, bool, "enable biharmonic horizontal mixing"),
    "enable_hor_friction_cos_scaling": Setting(False, bool, "scaling of hor. viscosity with cos(latitude)**cosPower"),
    "enable_ray_friction": Setting(False, bool, "enable Rayleigh damping"),
    "enable_bottom_friction": Setting(False, bool, "enable bottom friction"),
    "enable_bottom_friction_var": Setting(False, bool, "enable bottom friction with lateral variations"),
    "enable_quadratic_bottom_friction": Setting(False, bool, "enable quadratic bottom friction"),
    "enable_tempsalt_sources": Setting(False, bool, "enable restoring zones, etc"),
    "enable_momentum_sources": Setting(False, bool, "enable restoring zones, etc"),
    "enable_superbee_advection": Setting(False, bool, "enable advection scheme with implicit mixing"),
    "enable_conserve_energy": Setting(True, bool, "exchange energy consistently"),
    "enable_store_bottom_friction_tke": Setting(
        False, bool, "transfer dissipated energy by bottom/rayleig fric. to TKE, else transfer to internal waves"
    ),
    "enable_store_cabbeling_heat": Setting(
        False, bool, "transfer non-linear mixing terms to potential enthalpy, else transfer to TKE and EKE"
    ),
    "enable_noslip_lateral": Setting(
        False, bool, "enable lateral no-slip boundary conditions in harmonic- and biharmonic friction."
    ),
    "enable_streamfunction": Setting(
        True,
        bool,
        "solve for external mode with barotropic streamfunction, else solve for surface pressure and sea surface height",
    ),
    # Mixing parameters
    "A_h": Setting(0.0, float, "lateral viscosity in m^2/s"),
    "K_h": Setting(0.0, float, "lateral diffusivity in m^2/s"),
    "r_ray": Setting(0.0, float, "Rayleigh damping coefficient in 1/s"),
    "r_bot": Setting(0.0, float, "bottom friction coefficient in 1/s"),
    "r_quad_bot": Setting(0.0, float, "qudratic bottom friction coefficient"),
    "hor_friction_cosPower": Setting(3, float, "power to scale cos term by in horizontal friction"),
    "A_hbi": Setting(0.0, float, "lateral biharmonic viscosity in m^4/s"),
    "K_hbi": Setting(0.0, float, "lateral biharmonic diffusivity in m^4/s"),
    "biharmonic_friction_cosPower": Setting(0, float, "power to scale cos term by in biharmonic friction"),
    "kappaH_0": Setting(0.0, float, "fixed values for vertical viscosity/diffusivity which are set for no TKE model"),
    "kappaM_0": Setting(0.0, float, "fixed values for vertical viscosity/diffusivity which are set for no TKE model"),
    # Options for isopycnal mixing
    "enable_neutral_diffusion": Setting(False, bool, "enable isopycnal mixing"),
    "enable_skew_diffusion": Setting(False, bool, "enable skew diffusion approach for eddy-driven velocities"),
    "enable_TEM_friction": Setting(False, bool, "TEM approach for eddy-driven velocities"),
    "K_iso_0": Setting(0.0, float, "constant for isopycnal diffusivity in m^2/s"),
    "K_iso_steep": Setting(0.0, float, "lateral diffusivity for steep slopes in m^2/s"),
    "K_gm_0": Setting(0.0, float, "fixed value for K_gm which is set for no EKE model"),
    "iso_dslope": Setting(0.0008, float, "parameters controlling max allowed isopycnal slopes"),
    "iso_slopec": Setting(0.001, float, "parameters controlling max allowed isopycnal slopes"),
    # Idemix 1.0
    "enable_idemix": Setting(False, bool, ""),
    "tau_v": Setting(2.0 * 86400.0, float, "time scale for vertical symmetrisation"),
    "tau_h": Setting(15.0 * 86400.0, float, "time scale for horizontal symmetrisation"),
    "gamma": Setting(1.57, float, ""),
    "jstar": Setting(5.0, float, "spectral bandwidth in modes"),
    "mu0": Setting(1.0 / 3.0, float, "dissipation parameter"),
    "enable_idemix_hor_diffusion": Setting(False, bool, ""),
    "enable_eke_diss_bottom": Setting(False, bool, ""),
    "enable_eke_diss_surfbot": Setting(False, bool, ""),
    "eke_diss_surfbot_frac": Setting(1.0, float, "fraction which goes into bottom"),
    "enable_idemix_superbee_advection": Setting(False, bool, ""),
    "enable_idemix_upwind_advection": Setting(False, bool, ""),
    # TKE
    "enable_tke": Setting(False, bool, ""),
    "c_k": Setting(0.1, float, ""),
    "c_eps": Setting(0.7, float, ""),
    "alpha_tke": Setting(1.0, float, ""),
    "mxl_min": Setting(1e-12, float, ""),
    "kappaM_min": Setting(0.0, float, ""),
    "kappaM_max": Setting(100.0, float, ""),
    "tke_mxl_choice": Setting(1, int, ""),
    "enable_tke_superbee_advection": Setting(False, bool, ""),
    "enable_tke_upwind_advection": Setting(False, bool, ""),
    "enable_tke_hor_diffusion": Setting(False, bool, ""),
    "K_h_tke": Setting(2000.0, float, "lateral diffusivity for tke"),
    # EKE
    "enable_eke": Setting(False, bool, ""),
    "eke_lmin": Setting(100.0, float, "minimal length scale in m"),
    "eke_c_k": Setting(1.0, float, ""),
    "eke_cross": Setting(1.0, float, "Parameter for EKE model"),
    "eke_crhin": Setting(1.0, float, "Parameter for EKE model"),
    "eke_c_eps": Setting(1.0, float, "Parameter for EKE model"),
    "eke_k_max": Setting(1e4, float, "maximum of K_gm"),
    "alpha_eke": Setting(1.0, float, "factor vertical friction"),
    "enable_eke_superbee_advection": Setting(False, bool, ""),
    "enable_eke_upwind_advection": Setting(False, bool, ""),
    "enable_eke_isopycnal_diffusion": Setting(False, bool, "use K_gm also for isopycnal diffusivity"),
    # Restarts
    "restart_input_filename": Setting(
        None, optional(str), "File name of restart input. If not given, no restart data will be read."
    ),
    "restart_output_filename": Setting(
        "{identifier}_{itt:0>4d}.restart.h5",
        optional(str),
        "File name of restart output. May contain Python format syntax that is substituted with Veros attributes.",
    ),
    "restart_frequency": Setting(0, float, "Frequency (in seconds) to write restart data"),
    # New
    "kappaH_min": Setting(0.0, float, "minimum value for vertical diffusivity"),
    "enable_kappaH_profile": Setting(
        False, bool, "Compute vertical profile of diffusivity after Bryan and Lewis (1979) in TKE routine"
    ),
    "enable_Prandtl_tke": Setting(True, bool, "Compute Prandtl number from stratification levels in TKE routine"),
    "Prandtl_tke0": Setting(
        10.0, float, "Constant Prandtl number when stratification is neglected for kappaH computation in TKE routine"
    ),
}


def check_setting_conflicts(settings):
    if settings.enable_tke and not settings.enable_implicit_vert_friction:
        raise RuntimeError(
            "use TKE model only with implicit vertical friction (set enable_implicit_vert_fricton to True)"
        )
