import numbers

import discretisedfield as df
import numpy as np

import oommfc as oc


def evolver_script(evolver, **kwargs):
    mif = ""

    # Prepare parameters depending on what attributes are defined in evolver.
    if hasattr(evolver, "gamma_G"):
        gamma0mif, gamma0name = oc.scripts.setup_scalar_parameter(
            evolver.gamma_G, "pr_gamma0"
        )
        evolver.gamma_G = gamma0name
        mif += gamma0mif
    if hasattr(evolver, "alpha"):
        alphamif, alphaname = oc.scripts.setup_scalar_parameter(
            evolver.alpha, "dp_alpha"
        )
        evolver.alpha = alphaname
        mif += alphamif
    if hasattr(evolver, "u"):
        if (
            isinstance(evolver.u, numbers.Real)
            or (isinstance(evolver.u, df.Field) and evolver.u.nvdim == 1)
            or (
                isinstance(evolver.u, dict)
                and isinstance(list(evolver.u.values())[0], numbers.Real)
            )
        ):
            scalar_u = True
            umif, uname = oc.scripts.setup_scalar_parameter(evolver.u, "zl_u")
        else:
            scalar_u = False
            umif, uname = oc.scripts.setup_vector_parameter(evolver.u, "zl_u")
        evolver.u = uname
        mif += umif

    # zhangli.beta cannot spatially vary - it has to be a constant.

    if hasattr(evolver, "J"):
        Jmif, Jname = oc.scripts.setup_scalar_parameter(evolver.J, "sl_J")
        evolver.J = Jname
        mif += Jmif
    if hasattr(evolver, "mp"):
        mpmif, mpname = oc.scripts.setup_vector_parameter(evolver.mp, "sl_mp")
        evolver.mp = mpname
        mif += mpmif
    if hasattr(evolver, "P"):
        Pmif, Pname = oc.scripts.setup_scalar_parameter(evolver.P, "sl_P")
        evolver.P = Pname
        mif += Pmif
    if hasattr(evolver, "Lambda"):
        Lambda = evolver.Lambda
        if isinstance(evolver.Lambda, dict):
            # the automatically added default value 0 is not allowed
            Lambda = evolver.Lambda.copy()
            if "default" not in Lambda:
                Lambda["default"] = 1
        Lambdamif, Lambdaname = oc.scripts.setup_scalar_parameter(Lambda, "sl_Lambda")
        evolver.Lambda = Lambdaname
        mif += Lambdamif
    if hasattr(evolver, "eps_prime"):
        eps_primemif, eps_primename = oc.scripts.setup_scalar_parameter(
            evolver.eps_prime, "sl_eps_prime"
        )
        evolver.eps_prime = eps_primename
        mif += eps_primemif

    if hasattr(evolver, "dt") and isinstance(evolver.dt, numbers.Real):
        ts = np.arange(0, kwargs["t"] + evolver.dt, evolver.dt)
        tlist = [evolver.func(t) for t in ts]

        mif += "proc TimeFunction { total_time } {\n"
        mif += f"  set tstep {evolver.dt}\n"
        mif += "  set index [expr round($total_time/$tstep)]\n"
        mif += f"  set profile {{ {' '.join(map(str, tlist))} }}\n"
        mif += "  set factor [lindex $profile $index]\n"
        mif += "  return $factor\n"
        mif += "}\n\n"

        if isinstance(evolver, (oc.SpinXferEvolver, oc.Xf_ThermSpinXferEvolver)):
            evolver.J_profile = "TimeFunction"
            evolver.J_profile_args = "total_time"
        elif isinstance(evolver, oc.SpinTEvolver):
            evolver.u_profile = "TimeFunction"
            evolver.u_profile_args = "total_time"
    if hasattr(evolver, "tcl_strings") and isinstance(evolver.tcl_strings, dict):
        mif += evolver.tcl_strings["script"]
        if isinstance(evolver, (oc.SpinXferEvolver, oc.Xf_ThermSpinXferEvolver)):
            evolver.J_profile = evolver.tcl_strings["script_name"]
            evolver.J_profile_args = evolver.tcl_strings["script_args"]
        elif isinstance(evolver, oc.SpinTEvolver):
            evolver.u_profile = evolver.tcl_strings["script_name"]
            evolver.u_profile_args = evolver.tcl_strings["script_args"]

    # temperature cannot spacially vary

    # Scripts for a specific evolver.
    if isinstance(evolver, oc.EulerEvolver):
        mif += "# EulerEvolver\n"
        mif += "Specify Oxs_EulerEvolve:evolver {\n"
    elif isinstance(evolver, oc.RungeKuttaEvolver):
        mif += "# RungeKuttaEvolver\n"
        mif += "Specify Oxs_RungeKuttaEvolve:evolver {\n"
    elif isinstance(evolver, oc.SpinTEvolver):
        # time dependence
        mif += "# Zhang-Li evolver\n"
        if scalar_u:
            mif += "Specify Anv_SpinTEvolve:evolver {\n"
        else:
            mif += "Specify Anv_SpinTEvolve_3d:evolver {\n"
    elif isinstance(evolver, oc.SpinXferEvolver):
        # time dependence
        mif += "# Slonczewski evolver\n"
        mif += "Specify Oxs_SpinXferEvolve:evolver {\n"
    elif isinstance(evolver, oc.CGEvolver):
        mif += "# CGEvolver\n"
        mif += "Specify Oxs_CGEvolve:evolver {\n"
    elif isinstance(evolver, oc.UHH_ThetaEvolver):
        mif += "# UHH_ThetaEvolver\n"
        mif += "Specify UHH_ThetaEvolve:evolver {\n"
    elif isinstance(evolver, oc.Xf_ThermHeunEvolver):
        mif += "# Xf_ThermHeunEvolver\n"
        mif += "Specify Xf_ThermHeunEvolve:evolver {\n"
    elif isinstance(evolver, oc.Xf_ThermSpinXferEvolver):
        mif += "# Xf_ThermSpinXferEvolver\n"
        mif += "Specify Xf_ThermSpinXferEvolve:evolver {\n"

    # Define all other parameters.
    for attr, value in evolver:
        if attr not in ["time_dependence", "tstep", "tcl_strings"]:
            mif += f"  {attr} {value}\n"
    mif += "}\n\n"

    return mif
