"""This module is used by vtkPythonSelector to perform query-based
selections. It relies on the python-calculator (vtkPythonCalculator),
specifically, the Python code used by that class, to compute a mask array from
the query expression. Once the mask array is obtained, this module will
mark those elements as requested.
"""
from __future__ import absolute_import, print_function

try:
    import numpy as np
except ImportError:
    raise RuntimeError("'numpy' module is not found. numpy is needed for " \
                       "this functionality to work. Please install numpy and try again.")

import re
import vtkmodules.numpy_interface.dataset_adapter as dsa
import vtkmodules.numpy_interface.algorithms as algos
from vtkmodules.vtkCommonDataModel import vtkDataObject
from vtkmodules.util import vtkConstants
from . import calculator

# import wrapping module for `vtkPythonSelector`
from paraview.modules import vtkPVVTKExtensionsExtractionPython

import sys

if sys.hexversion < 0x03000000:
    import itertools

    izip = itertools.izip
else:
    izip = zip


def _create_id_array(dataobject, attributeType):
    """Returns a VTKArray or VTKCompositeDataArray for the ids"""
    if not dataobject:
        raise RuntimeError("dataobject cannot be None")
    if dataobject.IsA("vtkCompositeDataSet"):
        ids = []
        for ds in dataobject:
            ids.append(_create_id_array(ds, attributeType))
        return dsa.VTKCompositeDataArray(ids)
    else:
        numElems = dataobject.GetNumberOfElements(attributeType)
        return dsa.VTKArray(np.arange(numElems)) if numElems > 0 else dsa.NoneArray


def maskarray_is_valid(maskArray):
    """Validates that the maskArray is either a VTKArray,
    VTKCompositeDataArray, ndarray or a NoneArray other returns false."""
    return maskArray is dsa.NoneArray or \
        isinstance(maskArray, dsa.VTKArray) or \
        isinstance(maskArray, dsa.VTKCompositeDataArray) or \
        isinstance(maskArray, np.ndarray)


def execute(inputDO, selectionNode, insidednessArrayName, outputDO):
    field_type = selectionNode.GetFieldType()
    if field_type == selectionNode.CELL:
        attributeType = vtkDataObject.CELL
    elif field_type == selectionNode.POINT:
        attributeType = vtkDataObject.POINT
    elif field_type == selectionNode.ROW:
        attributeType = vtkDataObject.ROW
    else:
        raise RuntimeError("Unsupported field attributeType %r" % field_type)
    # Evaluate expression on the inputDO.
    # This is equivalent to executing the Python Calculator on the input dataset
    # to produce a mask array.

    inputs = []
    inputs.append(dsa.WrapDataObject(inputDO))

    query = selectionNode.GetQueryString()

    # Get a dictionary for arrays in the dataset attributes. We pass that
    # as the variables in the eval namespace for calculator.compute().
    elocals = calculator.get_arrays(inputs[0].GetAttributes(attributeType))
    if ("id" not in elocals) and re.search(r'\bid\b', query):
        # Add "id" array if the query string refers to id.
        # This is a temporary fix. We should look into
        # accelerating id-based selections in the future.
        elocals["id"] = _create_id_array(inputs[0], attributeType)
    try:
        maskArray = calculator.compute(inputs, query, ns=elocals)
    except:
        from sys import stderr
        print("Error: Failed to evaluate Expression '%s'. " \
              "The following exception stack should provide additional developer " \
              "specific information. This typically implies a malformed " \
              "expression. Verify that the expression is valid.\n" % query, file=stderr)
        raise

    if not maskarray_is_valid(maskArray):
        raise RuntimeError(
            "Expression '%s' did not produce a valid mask array. The value " \
            "produced is of the type '%s'. This typically implies a malformed " \
            "expression. Verify that the expression is valid." % \
            (query, type(maskArray)))

    # Preserve topology. Just add the mask array as vtkSignedCharArray to the
    # output.
    # Note: we must force the data type to VTK_SIGNED_CHAR or the array will
    # be ignored by the freeze selection operation
    from paraview.vtk.util import numpy_support
    output = dsa.WrapDataObject(outputDO)
    if maskArray is not dsa.NoneArray:
        if isinstance(maskArray, dsa.VTKCompositeDataArray):
            for ds, array in izip(output, maskArray.Arrays):
                if array is not dsa.NoneArray:
                    insidedness = numpy_support.numpy_to_vtk(array, deep=1, array_type=vtkConstants.VTK_SIGNED_CHAR)
                    insidedness.SetName(insidednessArrayName)
                    ds.GetAttributes(attributeType).VTKObject.AddArray(insidedness)
        else:
            insidedness = numpy_support.numpy_to_vtk(maskArray, deep=1, array_type=vtkConstants.VTK_SIGNED_CHAR)
            insidedness.SetName(insidednessArrayName)
            output.GetAttributes(attributeType).VTKObject.AddArray(insidedness)
