"""
The Dirichlet, periodic and linear combination boundary condition
classes, as well as the initial condition class.
"""
from __future__ import absolute_import
import numpy as nm

from sfepy.base.base import basestr, Container, Struct
from sfepy.discrete.functions import Function
import six

def get_condition_value(val, functions, kind, name):
    """
    Check a boundary/initial condition value type and return the value or
    corresponding function.
    """
    if type(val) == str:
        if functions is not None:
            try:
                fun = functions[val]

            except IndexError:
                raise ValueError('unknown function %s given for %s %s!'
                                 % (val, kind, name))

        else:
            raise ValueError('no functions given for %s %s!' % (kind, name))

    elif (isinstance(val, Function) or nm.isscalar(val)
          or isinstance(val, nm.ndarray)):
        fun = val

    else:
        raise ValueError('unknown value type for %s %s!'
                         % (kind, name))

    return fun

def _get_region(name, regions, bc_name):
    try:
        region = regions[name]
    except IndexError:
        msg = "no region '%s' used in condition %s!" % (name, bc_name)
        raise IndexError(msg)

    return region

class Conditions(Container):
    """
    Container for various conditions.
    """
    @staticmethod
    def from_conf(conf, regions):
        conds = []
        for key, cc in six.iteritems(conf):
            times = cc.get('times', None)

            if 'ebc' in key:
                region = _get_region(cc.region, regions, cc.name)
                cond = EssentialBC(cc.name, region, cc.dofs, key=key,
                                   times=times)

            elif 'epbc' in key:
                rs = [_get_region(ii, regions, cc.name) for ii in cc.region]
                cond = PeriodicBC(cc.name, rs, cc.dofs, cc.match, key=key,
                                   times=times)

            elif 'lcbc' in key:
                if isinstance(cc.region, basestr):
                    rs = [_get_region(cc.region, regions, cc.name), None]

                else:
                    rs = [_get_region(ii, regions, cc.name)
                          for ii in cc.region]

                cond = LinearCombinationBC(cc.name, rs, cc.dofs,
                                           cc.dof_map_fun, cc.kind,
                                           key=key,
                                           times=times,
                                           arguments=cc.get('arguments', None))

            elif 'ic' in key:
                region = _get_region(cc.region, regions, cc.name)
                cond = InitialCondition(cc.name, region, cc.dofs, key=key)

            else:
                raise ValueError('unknown condition type! (%s)' % key)

            conds.append(cond)

        obj = Conditions(conds)
        return obj

    def group_by_variables(self, groups=None):
        """
        Group boundary conditions of each variable. Each condition is a
        group is a single condition.

        Parameters
        ----------
        groups : dict, optional
            If present, update the `groups` dictionary.

        Returns
        -------
        out : dict
            The dictionary with variable names as keys and lists of
            single condition instances as values.
        """
        if groups is None:
            out = {}

        else:
            out = groups

        for cond in self:
            for single_cond in cond.iter_single():
                vname = single_cond.dofs[0].split('.')[0]
                out.setdefault(vname, Conditions()).append(single_cond)

        return out

    def canonize_dof_names(self, dofs):
        """
        Canonize the DOF names using the full list of DOFs of a
        variable.
        """
        for cond in self:
            cond.canonize_dof_names(dofs)

    def sort(self):
        """
        Sort boundary conditions by their key.
        """
        self._objs.sort(key=lambda a: a.key)
        self.update()

    def zero_dofs(self):
        """
        Set all boundary condition values to zero, if applicable.
        """
        for cond in self:
            if isinstance(cond, EssentialBC):
                cond.zero_dofs()

def _canonize(dofs, all_dofs):
    """
    Helper function.
    """
    vname, dd = dofs.split('.')

    if dd == 'all':
        cdofs = all_dofs

    elif dd[0] == '[':
        cdofs = [vname + '.' + ii.strip()
                 for ii in dd[1:-1].split(',')]

    else:
        cdofs = [dofs]

    return cdofs

class Condition(Struct):
    """
    Common boundary condition methods.
    """
    def __init__(self, name, **kwargs):
        
Struct.__init__(self, name=name, **kwargs)