###############################################################################
##
## Copyright (C) 2014-2015, New York University.
## Copyright (C) 2011-2014, NYU-Poly.
## Copyright (C) 2006-2011, University of Utah.
## All rights reserved.
## Contact: contact@vistrails.org
##
## This file is part of VisTrails.
##
## "Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions are met:
##
##  - Redistributions of source code must retain the above copyright notice,
##    this list of conditions and the following disclaimer.
##  - Redistributions in binary form must reproduce the above copyright
##    notice, this list of conditions and the following disclaimer in the
##    documentation and/or other materials provided with the distribution.
##  - Neither the name of the New York University nor the names of its
##    contributors may be used to endorse or promote products derived from
##    this software without specific prior written permission.
##
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
## THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
## PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
## EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
## PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
## OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
## WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
## OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
## ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
##
###############################################################################

# Author: Prabhu Ramachandran
# Copyright (c) 2004, Enthought, Inc.
# License: BSD Style.

# Modified for VisTrails by the VisTrails team.

"""This module parses the VTK methods, obtains the argument and return
type information, and organizes them.

"""
# Author: Prabhu Ramachandran
# Copyright (c) 2004-2007, Enthought, Inc.
# License: BSD Style.

from __future__ import division

import re

# Local imports (these are relative imports for a good reason).
import class_tree
import vtk_module as vtk


class VTKMethodParser:
    """This class provides useful methods for parsing methods of a VTK
    class or instance.

    The class allows one to categorize the methods of the VTK class
    and also obtain the method signatures in a form that is easy to
    use.  When the `parse` method is called, it in turn calls the
    `_organize_methods` method.  This method organizes the VTK methods
    into different instance variables described in the following.
    `self.toggle_meths` contains a dictionary of all the boolean
    methods of the form <Value>On/Off.  The dictionary keys are
    strings with the <Value>'s and the value of each item is the
    default value (0/1) of the item (the example below will clarify
    this).  `self.state_meths` contains a dictionary which collects
    the Set<Prop>To<Value> type of methods.  The key is the <Prop> and
    the value is a list containing the different string <Value>'s and
    their corresponding mapped value.  The first value in these is the
    default value of the <Prop>.  `self.get_set_meths` will contain a
    dictionary which collects all the methods of the form
    Set/Get<Prop> that are not already specified in
    `self.toggle_meths` or `self.state_meths`.  The default value of
    the Get<Prop> is stored.  If the value accepted by the method has
    a range (via the methods `Get<Prop>MinValue` and
    `Get<Prop>MaxValue`), then that range is computed and stored.
    `self.get_meths` stores the methods that are of the form
    `Get<Prop>`.  `self.other_meths` stores the remaining methods.
    The parsing is quite fast.  Parsing every class in the VTK API
    takes a couple of seconds (on a Pentium III @ 450Mhz).

    Here is an example::

       >>> import vtk
       >>> p = VTKMethodParser()
       >>> p.parse(vtk.vtkProperty)
       >>> print p.get_toggle_methods()
       {'EdgeVisibility': 0, 'BackfaceCulling': 0, 'FrontfaceCulling': 0}
       >>> print p.get_state_methods()['Representation']
       [['Surface', 2], ['Points', 0], ['Surface', 2], ['Wireframe', 1]]
       >>> print p.get_get_set_methods()['Opacity']
       (1.0, (0.0, 1.0))
       >>> print p.get_get_methods()
       ['GetClassName']
       >>> print p.get_other_methods()[:3]
       ['BackfaceRender', 'DeepCopy', 'IsA']


    The class also provides a method called `get_method_signature`
    that obtains the Python method signature given the VTK method
    object.  Here is an example::

       >>> import vtk
       >>> p = VTKMethodParser()
       >>> o = vtk.vtkProperty
       >>> print p.get_method_signature(o.GetClassName)
       [(['string'], None)]
       >>> print p.get_method_signature(o.GetColor)[0]
       ([('float', 'float', 'float')], None)
       >>> print p.get_method_signature(o.GetColor)[1]
       ([None], (('float', 'float', 'float'),))

    The `get_method_signature` is fairly efficient and obtaining the
    signature for every method in every class in the VTK API takes
    around 6 seconds (on a Pentium III @ 450Mhz).

    """

    def __init__(self, use_tree=True):
        """Initializes the object.

        Parameters
        ----------

        - use_tree : `bool`

          If True (default), use a ClassTree instance to obtain a
          concrete subclass for an abstract base class.  This is used
          only to find the range and default values for some of the
          methods.  If False, no ClassTree instance is created.

          This is optional because, creating a ClassTree is expensive.
          The parser functionality can be very useful even without the
          use of a ClassTree.  For example, if one wants to save the
          state of a VTK object one only needs to know the names of
          the methods and not their default values, ranges etc.  In
          that case using a parser should be cheap.

        """
        # The ClassTree is needed to find an instantiable child class
        # for an abstract VTK parent class.  This instance is used to
        # obtain the state values and the ranges of the arguments
        # accepted by the Get/Set methods that have a
        # Get<Prop>{MaxValue,MinValue} method.
        if use_tree:
            self._tree = class_tree.ClassTree(vtk)
            self._tree.create()
        else:
            self._tree = None
        self._state_patn = re.compile('To[A-Z0-9]')
        self._initialize()

    #################################################################
    # 'VTKMethodParser' interface.
    #################################################################

    def parse(self, obj, no_warn=True):
        """Parse the methods for a given VTK object/class.

        Given a VTK class or object, this method parses the methods
        and orgaizes them into useful categories.  The categories and
        their usage is documented in the documentation for the class.

        Parameters
        ----------

        - obj : VTK class or instance

        - no_warn : `bool` (default: True)

          If True (default), it suppresses any warnings generated by
          the VTK object when parsing the methods.  This is safe to
          use.

        """
        if not hasattr(obj, '__bases__'):
            klass = obj.__class__
        else:
            klass = obj

        methods = self.get_methods(klass)

        if no_warn:
            # Save warning setting and shut it off before parsing.
            warn = vtk.vtkObject.GetGlobalWarningDisplay()
            if klass.__name__ <> 'vtkObject':
                vtk.vtkObject.GlobalWarningDisplayOff()

        self._organize_methods(klass, methods)

        if no_warn:
            # Reset warning status.
            vtk.vtkObject.SetGlobalWarningDisplay(warn)

    def _get_parent_methods(self, klass):
        """Returns all the methods of the classes parents."""
        methods = {}
        while len(klass.__bases__) > 0:
            klass = klass.__bases__[0]
            meths = dir(klass)
            d = methods.fromkeys(meths)
            methods.update(d)
        return methods.keys()

    def get_methods(self, klass):
        """Returns all the relevant methods of the given VTK class."""
        methods = dir(klass)[:]
        if hasattr(klass, '__members__'):
            # Only VTK versions < 4.5 have these.
            for m in klass.__members__:
                methods.remove(m)
        # Ignore the parent methods.
        ignore = self._get_parent_methods(klass)

        # Skip some of the ignores.
        skip = ['GetInput', 'SetInput']
        # Sometimes the child has only GetInput while the parent has
        # SetInput.
        if hasattr(klass, 'SetInput') and \
            'SetInput' not in methods and \
            'GetInput' in methods:
            methods.append('SetInput')

        # Get/set pairs that are overridden.  Basically, if a parent
        # class has a 'GetThing' and the child overrides/has a
        # 'SetThing' (or vice-versa), then the removal of the parent
        # methods is wrong since the child changes the trait definition
        # which breaks things.  We therefore do not remove any of the
        # Get/SetThings that are ignored due to them being in the
        # parent.  However one has to be careful about cases where these are
        # really Toggle (ThingOn) or State (SetThingToThong) etc. methods and
        # in those cases we really should ignore the method.  So in essence,
        # any Get/Set pair that is not a State or Toggle should be redefined.
        overrides = []
        for m in methods:
            check = False
            if m.startswith('Get'):
                m1 = 'Set' + m[3:]
                check = True
            elif m.startswith('Set'):
                m1 = 'Get' + m[3:]
                check = True
            if check:
                if m1 in methods and (m1 in ignore or m in ignore):
                    # Skips are stored as Set followed by Get.
                    skip.extend(['Set' +m[3:], 'Get'+m[3:]])

        for m in skip[:]:
            if m.startswith('Set'):
                base = m[3:]
                mg, ms = 'Get' + base, 'Set' + base
                m_st = 'Set' + base + 'To'
                m_t = base + 'Off'
                for method in methods:
                    if m_st in method or m_t == method:
                        skip.remove(ms)
                        skip.remove(mg)
                        break

        if 'GetViewProp' in methods and 'GetProp' in methods:
            ignore.extend(['GetProp', 'SetProp'])
        if 'GetViewProps' in methods and 'GetProps' in methods:
            ignore.extend(['GetProps', 'SetProps'])
        # Remove any deprecated traits.
        if 'GetScaledText' in methods and 'GetTextScaleMode' in methods:
            ignore.extend(['GetScaledText', 'SetScaledText',
                           'ScaledTextOn', 'ScaledTextOff'])

        # Now we can safely remove the methods.
        for m in methods[:]:
            if m in ignore and m not in skip:
                methods.remove(m)

        return methods

    def get_toggle_methods(self):
        """Returns a dictionary of the parsed <Value>On/Off methods
        along with the default value.

        """
        return self.toggle_meths

    def get_state_methods(self):
        """Returns a dict of the parsed Set<Prop>To<Value>.

        The keys are the <Prop> string with a list of the different
        <Value> strings along with their corresponding value (if
        obtainable).  The first value is the default value of the
        state.

        """
        return self.state_meths

    def get_get_set_methods(self):
        """Returns a dict of the parsed Get/Set<Value> methods.

        The keys of the dict are the <Value> strings and contain a
        two-tuple containing the default value (or None if it is not
        obtainable for some reason) and a pair of numbers specifying
        an acceptable range of values (or None if not obtainable).

        """
        return self.get_set_meths

    def get_get_methods(self):
        """Return a list of parsed Get<Value> methods.

        All of these methods do NOT have a corresponding Set<Value>.

        """
        return self.get_meths

    def get_other_methods(self):
        """Return list of all other methods, that are not
        categorizable.

        """
        return self.other_meths

    def get_method_signature(self, method):
        """Returns information on the Python method signature given
        the VTK method.

        The doc string of the given method object to get the method
        signature.  The method returns a list of tuples, each of which
        contains 2 items, the first is a list representing the return
        value the second represents the arguments to be passed to the
        function.  If the method supports different return values and