from django.contrib.gis.geos import prototypes as capi
from django.contrib.gis.geos.coordseq import GEOSCoordSeq
from django.contrib.gis.geos.error import GEOSException
from django.contrib.gis.geos.geometry import (
    GEOSGeometry, ProjectInterpolateMixin,
)
from django.contrib.gis.geos.point import Point
from django.contrib.gis.shortcuts import numpy
from django.utils.six.moves import range


class LineString(ProjectInterpolateMixin, GEOSGeometry):
    _init_func = capi.create_linestring
    _minlength = 2
    has_cs = True

    def __init__(self, *args, **kwargs):
        """
        Initializes on the given sequence -- may take lists, tuples, NumPy arrays
        of X,Y pairs, or Point objects.  If Point objects are used, ownership is
        _not_ transferred to the LineString object.

        Examples:
         ls = LineString((1, 1), (2, 2))
         ls = LineString([(1, 1), (2, 2)])
         ls = LineString(array([(1, 1), (2, 2)]))
         ls = LineString(Point(1, 1), Point(2, 2))
        """
        # If only one argument provided, set the coords array appropriately
        if len(args) == 1:
            coords = args[0]
        else:
            coords = args

        if isinstance(coords, (tuple, list)):
            # Getting the number of coords and the number of dimensions -- which
            #  must stay the same, e.g., no LineString((1, 2), (1, 2, 3)).
            ncoords = len(coords)
            if coords:
                ndim = len(coords[0])
            else:
                raise TypeError('Cannot initialize on empty sequence.')
            self._checkdim(ndim)
            # Incrementing through each of the coordinates and verifying
            for i in range(1, ncoords):
                if not isinstance(coords[i], (tuple, list, Point)):
                    raise TypeError('each coordinate should be a sequence (list or tuple)')
                if len(coords[i]) != ndim:
                    raise TypeError('Dimension mismatch.')
            numpy_coords = False
        elif numpy and isinstance(coords, numpy.ndarray):
            shape = coords.shape  # Using numpy's shape.
            if len(shape) != 2:
                raise TypeError('Too many dimensions.')
            self._checkdim(shape[1])
            ncoords = shape[0]
            ndim = shape[1]
            numpy_coords = True
        else:
            raise TypeError('Invalid initialization input for LineStrings.')

        # Creating a coordinate sequence object because it is easier to
        # set the points using GEOSCoordSeq.__setitem__().
        cs = GEOSCoordSeq(capi.create_cs(ncoords, ndim), z=bool(ndim == 3))

        for i in range(ncoords):
            if numpy_coords:
                cs[i] = coords[i, :]
            elif isinstance(coords[i], Point):
                cs[i] = coords[i].tuple
            else:
                cs[i] = coords[i]

        # If SRID was passed in with the keyword arguments
        srid = kwargs.get('srid')

        # Calling the base geometry initialization with the returned pointer
        #  from the function.
        super(LineString, self).__init__(self._init_func(cs.ptr), srid=srid)

    def __iter__(self):
        "Allows iteration over this LineString."
        for i in range(len(self)):
            yield self[i]

    def __len__(self):
        "Returns the number of points in this LineString."
        return len(self._cs)

    def _get_single_external(self, index):
        return self._cs[index]

    _get_single_internal = _get_single_external

    def _set_list(self, length, items):
        ndim = self._cs.dims
        hasz = self._cs.hasz  # I don't understand why these are different

        # create a new coordinate sequence and populate accordingly
        cs = GEOSCoordSeq(capi.create_cs(length, ndim), z=hasz)
        for i, c in enumerate(items):
            cs[i] = c

        ptr = self._init_func(cs.ptr)
        if ptr:
            capi.destroy_geom(self.ptr)
            self.ptr = ptr
            self._post_init(self.srid)
        else:
            # can this happen?
            raise GEOSException('Geometry resulting from slice deletion was invalid.')

    def _set_single(self, index, value):
        self._checkindex(index)
        self._cs[index] = value

    def _checkdim(self, dim):
        if dim not in (2, 3):
            raise TypeError('Dimension mismatch.')

    # #### Sequence Properties ####
    @property
    def tuple(self):
        "Returns a tuple version of the geometry from the coordinate sequence."
        return self._cs.tuple
    coords = tuple

    def _listarr(self, func):
        """
        Internal routine that returns a sequence (list) corresponding with
        the given function.  Will return a numpy array if possible.
        """
        lst = [func(i) for i in range(len(self))]
        if numpy:
            return numpy.array(lst)  # ARRRR!
        else:
            return lst

    @property
    def array(self):
        "Returns a numpy array for the LineString."
        return self._listarr(self._cs.__getitem__)

    @property
    def merged(self):
        "Returns the line merge of this LineString."
        return self._topology(capi.geos_linemerge(self.ptr))

    @property
    def x(self):
        "Returns a list or numpy array of the X variable."
        return self._listarr(self._cs.getX)

    @property
    def y(self):
        "Returns a list or numpy array of the Y variable."
        return self._listarr(self._cs.getY)

    @property
    def z(self):
        "Returns a list or numpy array of the Z variable."
        if not self.hasz:
            return None
        else:
            return self._listarr(self._cs.getZ)


# LinearRings are LineStrings used within Polygons.
class LinearRing(LineString):
    _minlength = 4
    _init_func = capi.create_linearring
