# coding: utf-8

from __future__ import division, print_function, unicode_literals, absolute_import

from uuid import uuid4

import numpy as np

from pymatgen.io.vasp.sets import MPRelaxSet, MPStaticSet, MPHSERelaxSet
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
from pymatgen.io.vasp.inputs import Kpoints

from atomate.vasp.config import SMALLGAP_KPOINT_MULTIPLY, STABILITY_CHECK, VASP_CMD, DB_FILE, \
    ADD_WF_METADATA
from atomate.vasp.powerups import add_small_gap_multiply, add_stability_check, add_modify_incar, \
    add_wf_metadata, add_common_powerups
from atomate.vasp.workflows.base.core import get_wf
from atomate.vasp.workflows.base.elastic import get_wf_elastic_constant
from atomate.vasp.workflows.base.raman import get_wf_raman_spectra
from atomate.vasp.workflows.base.gibbs import get_wf_gibbs_free_energy
from atomate.vasp.workflows.base.bulk_modulus import get_wf_bulk_modulus
from atomate.vasp.workflows.base.thermal_expansion import get_wf_thermal_expansion
from atomate.vasp.workflows.base.neb import get_wf_neb_from_endpoints, get_wf_neb_from_structure, \
    get_wf_neb_from_images

__author__ = 'Anubhav Jain, Kiran Mathew'
__email__ = 'ajain@lbl.gov, kmathew@lbl.gov'


# TODO: @computron: Clarify the config dict -computron
# TODO: @computron: Allow default config dict to be loaded from file -computron

def wf_bandstructure(structure, c=None):

    c = c or {}
    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)

    wf = get_wf(structure, "bandstructure.yaml", vis=MPRelaxSet(structure, force_gamma=True),
                common_params={"vasp_cmd": vasp_cmd, "db_file": db_file})

    wf = add_common_powerups(wf, c)

    if c.get("SMALLGAP_KPOINT_MULTIPLY", SMALLGAP_KPOINT_MULTIPLY):
        wf = add_small_gap_multiply(wf, 0.5, 5, "static")
        wf = add_small_gap_multiply(wf, 0.5, 5, "nscf")

    if c.get("STABILITY_CHECK", STABILITY_CHECK):
        wf = add_stability_check(wf, fw_name_constraint="structure optimization")

    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)

    return wf


def wf_bandstructure_no_opt(structure, c=None):

    c = c or {}
    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)

    wf = get_wf(structure, "bandstructure_no_opt.yaml",
                vis=MPStaticSet(structure, force_gamma=True),
                common_params={"vasp_cmd": vasp_cmd, "db_file": db_file})

    wf = add_common_powerups(wf, c)

    if c.get("SMALLGAP_KPOINT_MULTIPLY", SMALLGAP_KPOINT_MULTIPLY):
        wf = add_small_gap_multiply(wf, 0.5, 5, "static")
        wf = add_small_gap_multiply(wf, 0.5, 5, "nscf")

    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)

    return wf


def wf_bandstructure_hse(structure, c=None):

    c = c or {}
    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)

    wf_src_name = "bandstructure_hse_full.yaml"

    wf = get_wf(structure, wf_src_name, vis=MPHSERelaxSet(structure, force_gamma=True),
                common_params={"vasp_cmd": vasp_cmd, "db_file": db_file})

    wf = add_common_powerups(wf, c)

    if c.get("SMALLGAP_KPOINT_MULTIPLY", SMALLGAP_KPOINT_MULTIPLY):
        wf = add_small_gap_multiply(wf, 0.5, 5, "static")

    if c.get("STABILITY_CHECK", STABILITY_CHECK):
        wf = add_stability_check(wf, fw_name_constraint="structure optimization")

    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)

    return wf

def wf_bandstructure_plus_hse(structure, gap_only=True, c=None):

    c = c or {}
    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)

    if gap_only:
        wf_src_name = "bandstructure_hsegap.yaml"
    else:
        wf_src_name = "bandstructure_hse.yaml"

    wf = get_wf(structure, wf_src_name, vis=MPRelaxSet(structure, force_gamma=True),
                common_params={"vasp_cmd": vasp_cmd, "db_file": db_file})

    wf = add_common_powerups(wf, c)

    if c.get("SMALLGAP_KPOINT_MULTIPLY", SMALLGAP_KPOINT_MULTIPLY):
        wf = add_small_gap_multiply(wf, 0.5, 5, "static")
        wf = add_small_gap_multiply(wf, 0.5, 5, "nscf")

    if c.get("STABILITY_CHECK", STABILITY_CHECK):
        wf = add_stability_check(wf, fw_name_constraint="structure optimization")

    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)

    return wf


def wf_bandstructure_plus_boltztrap(structure, c=None):

    c = c or {}
    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)

    params = []
    for x in range(4):  # everything but BoltzTrap task
        params.append({"vasp_cmd": vasp_cmd, "db_file": db_file})
    params.append({"db_file": db_file})

    wf = get_wf(structure, "bandstructure_boltztrap.yaml", vis=MPRelaxSet(structure, force_gamma=True),
                params=params)

    wf = add_common_powerups(wf, c)

    if c.get("SMALLGAP_KPOINT_MULTIPLY", SMALLGAP_KPOINT_MULTIPLY):
        wf = add_small_gap_multiply(wf, 0.5, 5, "static")
        wf = add_small_gap_multiply(wf, 0.5, 5, "nscf")

    if c.get("STABILITY_CHECK", STABILITY_CHECK):
        wf = add_stability_check(wf, fw_name_constraint="structure optimization")

    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)

    return wf


def wf_static(structure, c=None):

    c = c or {}
    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)

    wf = get_wf(structure, "static_only.yaml", vis=MPStaticSet(structure),
                common_params={"vasp_cmd": vasp_cmd, "db_file": db_file})

    wf = add_common_powerups(wf, c)

    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)

    return wf


def wf_structure_optimization(structure, c=None):

    c = c or {}
    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)
    user_incar_settings = c.get("USER_INCAR_SETTINGS")

    wf = get_wf(structure, "optimize_only.yaml",
                vis=MPRelaxSet(structure, force_gamma=True, user_incar_settings=user_incar_settings),
                common_params={"vasp_cmd": vasp_cmd, "db_file": db_file})

    wf = add_common_powerups(wf, c)

    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)

    return wf


def wf_dielectric_constant(structure, c=None):

    c = c or {}
    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)

    wf = get_wf(structure, "dielectric_constant.yaml", vis=MPRelaxSet(structure, force_gamma=True),
                common_params={"vasp_cmd": vasp_cmd, "db_file": db_file})

    wf = add_common_powerups(wf, c)

    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)

    return wf


def wf_dielectric_constant_no_opt(structure, c=None):

    c = c or {}
    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)

    wf = get_wf(structure, "dielectric_constant_no_opt.yaml",
                common_params={"vasp_cmd": vasp_cmd,  "db_file": db_file})

    wf = add_common_powerups(wf, c)

    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)

    return wf


def wf_piezoelectric_constant(structure, c=None):

    c = c or {}
    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)

    wf = get_wf(structure, "piezoelectric_constant.yaml",
                common_params={"vasp_cmd": vasp_cmd, "db_file": db_file})

    wf = add_common_powerups(wf, c)

    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)

    return wf

def wf_nmr(structure, c=None):

    c = c or {}
    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)

    wf = get_wf(structure, "nmr.yaml",
                common_params={"vasp_cmd": vasp_cmd, "db_file": db_file})

    wf = add_common_powerups(wf, c)

    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)

    return wf


def wf_elastic_constant(structure, c=None, order=2, sym_reduce=False):

    c = c or {}
    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)

    uis_optimize = {"ENCUT": 700, "EDIFF": 1e-6, "LAECHG": False}
    if order > 2:
        uis_optimize.update({"EDIFF": 1e-10, "EDIFFG": -0.001,
                             "ADDGRID": True, "LREAL": False, "ISYM": 0})
        # This ensures a consistent k-point mesh across all calculations
        # We also turn off symmetry to prevent VASP from changing the
        # mesh internally
        kpts_settings = Kpoints.automatic_density(structure, 40000, force_gamma=True)
        stencils = np.linspace(-0.075, 0.075, 7)
    else:
        kpts_settings = {'grid_density': 7000}
        stencils = None

    uis_static = uis_optimize.copy()
    uis_static.update({'ISIF': 2, 'IBRION': 2, 'NSW': 99, 'ISTART': 1, "PREC": "High"})

    # input set for structure optimization
    vis_relax = MPRelaxSet(structure, force_gamma=True, user_incar_settings=uis_optimize,
                           user_kpoints_settings=kpts_settings)

    # optimization only workflow
    wf = get_wf(structure, "optimize_only.yaml", vis=vis_relax,
                params=[{"vasp_cmd": vasp_cmd,  "db_file": db_file,
                         "name": "elastic structure optimization"}])

    vis_static = MPStaticSet(structure, force_gamma=True, lepsilon=False,
                             user_kpoints_settings=kpts_settings,
                             user_incar_settings=uis_static)

    # deformations wflow for elasticity calculation
    wf_elastic = get_wf_elastic_constant(structure, vasp_cmd=vasp_cmd, db_file=db_file,
                                         order=order, stencils=stencils, copy_vasp_outputs=True,
                                         vasp_input_set=vis_static, sym_reduce=sym_reduce)
    wf.append_wf(wf_elastic, wf.leaf_fw_ids)

    wf = add_common_powerups(wf, c)
    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)

    return wf


def wf_elastic_constant_minimal(structure, c=None, order=2, sym_reduce=True):

    c = c or {}
    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)

    stencil = np.arange(0.01, 0.01 * order, step=0.01)
    wf = get_wf_elastic_constant(structure, vasp_cmd=vasp_cmd, db_file=db_file,
                                 sym_reduce=sym_reduce, stencils=stencil, order=order,
                                 copy_vasp_outputs=False)

    wf = add_common_powerups(wf, c)
    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)
    return wf


def wf_raman_spectra(structure, c=None):
    """
    Raman spectra workflow from the given structure and config dict

    Args:
        structure (Structure): input structure
        c (dict): workflow config dict

    Returns:
        Workflow
    """

    c = c or {}
    modes = c.get("MODES", None)
    step_size = c.get("STEP_SIZE", 0.005)
    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)

    wf = get_wf_raman_spectra(structure, modes=modes, step_size=step_size, vasp_cmd=vasp_cmd,
                              db_file=db_file)

    wf = add_modify_incar(wf, modify_incar_params={"incar_update": {"ENCUT": 600, "EDIFF": 1e-6}},
                          fw_name_constraint="static dielectric")

    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)

    return wf


def wf_gibbs_free_energy(structure, c=None):
    """
    Gibbs free energy workflow from the given structure and config dict.

    Args:
        structure (Structure): input structure
        c (dict): workflow config dict

    Returns:
        Workflow
    """
    c = c or {}

    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)
    eos = c.get("EOS", "vinet")
    qha_type = c.get("QHA_TYPE", "debye_model")
    # min and max temp in K, default setting is to compute the properties at 300K only
    t_min = c.get("T_MIN", 300.0)
    t_max = c.get("T_MAX", 300.0)
    t_step = c.get("T_STEP", 100.0)
    pressure = c.get("PRESSURE", 0.0)
    poisson = c.get("POISSON", 0.25)
    anharmonic_contribution = c.get("ANHARMONIC_CONTRIBUTION", False)
    metadata = c.get("METADATA", None)

    # 21 deformed structures: from -10% to +10%
    defos = [(np.identity(3) * (1 + x)).tolist() for x in np.linspace(-0.1, 0.1, 21)]
    deformations = c.get("DEFORMATIONS", defos)
    user_kpoints_settings = {"grid_density": 7000}

    tag = "gibbs group: >>{}<<".format(str(uuid4()))

    # input set for structure optimization
    vis_relax = MPRelaxSet(structure, force_gamma=True)
    v = vis_relax.as_dict()
    v.update({"user_kpoints_settings": user_kpoints_settings})
    vis_relax = vis_relax.__class__.from_dict(v)

    # optimization only workflow
    wf = get_wf(structure, "optimize_only.yaml",
                params=[{"vasp_cmd": vasp_cmd,  "db_file": db_file,
                         "name": "{} structure optimization".format(tag)}],
                vis=vis_relax)

    # static input set for the transmute firework
    uis_static = {
        "ISIF": 2,
        "ISTART": 1,
    }

    lepsilon = False
    if qha_type not in ["debye_model"]:
        lepsilon = True
        try:
            from phonopy import Phonopy
        except ImportError:
            raise RuntimeError("'phonopy' package is NOT installed but is required for the final "
                               "analysis step; you can alternatively switch to the qha_type to "
                               "'debye_model' which does not require 'phonopy'.")
    vis_static = MPStaticSet(structure, force_gamma=True, lepsilon=lepsilon,
                             user_kpoints_settings=user_kpoints_settings,
                             user_incar_settings=uis_static)
    # get gibbs workflow and chain it to the optimization workflow
    wf_gibbs = get_wf_gibbs_free_energy(structure, user_kpoints_settings=user_kpoints_settings,
                                        deformations=deformations, vasp_cmd=vasp_cmd, db_file=db_file,
                                        eos=eos, qha_type=qha_type, pressure=pressure, poisson=poisson,
                                        t_min=t_min, t_max=t_max, t_step=t_step, metadata=metadata,
                                        anharmonic_contribution=anharmonic_contribution,
                                        tag=tag, vasp_input_set=vis_static)

    # chaining
    wf.append_wf(wf_gibbs, wf.leaf_fw_ids)

    wf = add_modify_incar(wf, modify_incar_params={"incar_update": {"ENCUT": 600,
                                                                    "EDIFF": 1e-6,
                                                                    "LAECHG": False}})

    wf = add_common_powerups(wf, c)

    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)

    return wf


def wf_bulk_modulus(structure, c=None):
    """
    Bulk modulus workflow from the given structure and config dict.

    Args:
        structure (Structure): input structure
        c (dict): workflow config dict

    Returns:
        Workflow
    """

    c = c or {}
    eos = c.get("EOS", "vinet")
    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)

    user_kpoints_settings = {"grid_density": 7000}
    # 6 deformations
    deformations = [(np.identity(3) * (1 + x)).tolist() for x in np.linspace(-0.05, 0.05, 6)]

    tag = "bulk_modulus group: >>{}<<".format(str(uuid4()))

    # input set for structure optimization
    vis_relax = MPRelaxSet(structure, force_gamma=True)
    v = vis_relax.as_dict()
    v.update({"user_kpoints_settings": user_kpoints_settings})
    vis_relax = vis_relax.__class__.from_dict(v)

    # static input set for the transmute firework
    uis_static = {
        "ISIF": 2,
        "ISTART": 1,
        "IBRION": 2,
        "NSW": 99
    }

    # optimization only workflow
    wf = get_wf(structure, "optimize_only.yaml",
                params=[{"vasp_cmd": vasp_cmd, "db_file": db_file,
                         "name": "{} structure optimization".format(tag)}],
                vis=vis_relax)

    vis_static = MPStaticSet(structure, force_gamma=True, lepsilon=False,
                             user_kpoints_settings=user_kpoints_settings,
                             user_incar_settings=uis_static)
    # get the deformations wflow for bulk modulus calculation
    wf_bm = get_wf_bulk_modulus(structure, eos=eos, user_kpoints_settings=user_kpoints_settings,
                                deformations=deformations, vasp_cmd=vasp_cmd, db_file=db_file, tag=tag,
                                vasp_input_set=vis_static)

    # chain it
    wf.append_wf(wf_bm, wf.leaf_fw_ids)

    wf = add_modify_incar(wf, modify_incar_params={"incar_update": {"ENCUT": 600, "EDIFF": 1e-6}})

    wf = add_common_powerups(wf, c)

    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)

    return wf


def wf_thermal_expansion(structure, c=None):
    """
    Thermal expansion coefficient workflow from the given structure and config dict.

    Args:
        structure (Structure): input structure
        c (dict): workflow config dict

    Returns:
        Workflow
    """
    c = c or {}
    eos = c.get("EOS", "vinet")
    vasp_cmd = c.get("VASP_CMD", VASP_CMD)
    db_file = c.get("DB_FILE", DB_FILE)
    pressure = c.get("PRESSURE", 0.0)

    user_kpoints_settings = {"grid_density": 7000}
    # 10 deformations
    deformations = [(np.identity(3) * (1 + x)).tolist() for x in np.linspace(-0.1, 0.1, 10)]

    tag = "thermal_expansion group: >>{}<<".format(str(uuid4()))

    # input set for structure optimization
    vis_relax = MPRelaxSet(structure, force_gamma=True)
    v = vis_relax.as_dict()
    v.update({"user_kpoints_settings": user_kpoints_settings})
    vis_relax = vis_relax.__class__.from_dict(v)

    # optimization only workflow
    wf = get_wf(structure, "optimize_only.yaml",
                params=[{"vasp_cmd": vasp_cmd,  "db_file": db_file,
                         "name": "{} structure optimization".format(tag)}],
                vis=vis_relax)

    wf_thermal = get_wf_thermal_expansion(structure, user_kpoints_settings=user_kpoints_settings,
                                          deformations=deformations, vasp_cmd=vasp_cmd, db_file=db_file,
                                          eos=eos, pressure=pressure, tag=tag)

    # chain it
    wf.append_wf(wf_thermal, wf.leaf_fw_ids)

    wf = add_modify_incar(wf, modify_incar_params={"incar_update": {"ENCUT": 600, "EDIFF": 1e-6}})

    wf = add_common_powerups(wf, c)

    if c.get("ADD_WF_METADATA", ADD_WF_METADATA):
        wf = add_wf_metadata(wf, structure)

    return wf


def wf_nudged_elastic_band(structures, parent, c=None):
    """
    Nudged elastic band (NEB) workflow from the given structures and config dict.

    'is_optimized' default False
    'neb_round' default 1

    Notes:
        Length of Structure list and "is_optimized" are used to determine the workflow:
        1 structure    # The parent structure & two endpoint indexes provided; need relaxation.
                       # The parent structure & two endpoint indexes provided; no need to relax.
        2 structures   # Two endpoints provided; need to relax two endpoints.
                       # Two relaxed endpoints provided; no need to relax two endpoints.
        >=3 structures # All images including two endpoints are provided.

    Args:
        structures ([Structure]):
            1) The parent structure
            2) Two endpoint structures
            3) An initial NEB path that comprises both images and endpoints
        parent (Structure): parent structure used to get two endpoints.
        c (dict): workflow config dict, basic format:
            {"fireworks": [],  "common_params": {}, "additional_ep_params": {},
             "additional_neb_params": {}}. When the length of structures is 1, "site_indices" key
             must be included in c. Note that "fireworks" is a list corresponding to the order of
             execution.
    Returns:
        Workflow
    """
    if not(isinstance(structures, list) and len(structures) > 0):
        raise ValueError("structures must be a list of Structure objects!")

    # config initialization
    c = c or {}
    # TODO: @shyuep - config dict params are typically capitalized and intended to be global
    # params unless namespaced. e.g. NEB.COMMON_PARAMS if it's only settings for NEB
    # workflows. -computron
    spec = c.get("common_params", {})
    is_optimized = spec.get("is_optimized", False)

    # TODO: @shyuep: the whole point of preset workflows is that they are supposed to be simple.
    # e.g., give a structure or a list of structures, and forget about the rest. Here it looks like
    # one needs to construct some kind of complicated configuration dictionary. Pretty sure no one
    # apart from the people in your group have any idea how to use these functions or set up this
    # config dict (which should not be necessary in the first place). -computron

    if c.get("fireworks"):  # Check config dict file
        fw_list = [f['fw'] for f in c.get("fireworks")]
        neb_round = len([f for f in fw_list if "NEBFW" in f])

        if neb_round < 1:
            raise ValueError("At least one NEB Fireworks (NEBFW) is needed in the config dict!")

        if len(structures) == 1:
            assert "site_indices" in spec, "Site indices not provided in config dict!"
            assert len(spec["site_indices"]) == 2, "Only two site indices should be provided!"

            if is_optimized:
                assert len(fw_list) == neb_round + 1
            else:
                assert len(fw_list) == neb_round + 2
        elif len(structures) == 2:
            if is_optimized:
                assert len(fw_list) == neb_round
            else:
                assert len(fw_list) == neb_round + 1
    else:  # Default settings if config dict is not provided.
        neb_round = 1

    # Get user_incar_settings, user_kpoints_settings & additional_cust_args
    user_incar_settings = [{}] * (neb_round + 2)
    user_kpoints_settings = [{"grid_density": 1000}] * (neb_round + 2)
    additional_cust_args = [{}] * (neb_round + 2)

    # TODO: @shyuep - I have no idea what's going on here -computron
    if "fireworks" in c:
        for i in range(1, len(c["fireworks"]) + 1):
            user_incar_settings[-i] = c["fireworks"][-i].get("user_incar_settings", {})
            user_kpoints_settings[-i] = c["fireworks"][-i].get("user_kpoints_settings",
                                                               {"grid_density": 1000})
            additional_cust_args[-i] = c["fireworks"][-i].get("additional_cust_args", {})

    kwargs = {"user_incar_settings": user_incar_settings,
              "user_kpoints_settings": user_kpoints_settings,
              "additional_cust_args": additional_cust_args}

    # Assign workflow using the number of given structures
    if len(structures) == 1:
        wf = get_wf_neb_from_structure(structure=structures[0],
                                       additional_spec=spec, **kwargs)
    elif len(structures) == 2:
        wf = get_wf_neb_from_endpoints(parent=parent, endpoints=structures,
                                       additional_spec=spec, **kwargs)
    else:  # len(structures) >= 3
        wf = get_wf_neb_from_images(parent=parent, images=structures,
                                    additional_spec=spec, **kwargs)

    return wf
