# -*- coding: utf-8 -*-
from __future__ import print_function

from os.path import join, exists
import grass.lib.gis as libgis
libgis.G_gisinit('')
import grass.lib.vector as libvect
import ctypes

#
# import pygrass modules
#
from grass.pygrass.vector.vector_type import VTYPE
from grass.pygrass.errors import GrassError, must_be_open
from grass.pygrass.gis import Location

from grass.pygrass.vector.geometry import GEOOBJ as _GEOOBJ
from grass.pygrass.vector.geometry import read_line, read_next_line
from grass.pygrass.vector.geometry import Area as _Area
from grass.pygrass.vector.abstract import Info
from grass.pygrass.vector.basic import Bbox, Cats, Ilist


_NUMOF = {"areas": libvect.Vect_get_num_areas,
          "dblinks": libvect.Vect_get_num_dblinks,
          "faces": libvect.Vect_get_num_faces,
          "holes": libvect.Vect_get_num_holes,
          "islands": libvect.Vect_get_num_islands,
          "kernels": libvect.Vect_get_num_kernels,
          "points": (libvect.Vect_get_num_primitives, libvect.GV_POINT),
          "lines": (libvect.Vect_get_num_primitives, libvect.GV_LINE),
          "centroids": (libvect.Vect_get_num_primitives, libvect.GV_CENTROID),
          "boundaries": (libvect.Vect_get_num_primitives, libvect.GV_BOUNDARY),
          "nodes": libvect.Vect_get_num_nodes,
          "updated_lines": libvect.Vect_get_num_updated_lines,
          "updated_nodes": libvect.Vect_get_num_updated_nodes,
          "volumes": libvect.Vect_get_num_volumes}

# For test purposes
test_vector_name = "vector_doctest_map"

#=============================================
# VECTOR
#=============================================

class Vector(Info):
    """Vector class is the grass vector format without topology

        >>> from grass.pygrass.vector import Vector
        >>> test_vect = Vector(test_vector_name)
        >>> test_vect.is_open()
        False
        >>> test_vect.mapset
        ''
        >>> test_vect.exist()
        True
        >>> test_vect.overwrite
        False

    """
    def __init__(self, name, mapset='', *args, **kwargs):
        # Set map name and mapset
        super(Vector, self).__init__(name, mapset, *args, **kwargs)
        self._topo_level = 1
        self._class_name = 'Vector'
        self.overwrite = False
        self._cats = []

    def __repr__(self):
        if self.exist():
            return "%s(%r, %r)" % (self._class_name, self.name, self.mapset)
        else:
            return "%s(%r)" % (self._class_name, self.name)

    def __iter__(self):
        """::

            >>> test_vect = Vector(test_vector_name)
            >>> test_vect.open(mode='r')
            >>> features = [feature for feature in test_vect]
            >>> features[:3]
            [Point(10.000000, 6.000000), Point(12.000000, 6.000000), Point(14.000000, 6.000000)]
            >>> test_vect.close()

        ..
        """
        #return (self.read(f_id) for f_id in xrange(self.num_of_features()))
        return self

    @must_be_open
    def __next__(self):
        """::

            >>> test_vect = Vector(test_vector_name)
            >>> test_vect.open(mode='r')
            >>> test_vect.next()
            Point(10.000000, 6.000000)
            >>> test_vect.next()
            Point(12.000000, 6.000000)
            >>> test_vect.close()

        ..
        """
        return read_next_line(self.c_mapinfo, self.table, self.writeable,
                              is2D=not self.is_3D())

    @must_be_open
    def next(self):
        return self.__next__()

    @must_be_open
    def rewind(self):
        """Rewind vector map to cause reads to start at beginning."""
        if libvect.Vect_rewind(self.c_mapinfo) == -1:
            raise GrassError("Vect_rewind raise an error.")

    @must_be_open
    def write(self, geo_obj, cat=None, attrs=None):
        """Write geometry features and attributes.

        :param geo_obj: a geometry grass object define in
                        grass.pygrass.vector.geometry
        :type geo_obj: geometry GRASS object
        :param attrs: a list with the values that will be insert in the
                      attribute table.
        :type attrs: list
        :param cat: The category of the geometry feature, otherwise the
                         c_cats attribute of the geometry object will be used.
        :type cat: integer

        Open a new vector map ::

            >>> new = VectorTopo('newvect')
            >>> new.exist()
            False

        define the new columns of the attribute table ::

            >>> cols = [(u'cat',       'INTEGER PRIMARY KEY'),
            ...         (u'name',      'TEXT')]

        open the vector map in write mode

            >>> new.open('w', tab_name='newvect', tab_cols=cols)

        import a geometry feature ::

            >>> from grass.pygrass.vector.geometry import Point

        create two points ::

            >>> point0 = Point(0, 0)
            >>> point1 = Point(1, 1)

        then write the two points on the map, with ::

            >>> new.write(point0, cat=1, attrs=('pub',))
            >>> new.write(point1, cat=2, attrs=('resturant',))

        commit the db changes ::

            >>> new.table.conn.commit()
            >>> new.table.execute().fetchall()
            [(1, u'pub'), (2, u'resturant')]

        close the vector map ::

            >>> new.close()
            >>> new.exist()
            True

        then play with the map ::

            >>> new.open(mode='r')
            >>> new.read(1)
            Point(0.000000, 0.000000)
            >>> new.read(2)
            Point(1.000000, 1.000000)
            >>> new.read(1).attrs['name']
            u'pub'
            >>> new.read(2).attrs['name']
            u'resturant'
            >>> new.close()
            >>> new.remove()

        """
        self.n_lines += 1
        if not isinstance(cat, int) and not isinstance(cat, str):
            # likely the case of using 7.0 API
            import warnings
            warnings.warn("Vector.write(geo_obj, attrs=(...)) is"
                          " depreciated, specify cat explicitly",
                          DeprecationWarning)
            # try to accommodate
            attrs = cat
            cat = None
        if attrs and cat is None:
            # TODO: this does not work as expected when there are
            # already features in the map when we opened it
            cat = (self._cats[-1] if self._cats else 0) + 1

        if cat is not None and cat not in self._cats:
            self._cats.append(cat)
            if self.table is not None and attrs is not None:
                attr = [cat, ]
                attr.extend(attrs)
                cur = self.table.conn.cursor()
                cur.execute(self.table.columns.insert_str, attr)
                cur.close()

        if cat is not None:
            cats = Cats(geo_obj.c_cats)
            cats.reset()
            cats.set(cat, self.layer)

        if geo_obj.gtype == _Area.gtype:
            result = self._write_area(geo_obj)
        result = libvect.Vect_write_line(self.c_mapinfo, geo_obj.gtype,
                                         geo_obj.c_points, geo_obj.c_cats)
        if result == -1:
            raise GrassError("Not able to write the vector feature.")
        if self._topo_level == 2:
            # return new feature id (on level 2)
            geo_obj.id = result
        else:
            # return offset into file where the feature starts (on level 1)
            geo_obj.offset = result

    @must_be_open
    def has_color_table(self):
        """Return if vector has color table associated in file system;
        Color table stored in the vector's attribute table well be not checked

        >>> test_vect = Vector(test_vector_name)
        >>> test_vect.open(mode='r')
        >>> test_vect.has_color_table()
        False

        >>> test_vect.close()
        >>> from grass.pygrass.utils import copy, remove
        >>> copy(test_vector_name,'mytest_vect','vect')
        >>> from grass.pygrass.modules.shortcuts import vector as v
        >>> v.colors(map='mytest_vect', color='population', column='value')
        Module('v.colors')
        >>> mytest_vect = Vector('mytest_vect')
        >>> mytest_vect.open(mode='r')
        >>> mytest_vect.has_color_table()
        True
        >>> mytest_vect.close()
        >>> remove('mytest_vect', 'vect')
        """
        loc = Location()
        path = join(loc.path(), self.mapset, 'vector', self.name, 'colr')
        return True if exists(path) else False


#=============================================
# VECTOR WITH TOPOLOGY
#=============================================

class VectorTopo(Vector):
    """Vector class with the support of the GRASS topology.

    Open a vector map using the *with statement*: ::

        >>> with VectorTopo(test_vector_name, mode='r') as test_vect:
        ...     for feature in test_vect[:7]:
        ...         print(feature.attrs['name'])
        ...
        point
        point
        point
        line
        line
        line
        >>> test_vect.is_open()
        False

    ..
    """
    def __init__(self, name, mapset='', *args, **kwargs):
        super(VectorTopo, self).__init__(name, mapset, *args, **kwargs)
        self._topo_level = 2
        self._class_name = 'VectorTopo'

    def __len__(self):
        return libvect.Vect_get_num_lines(self.c_mapinfo)

    def __getitem__(self, key):
        """::

            >>> test_vect = VectorTopo(test_vector_name)
            >>> test_vect.open(mode='r')
            >>> test_vect[:4]
            [Point(10.000000, 6.000000), Point(12.000000, 6.000000), Point(14.000000, 6.000000)]
            >>> test_vect.close()

        ..
        """
        if isinstance(key, slice):
            return [self.read(indx)
                    for indx in range(key.start if key.start else 1,
                                      key.stop if key.stop else len(self),
                                      key.step if key.step else 1)]
        elif isinstance(key, int):
            return self.read(key)
        else:
            raise ValueError("Invalid argument type: %r." % key)

    @must_be_open
    def num_primitive_of(self, primitive):
        """Return the number of primitive

        :param primitive: the name of primitive to query; the supported values are:

                            * *boundary*,
                            * *centroid*,
                            * *face*,
                            * *kernel*,
                            * *line*,
                            * *point*
                            * *area*
                            * *volume*

        :type primitive: str

        ::

            >>> test_vect = VectorTopo(test_vector_name)
            >>> test_vect.open(mode='r')
            >>> test_vect.num_primitive_of('point')
            3
            >>> test_vect.num_primitive_of('line')
            3
            >>> test_vect.num_primitive_of('centroid')
            4
            >>> test_vect.num_primitive_of('boundary')
            11
            >>> test_vect.close()

        ..
        """
        return libvect.Vect_get_num_primitives(self.c_mapinfo,
                                               VTYPE[primitive])

    @must_be_open
    def number_of(self, vtype):
        """Return the number of the chosen element type

        :param vtype: the name of type to query; the supported values are:
                      *areas*, *dblinks*, *faces*, *holes*, *islands*,
                      *kernels*, *points*, *lines*, *centroids*, *boundaries*,
                      *nodes*, *line_points*, *update_lines*, *update_nodes*,
                      *volumes*
        :type vtype: str

            >>> test_vect = VectorTopo(test_vector_name)
            >>> test_vect.open(mode='r')
            >>> test_vect.number_of("areas")
            4
            >>> test_vect.number_of("islands")
            2
            >>> test_vect.number_of("holes")
            0
            >>> test_vect.number_of("lines")
            3
            >>> test_vect.number_of("nodes")
            15
            >>> test_vect.number_of("pizza")
            ...                     # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
            Traceback (most recent call last):
                ...
            ValueError: vtype not supported, use one of: 'areas', ...
            >>> test_vect.close()


        ..
        """
        if vtype in _NUMOF.keys():
            if isinstance(_NUMOF[vtype], tuple):
                fn, ptype = _NUMOF[vtype]
                return fn(self.c_mapinfo, ptype)
            else:
                return _NUMOF[vtype](self.c_mapinfo)
        else:
            keys = "', '".join(sorted(_NUMOF.keys()))
            raise ValueError("vtype not supported, use one of: '%s'" % keys)

    @must_be_open
    def num_primitives(self):
        """Return dictionary with the number of all primitives
        """
        output = {}
        for prim in VTYPE.keys():
            output[prim] = self.num_primitive_of(prim)
        return output

    @must_be_open
    def viter(self, vtype, idonly=False):
        """Return an iterator of vector features

        :param vtype: the name of type to query; the supported values are:
                      *areas*, *dblinks*, *faces*, *holes*, *islands*,
                      *kernels*, *line_points*, *lines*, *nodes*, *points*,
                      *update_lines*, *update_nodes*, *volumes*
        :type vtype: str
        :param idonly: variable to return only the id of features instead of
                       full features
        :type idonly: bool

            >>> test_vect = VectorTopo(test_vector_name, mode='r')
            >>> test_vect.open(mode='r')
            >>> areas = [area for area in test_vect.viter('areas')]
            >>> areas[:3]
            [Area(1), Area(2), Area(3)]


        to sort the result in a efficient way, use: ::

            >>> from operator import methodcaller as method
            >>> areas.sort(key=method('area'), reverse=True)  # sort the list
            >>> for area in areas[:3]:
            ...     print(area, area.area())
            Area(1) 12.0
            Area(2) 8.0
            Area(4) 8.0

            >>> areas = [area for area in test_vect.viter('areas')]
            >>> for area in areas:
            ...     print(area.centroid().cat)
            3
            3
            3
            3

            >>> test_vect.close()
        """
        if vtype in _GEOOBJ.keys():
            if _GEOOBJ[vtype] is not None:
                ids = (indx for indx in range(1, self.number_of(vtype) + 1))
                if idonly:
                    return ids
                return (_GEOOBJ[vtype](v_id=indx, c_mapinfo=self.c_mapinfo,
                                       table=self.table,
                                       writeable=self.writeable)
                        for indx in ids)
        else:
            keys = "', '".join(sorted(_GEOOBJ.keys()))
            raise ValueError("vtype not supported, use one of: '%s'" % keys)

    @must_be_open
    def rewind(self):
        """Rewind vector map to cause reads to start at beginning. ::

            >>> test_vect = VectorTopo(test_vector_name)
            >>> test_vect.open(mode='r')
            >>> test_vect.next()
            Point(10.000000, 6.000000)
            >>> test_vect.next()
            Point(12.000000, 6.000000)
            >>> test_vect.next()
            Point(14.000000, 6.000000)
            >>> test_vect.rewind()
            >>> test_vect.next()
            Point(10.000000, 6.000000)
            >>> test_vect.close()

        ..
        """
        libvect.Vect_rewind(self.c_mapinfo)

    @must_be_open
    def cat(self, cat_id, vtype, layer=None, generator=False, geo=None):
        """Return the geometry features with category == cat_id.

        :param cat_id: the category number
        :type cat_id: int
        :param vtype: the type of geometry feature that we are looking for
        :type vtype: str
        :param layer: the layer number that will be used
        :type layer: int
        :param generator: if True return a generator otherwise it return a
                          list of features
        :type generator: bool
        """
        if geo is None and vtype not in _GEOOBJ:
            keys = "', '".join(sorted(_GEOOBJ.keys()))
            raise ValueError("vtype not supported, use one of: '%s'" % keys)
        Obj = _GEOOBJ[vtype] if geo is None else geo
        ilist = Ilist()
        libvect.Vect_cidx_find_all(self.c_mapinfo,
                                   layer if layer else self.layer,
                                   Obj.gtype, cat_id, ilist.c_ilist)
        is2D = not self.is_3D()
        if generator:
            return (read_line(feature_id=v_id, c_mapinfo=self.c_mapinfo,
                              table=self.table, writeable=self.writeable,
                              is2D=is2D)
                    for v_id in ilist)
        else:
            return [read_line(feature_id=v_id, c_mapinfo=self.c_mapinfo,
                              table=self.table, writeable=self.writeable,
                              is2D=is2D)
                    for v_id in ilist]

    @must_be_open
    def read(self, feature_id):
        """Return a geometry object given the feature id.

        :param int feature_id: the id of feature to obtain

        >>> test_vect = VectorTopo(test_vector_name)
        >>> test_vect.open(mode='r')
        >>> feature1 = test_vect.read(0)                     #doctest: +ELLIPSIS
        Traceback (most recent call last):
            ...
        ValueError: The index must be >0, 0 given.
        >>> feature1 = test_vect.read(5)
        >>> feature1
        Line([Point(12.000000, 4.000000), Point(12.000000, 2.000000), Point(12.000000, 0.000000)])
        >>> feature1.length()
        4.0
        >>> test_vect.read(-1)
        Centroid(7.500000, 3.500000)
        >>> len(test_vect)
        21
        >>> test_vect.read(21)
        Centroid(7.500000, 3.500000)
        >>> test_vect.read(22)                             #doctest: +ELLIPSIS
        Traceback (most recent call last):
          ...
        IndexError: Index out of range
        >>> test_vect.close()

        """
        return read_line(feature_id, self.c_mapinfo, self.table, self.writeable,
                         is2D=not self.is_3D())

    @must_be_open
    def is_empty(self):
        """Return if a vector map is empty or not
        """
        primitives = self.num_primitives()
        output = True
        for v in primitives.values():
            if v != 0:
                output = False
                break
        return output

    @must_be_open
    def rewrite(self, geo_obj, cat, attrs=None, **kargs):
        """Rewrite a geometry features

            >>> cols = [(u'cat',       'INTEGER PRIMARY KEY'),
            ...         (u'name',      'TEXT')]

        Generate a new vector map

            >>> test_vect = VectorTopo('newvect_2')
            >>> test_vect.open('w', tab_name='newvect_2', tab_cols=cols,
            ...                overwrite=True)

        import a geometry feature ::

            >>> from grass.pygrass.vector.geometry import Point

        create two points ::

            >>> point0 = Point(0, 0)
            >>> point1 = Point(1, 1)
            >>> point2 = Point(2, 2)

        then write the two points on the map, with ::

            >>> test_vect.write(point0, cat=1, attrs=('pub',))
            >>> test_vect.write(point1, cat=2, attrs=('resturant',))
            >>> test_vect.table.conn.commit()  # save changes in the DB
            >>> test_vect.table_to_dict()
            {1: [1, u'pub'], 2: [2, u'resturant']}
            >>> test_vect.close()

        Now rewrite one point of the vector map: ::

            >>> test_vect.open('rw')
            >>> test_vect.rewrite(point2, cat=1, attrs=('Irish Pub',))
            >>> test_vect.table.conn.commit()  # save changes in the DB
            >>> test_vect.close()

        Check the output:

            >>> test_vect.open('r')
            >>> test_vect[1] == point2
            True
            >>> test_vect[1].attrs['name'] == 'Irish Pub'
            True
            >>> test_vect.close()
            >>> test_vect.remove()
        """
        if self.table is not None and attrs:
            self.table.update(key=cat, values=attrs)
        elif self.table is None and attrs:
            print("Table for vector {name} does not exist, attributes not"
                  " loaded".format(name=self.name))
        libvect.Vect_cat_set(geo_obj.c_cats, self.layer, cat)
        result = libvect.Vect_rewrite_line(self.c_mapinfo,
                                           cat, geo_obj.gtype,
                                           geo_obj.c_points,
                                           geo_obj.c_cats)
        if result == -1:
            raise GrassError("Not able to write the vector feature.")

        # return offset into file where the feature starts
        geo_obj.offset = result

    @must_be_open
    def delete(self, feature_id):
        """Remove a feature by its id

        :param feature_id: the id of the feature
        :type feature_id: int
        """
        if libvect.Vect_rewrite_line(self.c_mapinfo, feature_id) == -1:
            raise GrassError("C function: Vect_rewrite_line.")

    @must_be_open
    def restore(self, geo_obj):
        if hasattr(geo_obj, 'offset'):
            if libvect.Vect_restore_line(self.c_mapinfo, geo_obj.offset,
                                         geo_obj.id) == -1:
                raise GrassError("C function: Vect_restore_line.")
        else:
            raise ValueError("The value have not an offset attribute.")

    @must_be_open
    def bbox(self):
        """Return the BBox of the vecor map
        """
        bbox = Bbox()
        if libvect.Vect_get_map_box(self.c_mapinfo, bbox.c_bbox) == 0:
            raise GrassError("I can not find the Bbox.")
        return bbox

    def close(self, build=True, release=True):
        """Close the VectorTopo map, if release is True, the memory
        occupied by spatial index is released"""
        if release:
            libvect.Vect_set_release_support(self.c_mapinfo)
        super(VectorTopo, self).close(build=build)

    @must_be_open
    def table_to_dict(self, where=None):
        """Return the attribute table as a dictionary with the category as keys

            The columns have the order of the self.table.columns.names() list.

            Examples

            >>> from grass.pygrass.vector import VectorTopo
            >>> from grass.pygrass.vector.basic import Bbox
            >>> test_vect = VectorTopo(test_vector_name)
            >>> test_vect.open('r')

            >>> test_vect.table_to_dict()
            {1: [1, u'point', 1.0], 2: [2, u'line', 2.0], 3: [3, u'centroid', 3.0]}

            >>> test_vect.table_to_dict(where="value > 2")
            {3: [3, u'centroid', 3.0]}

            >>> test_vect.table_to_dict(where="value > 0")
            {1: [1, u'point', 1.0], 2: [2, u'line', 2.0], 3: [3, u'centroid', 3.0]}

            >>> test_vect.table.filters.get_sql()
            u'SELECT cat,name,value FROM vector_doctest_map WHERE value > 0 ORDER BY cat;'

        """

        if self.table is not None:
            table_dict = {}
            # Get the category index
            cat_index = self.table.columns.names().index("cat")
            # Prepare a filter
            if where is not None:
                self.table.filters.where(where)

            self.table.filters.order_by("cat")

            self.table.filters.select(",".join(self.table.columns.names()))
            # Execute the query and fetch the result
            cur = self.table.execute()
            l = cur.fetchall()
            # Generate the dictionary
            for entry in l:
                table_dict[entry[cat_index]] = list(entry)

            return(table_dict)

        return None

    @must_be_open
    def features_to_wkb_list(self, bbox=None, feature_type="point", field=1):
        """Return all features of type point, line, boundary or centroid
           as a list of Well Known Binary representations (WKB)
           (id, cat, wkb) triplets located in a specific
           bounding box.

           :param bbox: The boundingbox to search for features,
                       if bbox=None the boundingbox of the whole
                       vector map layer is used

           :type bbox: grass.pygrass.vector.basic.Bbox

           :param feature_type: The type of feature that should be converted to
                                the Well Known Binary (WKB) format. Supported are:
                               'point'    -> libvect.GV_POINT     1
                               'line'     -> libvect.GV_LINE      2
                               'boundary' -> libvect.GV_BOUNDARY  3
                               'centroid' -> libvect.GV_CENTROID  4
           :type type: string

           :param field: The category field
           :type field: integer

           :return: A list of triplets, or None if nothing was found

           The well known binary are stored in byte arrays.

            Examples:

            >>> from grass.pygrass.vector import VectorTopo
            >>> from grass.pygrass.vector.basic import Bbox
            >>> test_vect = VectorTopo(test_vector_name)
            >>> test_vect.open('r')

            >>> bbox = Bbox(north=20, south=-1, east=20, west=-1)
            >>> result = test_vect.features_to_wkb_list(bbox=bbox,
            ...                                         feature_type="point")
            >>> len(result)
            3
            >>> for entry in result:
            ...     f_id, cat, wkb = entry
            ...     print((f_id, cat, len(wkb)))
            (1, 1, 21)
            (2, 1, 21)
            (3, 1, 21)

            >>> result = test_vect.features_to_wkb_list(bbox=None,
            ...                                         feature_type="line")
            >>> len(result)
            3
            >>> for entry in result:
            ...     f_id, cat, wkb = entry
            ...     print((f_id, cat, len(wkb)))
            (4, 2, 57)
            (5, 2, 57)
            (6, 2, 57)

            >>> result = test_vect.features_to_wkb_list(bbox=bbox,
            ...                                         feature_type="boundary")
            >>> len(result)
            11

            >>> result = test_vect.features_to_wkb_list(bbox=None,
            ...                                         feature_type="centroid")
            >>> len(result)
            4

            >>> for entry in result:
            ...     f_id, cat, wkb = entry
            ...     print((f_id, cat, len(wkb)))
            (19, 3, 21)
            (18, 3, 21)
            (20, 3, 21)
            (21, 3, 21)

            >>> result = test_vect.features_to_wkb_list(bbox=bbox,
            ...                                         feature_type="blub")
            Traceback (most recent call last):
            ...
            GrassError: Unsupported feature type <blub>, supported are <point,line,boundary,centroid>

            >>> test_vect.close()

        """

        supported = ['point', 'line', 'boundary', 'centroid']

        if feature_type.lower() not in supported:
            raise GrassError("Unsupported feature type <%s>, "\
                             "supported are <%s>"%(feature_type,
                                                   ",".join(supported)))

        if bbox is None:
            bbox = self.bbox()

        bboxlist = self.find_by_bbox.geos(bbox, type=feature_type.lower(),
                                          bboxlist_only = True)

        if bboxlist is not None and len(bboxlist) > 0:

            l = []
            line_p = libvect.line_pnts()
            line_c = libvect.line_cats()
            size = ctypes.c_size_t()
            cat = ctypes.c_int()
            error = ctypes.c_int()

            for f_id in bboxlist.ids:
                barray = libvect.Vect_read_line_to_wkb(self.c_mapinfo,
                                                       ctypes.byref(line_p),
                                                       ctypes.byref(line_c),
                                                       f_id,
                                                       ctypes.byref(size),
                                                       ctypes.byref(error))
                if not barray:
                    if error == -1:
                        raise GrassError(_("Unable to read line of feature %i"%(f_id)))
                    if error == -2:
                        print("Empty feature %i"%(f_id))
                    continue

                ok = libvect.Vect_cat_get(ctypes.byref(line_c), field,
                                          ctypes.byref(cat))
                if ok < 1:
                    pcat = None
                else:
                    pcat = cat.value

                l.append((f_id, pcat, ctypes.string_at(barray, size.value)))
                libgis.G_free(barray)

            return l
        return None

    @must_be_open
    def areas_to_wkb_list(self, bbox=None, field=1):
        """Return all features of type point, line, boundary or centroid
           as a list of Well Known Binary representations (WKB)
           (id, cat, wkb) triplets located in a specific
           bounding box.

           :param bbox: The boundingbox to search for features,
                       if bbox=None the boundingbox of the whole
                       vector map layer is used

           :type bbox: grass.pygrass.vector.basic.Bbox

           :param field: The centroid category field
           :type field: integer

           :return: A list of triplets, or None if nothing was found

           The well known binary are stored in byte arrays.

            Examples:

            >>> from grass.pygrass.vector import VectorTopo
            >>> from grass.pygrass.vector.basic import Bbox
            >>> test_vect = VectorTopo(test_vector_name)
            >>> test_vect.open('r')

            >>> bbox = Bbox(north=20, south=-1, east=20, west=-1)
            >>> result = test_vect.areas_to_wkb_list(bbox=bbox)
            >>> len(result)
            4
            >>> for entry in result:
            ...     a_id, cat, wkb = entry
            ...     print((a_id, cat, len(wkb)))
            (1, 3, 225)
            (2, 3, 141)
            (3, 3, 93)
            (4, 3, 141)

            >>> result = test_vect.areas_to_wkb_list()
            >>> len(result)
            4
            >>> for entry in result:
            ...     a_id, cat, wkb = entry
            ...     print((a_id, cat, len(wkb)))
            (1, 3, 225)
            (2, 3, 141)
            (3, 3, 93)
            (4, 3, 141)

            >>> test_vect.close()


        """
        if bbox is None:
            bbox = self.bbox()

        bboxlist = self.find_by_bbox.areas(bbox, bboxlist_only = True)

        if bboxlist is not None and len(bboxlist) > 0:

            l = []
            line_c = libvect.line_cats()
            size = ctypes.c_size_t()
            cat = ctypes.c_int()

            for a_id in bboxlist.ids:
                barray = libvect.Vect_read_area_to_wkb(self.c_mapinfo,
                                                       a_id,
                                                       ctypes.byref(size))
                if not barray:
                    raise GrassError(_("Unable to read area with id %i"%(a_id)))

                pcat = None
                c_ok = libvect.Vect_get_area_cats(self.c_mapinfo, a_id,
                                                  ctypes.byref(line_c))
                if c_ok == 0: # Centroid found

                    ok = libvect.Vect_cat_get(ctypes.byref(line_c), field,
                                              ctypes.byref(cat))
                    if ok > 0:
                        pcat = cat.value

                l.append((a_id, pcat, ctypes.string_at(barray, size.value)))
                libgis.G_free(barray)

            return l
        return None

if __name__ == "__main__":
    import doctest
    from grass.pygrass import utils
    utils.create_test_vector_map(test_vector_name)
    doctest.testmod()

    """Remove the generated vector map, if exist"""
    from grass.pygrass.utils import get_mapset_vector
    from grass.script.core import run_command
    mset = get_mapset_vector(test_vector_name, mapset='')
    if mset:
        run_command("g.remove", flags='f', type='vector', name=test_vector_name)
