import os

from fiona.env import Env
from fiona._env import get_gdal_version_tuple


_GDAL_VERSION = get_gdal_version_tuple()

# Here is the list of available drivers as (name, modes) tuples. Currently,
# we only expose the defaults (excepting FileGDB). We also don't expose
# the CSV or GeoJSON drivers. Use Python's csv and json modules instead.
# Might still exclude a few more of these after making a pass through the
# entries for each at https://gdal.org/drivers/vector/index.html to screen
# out the multi-layer formats.

supported_drivers = dict([
    # OGR Vector Formats
    # Format Name 	Code 	Creation 	Georeferencing 	Compiled by default
    # Aeronav FAA files 	AeronavFAA 	No 	Yes 	Yes
    ("AeronavFAA", "r"),
    # ESRI ArcObjects 	ArcObjects 	No 	Yes 	No, needs ESRI ArcObjects
    # Arc/Info Binary Coverage 	AVCBin 	No 	Yes 	Yes
    # multi-layer
    #   ("AVCBin", "r"),
    # Arc/Info .E00 (ASCII) Coverage 	AVCE00 	No 	Yes 	Yes
    # multi-layer
    #    ("AVCE00", "r"),
    # Arc/Info Generate 	ARCGEN 	No 	No 	Yes
    ("ARCGEN", "r"),
    # Atlas BNA 	BNA 	Yes 	No 	Yes
    ("BNA", "rw"),
    # AutoCAD DWG 	DWG 	No 	No 	No
    # AutoCAD DXF 	DXF 	Yes 	No 	Yes
    ("DXF", "rw"),
    # Comma Separated Value (.csv) 	CSV 	Yes 	No 	Yes
    ("CSV", "raw"),
    # CouchDB / GeoCouch 	CouchDB 	Yes 	Yes 	No, needs libcurl
    # DODS/OPeNDAP 	DODS 	No 	Yes 	No, needs libdap
    # EDIGEO 	EDIGEO 	No 	Yes 	Yes
    # multi-layer? Hard to tell from the OGR docs
    #   ("EDIGEO", "r"),
    # ElasticSearch 	ElasticSearch 	Yes (write-only) 	- 	No, needs libcurl
    # ESRI FileGDB 	FileGDB 	Yes 	Yes 	No, needs FileGDB API library
    # multi-layer
    ("FileGDB", "raw"),
    ("OpenFileGDB", "raw"),
    # ESRI Personal GeoDatabase 	PGeo 	No 	Yes 	No, needs ODBC library
    # ESRI ArcSDE 	SDE 	No 	Yes 	No, needs ESRI SDE
    # ESRIJSON 	ESRIJSON 	No 	Yes 	Yes
    ("ESRIJSON", "r"),
    # ESRI Shapefile 	ESRI Shapefile 	Yes 	Yes 	Yes
    ("ESRI Shapefile", "raw"),
    # FMEObjects Gateway 	FMEObjects Gateway 	No 	Yes 	No, needs FME
    ("FlatGeobuf", "raw"),
    # GeoJSON 	GeoJSON 	Yes 	Yes 	Yes
    ("GeoJSON", "raw"),
    # GeoJSONSeq 	GeoJSON sequences 	Yes 	Yes 	Yes
    ("GeoJSONSeq", "raw"),
    # Géoconcept Export 	Geoconcept 	Yes 	Yes 	Yes
    # multi-layers
    #   ("Geoconcept", "raw"),
    # Geomedia .mdb 	Geomedia 	No 	No 	No, needs ODBC library
    # GeoPackage	GPKG	Yes	Yes	No, needs libsqlite3
    ("GPKG", "raw"),
    # GeoRSS 	GeoRSS 	Yes 	Yes 	Yes (read support needs libexpat)
    # Google Fusion Tables 	GFT 	Yes 	Yes 	No, needs libcurl
    # GML 	GML 	Yes 	Yes 	Yes (read support needs Xerces or libexpat)
    ("GML", "rw"),
    # GMT 	GMT 	Yes 	Yes 	Yes
    ("GMT", "rw"),
    # GMT renamed to OGR_GMT for GDAL 2.x
    ("OGR_GMT", "rw"),
    # GPSBabel 	GPSBabel 	Yes 	Yes 	Yes (needs GPSBabel and GPX driver)
    # GPX 	GPX 	Yes 	Yes 	Yes (read support needs libexpat)
    ("GPX", "rw"),
    # GRASS 	GRASS 	No 	Yes 	No, needs libgrass
    # GPSTrackMaker (.gtm, .gtz) 	GPSTrackMaker 	Yes 	Yes 	Yes
    # ("GPSTrackMaker", "rw"),
    # Hydrographic Transfer Format 	HTF 	No 	Yes 	Yes
    # TODO: Fiona is not ready for multi-layer formats: ("HTF", "r"),
    # Idrisi Vector (.VCT) 	Idrisi 	No 	Yes 	Yes
    ("Idrisi", "r"),
    # Informix DataBlade 	IDB 	Yes 	Yes 	No, needs Informix DataBlade
    # INTERLIS 	"Interlis 1" and "Interlis 2" 	Yes 	Yes 	No, needs Xerces (INTERLIS model reading needs ili2c.jar)
    # INGRES 	INGRES 	Yes 	No 	No, needs INGRESS
    # KML 	KML 	Yes 	Yes 	Yes (read support needs libexpat)
    # LIBKML 	LIBKML 	Yes 	Yes 	No, needs libkml
    # Mapinfo File 	MapInfo File 	Yes 	Yes 	Yes
    ("MapInfo File", "raw"),
    # Microstation DGN 	DGN 	Yes 	No 	Yes
    ("DGN", "raw"),
    # Access MDB (PGeo and Geomedia capable) 	MDB 	No 	Yes 	No, needs JDK/JRE
    # Memory 	Memory 	Yes 	Yes 	Yes
    # MySQL 	MySQL 	No 	Yes 	No, needs MySQL library
    # NAS - ALKIS 	NAS 	No 	Yes 	No, needs Xerces
    # Oracle Spatial 	OCI 	Yes 	Yes 	No, needs OCI library
    # ODBC 	ODBC 	No 	Yes 	No, needs ODBC library
    # MS SQL Spatial 	MSSQLSpatial 	Yes 	Yes 	No, needs ODBC library
    # Open Document Spreadsheet 	ODS 	Yes 	No 	No, needs libexpat
    # OGDI Vectors (VPF, VMAP, DCW) 	OGDI 	No 	Yes 	No, needs OGDI library
    # OpenAir 	OpenAir 	No 	Yes 	Yes
    # multi-layer
    #   ("OpenAir", "r"),
    # PCI Geomatics Database File 	PCIDSK 	No 	No 	Yes, using internal PCIDSK SDK (from GDAL 1.7.0)
    ("PCIDSK", "raw"),
    # PDS 	PDS 	No 	Yes 	Yes
    ("PDS", "r"),
    # PDS renamed to OGR_PDS for GDAL 2.x
    ("OGR_PDS", "r"),
    # PGDump 	PostgreSQL SQL dump 	Yes 	Yes 	Yes
    # PostgreSQL/PostGIS 	PostgreSQL/PostGIS 	Yes 	Yes 	No, needs PostgreSQL client library (libpq)
    # EPIInfo .REC 	REC 	No 	No 	Yes
    # S-57 (ENC) 	S57 	No 	Yes 	Yes
    # multi-layer
    ("S57", "r"),
    # SDTS 	SDTS 	No 	Yes 	Yes
    # multi-layer
    #   ("SDTS", "r"),
    # SEG-P1 / UKOOA P1/90 	SEGUKOOA 	No 	Yes 	Yes
    # multi-layers
    #   ("SEGUKOOA", "r"),
    # SEG-Y 	SEGY 	No 	No 	Yes
    ("SEGY", "r"),
    # Norwegian SOSI Standard 	SOSI 	No 	Yes 	No, needs FYBA library
    # SQLite/SpatiaLite 	SQLite 	Yes 	Yes 	No, needs libsqlite3 or libspatialite
    ("SQLite", "raw"),
    # SUA 	SUA 	No 	Yes 	Yes
    ("SUA", "r"),
    # SVG 	SVG 	No 	Yes 	No, needs libexpat
    # TopoJSON 	TopoJSON 	No 	Yes 	Yes
    ("TopoJSON", "r"),
    # UK .NTF 	UK. NTF 	No 	Yes 	Yes
    # multi-layer
    #   ("UK. NTF", "r"),
    # U.S. Census TIGER/Line 	TIGER 	No 	Yes 	Yes
    # multi-layer
    #   ("TIGER", "r"),
    # VFK data 	VFK 	No 	Yes 	Yes
    # multi-layer
    #   ("VFK", "r"),
    # VRT - Virtual Datasource 	VRT 	No 	Yes 	Yes
    # multi-layer
    #   ("VRT", "r"),
    # OGC WFS (Web Feature Service) 	WFS 	Yes 	Yes 	No, needs libcurl
    # MS Excel format 	XLS 	No 	No 	No, needs libfreexl
    # Office Open XML spreadsheet 	XLSX 	Yes 	No 	No, needs libexpat
    # X-Plane/Flighgear aeronautical data 	XPLANE 	No 	Yes 	Yes
    # multi-layer
    #   ("XPLANE", "r")
])

# Minimal gdal version for different modes
driver_mode_mingdal = {

    'r': {'GPKG': (1, 11, 0),
          'GeoJSONSeq': (2, 4, 0),
          'FlatGeobuf': (3, 1, 0)},

    'w': {'GPKG': (1, 11, 0),
          'PCIDSK': (2, 0, 0),
          'GeoJSONSeq': (2, 4, 0),
          'FlatGeobuf': (3, 1, 3),
          'OpenFileGDB': (3, 6, 0)},

    'a': {'GPKG': (1, 11, 0),
          'PCIDSK': (2, 0, 0),
          'GeoJSON': (2, 1, 0),
          'GeoJSONSeq': (3, 6, 0),
          'MapInfo File': (2, 0, 0),
          'FlatGeobuf': (3, 5, 1),
          'OpenFileGDB': (3, 6, 0)}
}


def _driver_supports_mode(driver, mode):
    """ Returns True if driver supports mode, False otherwise

        Note: this function is not part of Fiona's public API.
    """
    if driver not in supported_drivers:
        return False
    if mode not in supported_drivers[driver]:
        return False
    if driver in driver_mode_mingdal[mode]:
        if _GDAL_VERSION < driver_mode_mingdal[mode][driver]:
            return False
    return True


# Removes drivers in the supported_drivers dictionary that the
# machine's installation of OGR due to how it is compiled.
# OGR may not have optional libraries compiled or installed.
def _filter_supported_drivers():
    global supported_drivers

    with Env() as gdalenv:
        ogrdrv_names = gdalenv.drivers().keys()
        supported_drivers_copy = supported_drivers.copy()
        for drv in supported_drivers.keys():
            if drv not in ogrdrv_names:
                del supported_drivers_copy[drv]

    supported_drivers = supported_drivers_copy


_filter_supported_drivers()


def vector_driver_extensions():
    """
    Returns
    -------
    dict:
        Map of extensions to the driver.
    """
    from fiona.meta import extensions  # prevent circular import

    extension_to_driver = {}
    for drv, modes in supported_drivers.items():
        # update extensions based on driver suppport
        for extension in extensions(drv) or ():
            if "w" in modes:
                extension_to_driver[extension] = extension_to_driver.get(extension, drv)
    return extension_to_driver


def driver_from_extension(path):
    """
    Attempt to auto-detect driver based on the extension.

    Parameters
    ----------
    path: str or pathlike object
        The path to the dataset to write with.

    Returns
    -------
    str:
        The name of the driver for the extension.
    """
    try:
        # in case the path is a file handle
        # or a partsed path
        path = path.name
    except AttributeError:
        pass

    driver_extensions = vector_driver_extensions()

    try:
        return driver_extensions[os.path.splitext(path)[-1].lstrip(".").lower()]
    except KeyError:
        raise ValueError("Unable to detect driver. Please specify driver.")


# driver_converts_to_str contains field type, driver combinations that
# are silently converted to string None: field type is always converted
# to str (2, 0, 0): starting from gdal 2.0 field type is not converted
# to string
_driver_converts_to_str = {
    'time': {
        'CSV': None,
        'PCIDSK': None,
        'GeoJSON': (2, 0, 0),
        'GPKG': None,
        'GMT': None,
        'OGR_GMT': None
    },
    'datetime': {
        'CSV': None,
        'PCIDSK': None,
        'GeoJSON': (2, 0, 0),
        'GML': (3, 1, 0),
    },
    'date': {
        'CSV': None,
        'PCIDSK': None,
        'GeoJSON': (2, 0, 0),
        'GMT': None,
        'OGR_GMT': None,
        'GML': (3, 1, 0),
    }
}


def _driver_converts_field_type_silently_to_str(driver, field_type):
    """ Returns True if the driver converts the field_type silently to str, False otherwise

        Note: this function is not part of Fiona's public API.
    """
    if field_type in _driver_converts_to_str and driver in _driver_converts_to_str[field_type]:
        if _driver_converts_to_str[field_type][driver] is None:
            return True
        elif _GDAL_VERSION < _driver_converts_to_str[field_type][driver]:
            return True
    return False


# None: field type is never supported, (2, 0, 0) field type is supported starting with gdal 2.0
_driver_field_type_unsupported = {
    'time': {
        'ESRI Shapefile': None,
        'GPKG': (2, 0, 0),
        'GPX': None,
        'GPSTrackMaker': None,
        'GML': (3, 1, 0),
        'DGN': None,
        'BNA': None,
        'DXF': None,
        'PCIDSK': (2, 1, 0),
        'FileGDB': (3, 5, 0),
        'FlatGeobuf': None,
        'OpenFileGDB': None
    },
    'datetime': {
        'ESRI Shapefile': None,
        'GPKG': (2, 0, 0),
        'DGN': None,
        'BNA': None,
        'DXF': None,
        'PCIDSK': (2, 1, 0)
    },
    'date': {
        'GPX': None,
        'GPSTrackMaker': None,
        'DGN': None,
        'BNA': None,
        'DXF': None,
        'PCIDSK': (2, 1, 0),
        'FileGDB': (3, 5, 0),
        'FlatGeobuf': None,
        'OpenFileGDB': None
    }
}


def _driver_supports_field(driver, field_type):
    """ Returns True if the driver supports the field_type, False otherwise

        Note: this function is not part of Fiona's public API.
    """
    if field_type in _driver_field_type_unsupported and driver in _driver_field_type_unsupported[field_type]:
        if _driver_field_type_unsupported[field_type][driver] is None:
            return False
        elif _GDAL_VERSION < _driver_field_type_unsupported[field_type][driver]:
            return False

    return True


# None: field type never supports timezones, (2, 0, 0): field type supports timezones with GDAL 2.0.0
_drivers_not_supporting_timezones = {
    'datetime': {
        'MapInfo File': None,
        'GPKG': (3, 1, 0),
        'GPSTrackMaker': (3, 1, 1),
        'FileGDB': None,
        'SQLite': (2, 4, 0)
    },
    'time': {
        'MapInfo File': None,
        'GPKG': None,
        'GPSTrackMaker': None,
        'GeoJSON': None,
        'GeoJSONSeq': None,
        'GML': None,
        'CSV': None,
        'GMT': None,
        'OGR_GMT': None,
        'SQLite': None
    }
}


def _driver_supports_timezones(driver, field_type):
    """ Returns True if the driver supports timezones for field_type, False otherwise

        Note: this function is not part of Fiona's public API.
    """
    if field_type in _drivers_not_supporting_timezones and driver in _drivers_not_supporting_timezones[field_type]:
        if _drivers_not_supporting_timezones[field_type][driver] is None:
            return False
        elif _GDAL_VERSION < _drivers_not_supporting_timezones[field_type][driver]:
            return False
    return True


# None: driver never supports timezones, (2, 0, 0): driver supports timezones with GDAL 2.0.0
_drivers_not_supporting_milliseconds = {
    'GPSTrackMaker': None,
    'FileGDB': None,
    'OpenFileGDB': None
}


def _driver_supports_milliseconds(driver):
    """ Returns True if the driver supports milliseconds, False otherwise

        Note: this function is not part of Fiona's public API.
    """
    # GDAL 2.0 introduced support for milliseconds
    if _GDAL_VERSION.major < 2:
        return False

    if driver in _drivers_not_supporting_milliseconds:
        if _drivers_not_supporting_milliseconds[driver] is None:
            return False
        elif _drivers_not_supporting_milliseconds[driver] < _GDAL_VERSION:
            return False

    return True
