# -*- coding: utf-8 -*-
# Copyright (C) 2009-2011 Anders Logg
#
# This file is part of DOLFIN.
#
# DOLFIN is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DOLFIN is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DOLFIN. If not, see <http://www.gnu.org/licenses/>.
#
# Modified by Garth N. Wells, 2012
#
# First added:  2008-10-22
# Last changed: 2012-08-18

import types

from six import string_types

import dolfin.cpp as cpp
from dolfin.functions.constant import Constant
from dolfin.compilemodules.subdomains import CompiledSubDomain
import ufl
from dolfin.fem.projection import project

__all__ = ["AutoSubDomain", "DirichletBC"]


class AutoSubDomain(cpp.SubDomain):
    "Wrapper class for creating a SubDomain from an inside() function."

    def __init__(self, inside_function):
        "Create SubDomain subclass for given inside() function"

        # Check that we get a function
        if not isinstance(inside_function, types.FunctionType):
            cpp.dolfin_error("bcs.py",
                             "auto-create subdomain",
                             "Expecting a function (not %s)" %
                             str(type(inside_function)))
        self.inside_function = inside_function

        # Check the number of arguments
        if inside_function.__code__.co_argcount not in (1, 2):
            cpp.dolfin_error("bcs.py",
                             "auto-create subdomain",
                             "Expecting a function of the form inside(x) or inside(x, on_boundary)")
        self.num_args = inside_function.__code__.co_argcount

        cpp.SubDomain.__init__(self)

    def inside(self, x, on_boundary):
        "Return true for points inside the subdomain"

        if self.num_args == 1:
            return self.inside_function(x)
        else:
            return self.inside_function(x, on_boundary)


class DirichletBC(cpp.DirichletBC):

    # Reuse doc-string from cpp.DirichletBC
    __doc__ = cpp.DirichletBC.__doc__

    def __init__(self, *args, **kwargs):
        "Create Dirichlet boundary condition"

        # Copy constructor
        if len(args) == 1:
            if not isinstance(args[0], cpp.DirichletBC):
                cpp.dolfin_error("bcs.py",
                                 "create DirichletBC",
                                 "Expecting a DirichleBC as only argument"
                                 " for copy constructor")

            # Initialize base class
            cpp.DirichletBC.__init__(self, args[0])
            self.domain_args = args[0].domain_args
            return

        mpi_comm = args[0].mesh().mpi_comm()

        # Special case for value specified as float, tuple or similar
        if len(args) >= 2 and not isinstance(args[1], cpp.GenericFunction):
            if isinstance(args[1], ufl.classes.Expr):
                expr = project(args[1], args[0])
            else:
                expr = Constant(args[1])  # let Constant handle all problems
            args = args[:1] + (expr,) + args[2:]

        # Special case for sub domain specified as a function
        if len(args) >= 3 and isinstance(args[2], types.FunctionType):
            sub_domain = AutoSubDomain(args[2])
            args = args[:2] + (sub_domain,) + args[3:]

        # Special case for sub domain specified as a string
        if len(args) >= 3 and isinstance(args[2], string_types):
            sub_domain = CompiledSubDomain(args[2], mpi_comm=mpi_comm)
            args = args[:2] + (sub_domain,) + args[3:]

        # Store Expression to avoid scoping issue with SWIG directors
        if isinstance(args[1], cpp.Expression):
            self.function_arg = args[1]

        # Store SubDomain to avoid scoping issue with SWIG directors
        self.domain_args = args[2:]

        # FIXME: Handling of multiple default arguments does not
        # really work between Python and C++ when C++ requires them to
        # be ordered and cannot accept the latter without the
        # former...

        # Add keyword arguments (in correct order...)
        allowed_kwargs = ["method", "check_midpoint"]
        for key in allowed_kwargs:
            if key in kwargs:
                args = tuple(list(args) + [kwargs[key]])

        # Check for other keyword arguments
        for key in kwargs:
            if key not in allowed_kwargs:
                cpp.dolfin_error("bcs.py",
                                 "create boundary condition",
                                 "Unknown keyword argument \"%s\"" % key)

        # Initialize base class
        cpp.DirichletBC.__init__(self, *args)

    # Set doc string
    __init__.__doc__ = cpp.DirichletBC.__init__.__doc__
