"""
Problem description file handling.

Notes
-----

Short syntax: key is suffixed with '__<number>' to prevent collisions with long
syntax keys -> both cases can be used in a single input.
"""
from __future__ import absolute_import
import re
import numpy as nm

from sfepy.base.base import (Struct, IndexedStruct, dict_to_struct,
                             output, copy, update_dict_recursively,
                             import_file, assert_, get_default, basestr)
from sfepy.base.parse_conf import create_bnf
import six

_required = ['filename_mesh|filename_domain', 'field_[0-9]+|fields',
            # TODO originaly EBC were required to be specified but in some examples
            #  (especially 1D) only EPBCs specified is valid
             'ebc_[0-9]+|ebcs|dgebc_[0-9]+|dgebcs|dgepbc_[0-9]+|dgepbcs',
             'equations',
             'region_[0-9]+|regions', 'variable_[0-9]+|variables',
             'material_[0-9]+|materials',
             'solver_[0-9]+|solvers']

_other = ['epbc_[0-9]+|epbcs',
          'lcbc_[0-9]+|lcbcs', 'nbc_[0-9]+|nbcs',
          'ic_[0-9]+|ics', 'function_[0-9]+|functions', 'options',
          'integral_[0-9]+|integrals']

def get_standard_keywords():
    return copy(_required), copy(_other)

def tuple_to_conf(name, vals, order):
    """
    Convert a configuration tuple `vals` into a Struct named `name`, with
    attribute names given in and ordered by `order`.

    Items in `order` at indices outside the length of `vals` are ignored.
    """
    conf = Struct(name=name)
    for ii, key in enumerate(order[:len(vals)]):
        setattr(conf, key, vals[ii])
    return conf

def transform_variables(adict):
    d2 = {}
    for ii, (key, conf) in enumerate(six.iteritems(adict)):
        if isinstance(conf, tuple):
            c2 = tuple_to_conf(key, conf, ['kind', 'field'])
            if len(conf) >= 3:
                kind = c2.kind.split()[0]
                if kind == 'unknown':
                    c2.order = conf[2]
                elif kind == 'test':
                    c2.dual = conf[2]
                elif kind == 'parameter':
                    if isinstance(conf[2], basestr) or (conf[2] is None):
                        c2.like = conf[2]
                    else:
                        c2.like = None
                        c2.special = conf[2]
                if len(conf) == 4:
                    c2.history = conf[3]
            d2['variable_%s__%d' % (c2.name, ii)] = c2
        else:
            c2 = transform_to_struct_1(conf)
            d2['variable_'+c2.name] = c2
    return d2

def transform_conditions(adict, prefix):
    d2 = {}
    for ii, (key, conf) in enumerate(six.iteritems(adict)):
        if isinstance(conf, tuple):
            if len(conf) == 2:
                c2 = tuple_to_conf(key, conf, ['region', 'dofs'])

            else:
                c2 = tuple_to_conf(key, conf, ['region', 'times', 'dofs'])

            d2['%s_%s__%d' % (prefix, c2.name, ii)] = c2

        else:
            c2 = transform_to_struct_1(conf)
            d2['%s_%s' % (prefix, c2.name)] = c2

    return d2

def transform_dgebcs(adict):
    return transform_conditions(adict, "dgebc")

def transform_ebcs(adict):
    return transform_conditions(adict, 'ebc')

def transform_ics(adict):
    return transform_conditions(adict, 'ic')

def transform_lcbcs(adict):
    d2 = {}
    for ii, (key, conf) in enumerate(six.iteritems(adict)):
        if isinstance(conf, tuple):
            if len(conf) >= 4:
                if isinstance(conf[1], dict):
                    c2 = tuple_to_conf(key, conf, ['region', 'dofs',
                                                   'dof_map_fun', 'kind'])
                    c2.arguments = conf[4:]

                else:
                    c2 = tuple_to_conf(key, conf, ['region', 'times', 'dofs',
                                                   'dof_map_fun', 'kind'])
                    c2.arguments = conf[5:]

            else:
                msg = 'LCBC syntax has to be: region[, times], dofs,' \
                      ' dof_map_fun, kind[, other arguments]'
                raise SyntaxError(msg)

            d2['lcbc_%s__%d' % (c2.name, ii)] = c2

        else:
            c2 = transform_to_struct_1(conf)
            c2.set_default('dof_map_fun', None)
            c2.set_default('arguments', ())
            d2['lcbc_%s' % (c2.name)] = c2

    return d2

def transform_epbcs(adict, prefix="epbc"):
    d2 = {}
    for ii, (key, conf) in enumerate(six.iteritems(adict)):
        if isinstance(conf, tuple):
            if len(conf) == 3:
                c2 = tuple_to_conf(key, conf, ['region', 'dofs', 'match'])

            else:
                c2 = tuple_to_conf(key, conf,
                                   ['region', 'times', 'dofs', 'match'])

            d2['%s_%s__%d' % (prefix, c2.name, ii)] = c2
        else:
            c2 = transform_to_struct_1(conf)
            d2['%s_%s' % (prefix, c2.name)] = c2
    return d2

def transform_dgepbcs(adict):
    return transform_epbcs(adict, "dgepbc")

def transform_regions(adict):
    d2 = {}
    for ii, (key, conf) in enumerate(six.iteritems(adict)):
        if isinstance(conf, basestr):
            c2 = Struct(name=key, select=conf)
            d2['region_%s__%d' % (c2.name, ii)] = c2
        elif isinstance(conf, tuple):
            c2 = tuple_to_conf(key, conf, ['select', 'kind'])
            if len(conf) == 3:
                c2.parent = conf[2]
            if len(conf) == 4:
                c2.parent = conf[2]
                c2.extra_options = conf[3]
            d2['region_%s__%d' % (c2.name, ii)] = c2
        else:
            c2 = transform_to_struct_1(conf)
            d2['region_'+c2.name] = c2
    return d2

def transform_integrals(adict):
    d2 = {}
    for ii, (key, conf) in enumerate(six.iteritems(adict)):
        if isinstance(conf, int):
            c2 = Struct(name=key, order=conf)
            d2['integral_%s__%d' % (c2.name, ii)] = c2

        elif isinstance(conf, tuple):
            if len(conf) == 2: # Old tuple version with now-ignored 'kind'.
                conf = conf[1]
                c2 = Struct(name=key, order=conf)

            elif len(conf) == 3:
                c2 = tuple_to_conf(key, conf, ['order', 'vals', 'weights'])

            d2['integral_%s__%d' % (c2.name, ii)] = c2

        else:
            c2 = transform_to_struct_1(conf)
            d2['integral_'+c2.name] = c2

    return d2

def transform_fields(adict):
    dtypes = {'real' : nm.float64, 'complex' : nm.complex128}
    d2 = {}
    for ii, (key, conf) in enumerate(six.iteritems(adict)):
        if isinstance(conf, tuple):
            c2 = tuple_to_conf(key, conf,
                               ['dtype', 'shape', 'region', 'approx_order',
                                'space', 'poly_space_base'])
            if c2.dtype in dtypes:
                c2.dtype = dtypes[c2.dtype]
            d2['field_%s__%d' % (c2.name, ii)] = c2
        else:
            c2 = transform_to_struct_1(conf)
            c2.set_default('dtype', nm.float64)
            if c2.dtype in dtypes:
                c2.dtype = dtypes[c2.dtype]
            d2['field_'+c2.name] = c2
    return d2

def transform_materials(adict):
    d2 = {}
    for ii, (key, conf) in enumerate(six.iteritems(adict)):
        if isinstance(conf, basestr):
            c2 = Struct(name=key, function=conf)
            d2['material_%s__%d' % (c2.name, ii)] = c2

        elif isinstance(conf, tuple):
            c2 = tuple_to_conf(key, conf,
                               ['values', 'function', 'kind'])
            if len(conf) == 4:
                c2.flags = conf[3]
            d2['material_%s__%d' % (c2.name, ii)] = c2

        else:
            c2 = transform_to_struct_1(conf)
            d2['material_'+conf['name']] = c2

    return d2

def transform_solvers(adict):
    d2 = {}
    for ii, (key, conf) in enumerate(six.iteritems(adict)):
        if isinstance(conf, tuple):
            c2 = tuple_to_conf(key, conf, ['kind','params'])
            for param, val in six.iteritems(c2.params):
                setattr(c2, param, val)
            delattr(c2, 'params')
            d2['solvers_%s__%d' % (c2.name, ii)] = c2
        else:
            c2 = transform_to_struct_1(conf)
            d2['solvers_'+c2.name] = c2
    return d2

def transform_functions(adict):
    d2 = {}
    for ii, (key, conf) in enumerate(six.iteritems(adict)):
        if isinstance(conf, tuple):
            c2 = tuple_to_conf(key, conf, ['function'])
            d2['function_%s__%d' % (c2.name, ii)] = c2
        else:
            c2 = transform_to_struct_1(conf)
            d2['function_'+c2.name] = c2
    return d2

def transform_to_struct_1(adict):
    return dict_to_struct(adict, flag=(1,))
def transform_to_i_struct_1(adict):
    return dict_to_struct(adict, flag=(1,), constructor=IndexedStruct)
def transform_to_struct_01(adict):
    return dict_to_struct(adict, flag=(0,1))
def transform_to_struct_10(adict):
    return 
dict_to_struct(adict, flag=(1,0))