"""This module defines the Terminal class, the superclass
for all types that are terminal nodes in the expression trees."""

# Copyright (C) 2008-2014 Martin Sandve Alnes
#
# This file is part of UFL.
#
# UFL 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.
#
# UFL 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 UFL. If not, see <http://www.gnu.org/licenses/>.
#
# Modified by Anders Logg, 2008

from ufl.log import error, warning
from ufl.assertions import ufl_assert
from ufl.common import EmptyDict
from ufl.common import counted_init
from ufl.core.expr import Expr
from ufl.core.ufl_type import ufl_type

#--- Base class for terminal objects ---

@ufl_type(is_abstract=True, is_terminal=True)
class Terminal(Expr):
    "A terminal node in the UFL expression tree."
    __slots__ = ()

    def __init__(self):
        Expr.__init__(self)

    def reconstruct(self, *operands):
        "Return self."
        if operands:
            error("Got call to reconstruct in a terminal with non-empty operands.")
        return self

    ufl_operands = ()
    ufl_free_indices = ()
    ufl_index_dimensions = ()

    def operands(self):
        "A Terminal object never has operands."
        return ()

    def free_indices(self):
        "A Terminal object never has free indices."
        return ()

    def index_dimensions(self):
        "A Terminal object never has free indices."
        return EmptyDict

    def domains(self):
        "Return tuple of domains related to this terminal object."
        raise NotImplementedError("Missing implementation of domains().")

    def evaluate(self, x, mapping, component, index_values, derivatives=()):
        "Get self from mapping and return the component asked for."
        f = mapping.get(self)
        # No mapping, trying to evaluate self as a constant
        if f is None:
            try:
                f = float(self)
                if derivatives:
                    f = 0.0
                return f
            except:
                pass
            # If it has an ufl_evaluate function, call it
            if hasattr(self, 'ufl_evaluate'):
                return self.ufl_evaluate(x, component, derivatives)
            # Take component if any
            warning("Couldn't map '%s' to a float, returning ufl object without evaluation." % str(self))
            f = self
            if component:
                f = f[component]
            return f

        # Found a callable in the mapping
        if callable(f):
            if derivatives:
                f = f(x, derivatives)
            else:
                f = f(x)
        else:
            if derivatives:
                return 0.0

        # Take component if any (expecting nested tuple)
        for c in component:
            f = f[c]
        return f

    def signature_data(self, renumbering):
        "Default signature data for of terminals just return the repr string."
        return repr(self)

    def _ufl_compute_hash_(self):
        "Default hash of terminals just hash the repr string."
        return hash(repr(self))

    def __eq__(self, other):
        "Default comparison of terminals just compare repr strings."
        return repr(self) == repr(other)


#--- Subgroups of terminals ---

@ufl_type(is_abstract=True)
class FormArgument(Terminal):
    __slots__ = ()
    def __init__(self):
        Terminal.__init__(self)
