#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This code is in the public domain, so as to serve as a template for
# real-world plugins.
# or, at the choice of the licensee,
# Copyright 2019 Even Rouault
# SPDX-License-Identifier: MIT

# Metadata parsed by GDAL C++ code at driver pre-loading, starting with '# gdal: '
# Required and with that exact syntax since it is parsed by non-Python
# aware code. So just literal values, no expressions, etc.
# gdal: DRIVER_NAME = "DUMMY"
# API version(s) supported. Must include 1 currently
# gdal: DRIVER_SUPPORTED_API_VERSION = [1]
# gdal: DRIVER_DCAP_VECTOR = "YES"
# gdal: DRIVER_DMD_LONGNAME = "my super plugin"
# gdal: DRIVER_DMD_OPENOPTIONLIST = "<OpenOptionList><Option name='GEOMFORMAT' type='string-select' description='Geometry format' default='WKT'><Value>WKT</Value><Value>WKB</Value><Value>WKB/bytearray</Value></Option></OpenOptionList>"

# Optional driver metadata items.
# # gdal: DRIVER_DMD_EXTENSIONS = "ext1 est2"
# # gdal: DRIVER_DMD_HELPTOPIC = "http://example.com/my_help.html"

try:
    # The gdal_python_driver module is defined by the GDAL library at runtime
    from gdal_python_driver import BaseDataset, BaseDriver, BaseLayer
except ImportError:
    # To be able to run in standalone mode
    class BaseDriver:
        pass

    class BaseDataset:
        pass

    class BaseLayer:
        pass


class Layer(BaseLayer):
    def __init__(self, options):
        self.options = options

        # Reserved attribute names. Either those or the corresponding method
        # must be defined
        self.name = "my_layer"  # Required, or name() method

        self.fid_name = "my_fid"  # Optional

        self.fields = [
            {"name": "boolField", "type": "Boolean"},
            {"name": "int16Field", "type": "Integer16"},
            {"name": "int32Field", "type": "Integer"},
            {"name": "int64Field", "type": "Integer64"},
            {"name": "realField", "type": "Real"},
            {"name": "floatField", "type": "Float"},
            {"name": "strField", "type": "String"},
            {"name": "strNullField", "type": "String"},
            {"name": "strUnsetField", "type": "String"},
            {"name": "binaryField", "type": "Binary"},
            {"name": "timeField", "type": "Time"},
            {"name": "dateField", "type": "Date"},
            {"name": "datetimeField", "type": "DateTime"},
        ]  # Required, or fields() method

        self.geometry_fields = [
            {
                "name": "geomField",
                "type": "Point",  # optional
                "srs": "EPSG:4326",  # optional
            }
        ]  # Required, or geometry_fields() method

        self.metadata = {"foo": "bar"}  # optional

        # uncomment if __iter__() honour self.attribute_filter
        # self.iterator_honour_attribute_filter = True

        # uncomment if __iter__() honour self.spatial_filter
        # self.iterator_honour_spatial_filter = True

        # uncomment if feature_count() honour self.attribute_filter
        # self.feature_count_honour_attribute_filter = True

        # uncomment if feature_count() honour self.spatial_filter
        # self.feature_count_honour_spatial_filter = True

        # End of reserved attribute names

        self.count = 5

    # Required, unless self.name attribute is defined
    # def name(self):
    #    return 'my_layer'

    # Optional. If not defined, fid name is 'fid'
    # def fid_name(self):
    #    return 'my_fid'

    # Required, unless self.geometry_fields attribute is defined
    # def geometry_fields(self):
    #    return [...]

    # Required, unless self.required attribute is defined
    # def fields(self):
    #    return [...]

    # Optional. Only to be usd if self.metadata field is not defined
    # def metadata(self, domain):
    #    if domain is None:
    #        return {'foo': 'bar'}
    #    return None

    # Optional. Called when self.attribute_filter is changed by GDAL
    # def attribute_filter_changed(self):
    #     # You may change self.iterator_honour_attribute_filter
    #     # or feature_count_honour_attribute_filter
    #     pass

    # Optional. Called when self.spatial_filter is changed by GDAL
    # def spatial_filter_changed(self):
    #     # You may change self.iterator_honour_spatial_filter
    #     # or feature_count_honour_spatial_filter
    #     pass

    # Optional
    def test_capability(self, cap):
        if cap == BaseLayer.FastGetExtent:
            return True
        if cap == BaseLayer.StringsAsUTF8:
            return True
        # if cap == BaseLayer.FastSpatialFilter:
        #    return False
        # if cap == BaseLayer.RandomRead:
        #    return False
        if cap == BaseLayer.FastFeatureCount:
            return self.attribute_filter is None and self.spatial_filter is None
        return False

    # Optional
    def extent(self, force_computation):
        return [2.1, 49, 3, 50]  # minx, miny, maxx, maxy

    # Optional.
    def feature_count(self, force_computation):
        # As we did not declare feature_count_honour_attribute_filter and
        # feature_count_honour_spatial_filter, the below case cannot happen
        # But this is to illustrate that you can callback the default implementation
        # if needed
        # if self.attribute_filter is not None or \
        #   self.spatial_filter is not None:
        #    return super(Layer, self).feature_count(force_computation)

        return self.count

    # Required. You do not need to handle the case of simultaneous iterators on
    # the same Layer object.
    def __iter__(self):
        for i in range(self.count):
            properties = {
                "boolField": True,
                "int16Field": 32767,
                "int32Field": i + 2,
                "int64Field": 1234567890123,
                "realField": 1.23,
                "floatField": 1.2,
                "strField": "foo",
                "strNullField": None,
                "binaryField": b"\x01\x00\x02",
                "timeField": "12:34:56.789",
                "dateField": "2017-04-26",
                "datetimeField": "2017-04-26T12:34:56.789Z",
            }
            POINT_WKB = b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x80H@"

            geom_format = self.options.get("GEOMFORMAT", "WKT")
            if geom_format == "WKT":
                geom = "POINT(2 49)"
            elif geom_format == "WKB":
                geom = POINT_WKB
            elif geom_format == "WKB/bytearray":
                geom = bytearray(POINT_WKB)
            else:
                raise ValueError(f"Unknown GEOMFORMAT: {geom_format}")

            yield {
                "type": "OGRFeature",
                "id": i + 1,
                "fields": properties,
                "geometry_fields": {"geomField": geom},
                "style": "SYMBOL(a:0)" if i % 2 == 0 else None,
            }

    # Optional
    # def feature_by_id(self, fid):
    #    return {}


class Dataset(BaseDataset):

    # Optional, but implementations will generally need it
    def __init__(self, filename, options):
        # If the layers member is set, layer_count() and layer() will not be used
        self.layers = [Layer(options)]
        self.metadata = {"foo": "bar"}
        self.options = options

    # Optional, called on native object destruction
    def close(self):
        pass

    # Optional. Only to be usd if self.metadata field is not defined
    # def metadata(self, domain):
    #    if domain is None:
    #        return {'foo': 'bar'}
    #    return None

    # Required, unless a layers attribute is set in __init__
    # def layer_count(self):
    #    return len(self.layers)

    # Required, unless a layers attribute is set in __init__
    # def layer(self, idx):
    #    return self.layers[idx]


# Required: class deriving from BaseDriver
class Driver(BaseDriver):

    # Optional. Called the first time the driver is loaded
    def __init__(self):
        pass

    # Required
    def identify(self, filename, first_bytes, open_flags, open_options={}):
        return filename == "DUMMY:"

    # Required
    def open(self, filename, first_bytes, open_flags, open_options={}):
        if not self.identify(filename, first_bytes, open_flags):
            return None
        return Dataset(filename, options=open_options)
