# *****************************************************************************
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#
#   See NOTICE file for details.
#
# *****************************************************************************
import _jpype
from . import _jcustomizer


__all__ = ['JArray']


class JArray(_jpype._JObject, internal=True):  # type: ignore[call-arg]
    """ Creates a Java array class for a Java type of a given dimension.

    This serves as a base type and factory for all Java array classes.
    The resulting Java array class can be used to construct a new
    array with a given size or specified members.

    JPype arrays support Python operators for iterating, length, equals, 
    not equals, subscripting, and slicing. They also support Java
    object methods, clone, and length property. Java arrays may not
    be resized, and as such elements cannot be added nor deleted. Currently,
    applying the slice operator produces a new Python sequence.

    Example:
        .. code-block:: python

          # Define a new array class for ``int[]``
          IntArrayCls = JArray(JInt)

          # Create an array holding 10 elements
          #   equivalent to Java ``int[] x=new int[10]``
          x = IntArrayCls(10)

          # Create a length 3 array initialized with [1,2,3]
          #   equivalent to Java ``int[] x = new int[]{1,2,3};``
          x = IntArrayCls([1,2,3])

          # Operate on an array
          print(len(x))
          print(x[0])
          print(x[:-2])
          x[1:]=(5,6)

          if isinstance(x, JArray):
               print("object is a java array")

          if issubclass(IntArrayCls, JArray):
               print("class is a java array type.")

    Args:
      javaClass (str,type): Is the type of element to hold in
        the array.
      ndims (Optional,int): the number of dimensions of the array
        (default=1)

    Returns:
      A new Python class that representing a Java array class.

    Raises:
      TypeError: if the component class is invalid or could not be found.

    Note:
      javaClass can be specified in three ways:

        - as a string with the name of a java class.
        - as a Java primitive type such as ``jpype.JInt``.
        - as a Java class type such as ``java.lang.String``.


    """
    def __new__(cls, tp, dims=1):
        if cls != JArray:
            raise TypeError("Arrays factory can't be used as type")
        jc = _toJavaClass(tp)
        return _jpype._newArrayType(jc, dims)

    @classmethod
    def of(cls, array, dtype=None):
        return _jpype.arrayFromBuffer(array, dtype)

    def __class_getitem__(cls, key):
        if key is _jpype.JClass:
            # explicit check for JClass
            # _toJavaClass cannot be used
            # passing int, float, str, etc is not allowed
            key = _jpype._java_lang_Class
        if isinstance(key, _jpype._java_lang_Class):
            key = _jpype.JClass(key)
        if isinstance(key, _jpype.JClass):
            return type(key[0])
        raise TypeError("%s is not a Java class" % key)


class _JArrayProto(object):

    def __str__(self):
        return str(list(self))

    def __iter__(self):
        return _JavaArrayIter(self)

    def __reversed__(self):
        for elem in self[::-1]:
            yield elem

    def clone(self):
        """ Clone the Java array.

        Create a "shallow" copy of a Java array. For a
        single dimensional array of primitives, the cloned array is
        complete independent copy of the original. For objects or
        multidimensional arrays, the new array is a copy which points
        to the same members as the original.

        To obtain a deep copy of a Java array, use Java serialize and
        deserialize operations to duplicate the entire array and
        contents. In order to deep copy, the objects must be
        Serializable.

        Returns:
            A shallow copy of the array.
        """
        return _jpype.JClass("java.util.Arrays").copyOf(self, len(self))


def _toJavaClass(tp):
    """(internal) Converts a class type in python into a internal java class.

    Used mainly to support JArray.

    The type argument will operate on:
     - (str) lookup by class name or fail if not found.
     - (JClass) just returns the java type.
     - (type) uses a lookup table to find the class.
    """
    # if it a string than we lookup the class by name.
    if isinstance(tp, str):
        return _jpype._java_lang_Class.forName(tp)

    # if is a java.lang.Class instance, then no coversion required
    if isinstance(tp, _jpype._JClass):
        return tp

    # Okay then it must be a type
    try:
        return _jpype._type_classes[tp].class_
    except KeyError:
        pass

    # See if it a class type
    try:
        return tp.class_
    except AttributeError:
        pass

    raise TypeError("Unable to find class for '%s'" % tp.__name__)


# FIXME are these not sequences?  They act like sequences but are they
# connected to collections.abc.Sequence
# has: __len__, __iter__, __getitem__
# missing: __contains__ (required for in)
# Cannot be Mutable because java arrays are fixed in length


class _JavaArrayIter(object):
    def __init__(self, a):
        self._array = a
        self._ndx = -1

    def __iter__(self):
        return self

    def __next__(self):
        self._ndx += 1
        if self._ndx >= len(self._array):
            raise StopIteration
        return self._array[self._ndx]

    next = __next__

# **********************************************************
# Char array customizer


@_jcustomizer.JImplementationFor("byte[]")
@_jcustomizer.JImplementationFor("char[]")
class _JCharArray(object):
    def __str__(self):
        return str(_jpype.JString(self))

    def __eq__(self, other):
        if isinstance(other, str):
            return str(self) == other
        try:
            return self.equals(self.__class__(other))
        except TypeError:
            return False
    __hash__ = _jpype._JObject.__hash__


# Install module hooks
_jcustomizer._applyCustomizerPost(_jpype._JArray, _JArrayProto)
_jpype.JArray = JArray
