#!python3
# -*- coding: utf-8 -*-
"""
CKAN DataStore field types and link with PostgreSQL types
"""
from abc import ABC, abstractmethod
from enum import IntEnum


class CkanFieldTypeABC(ABC):
    @staticmethod
    @abstractmethod
    def from_str(s):
        raise NotImplementedError()

class CkanFieldType(str, CkanFieldTypeABC):
    """
    Role previously managed by CkanFieldTypeEnum, but accepts any string
    """
    @staticmethod
    def from_str(s):
        return CkanFieldType(s)


class CkanFieldTypeEnum(IntEnum):  #, CkanFieldTypeABC):
    """
    Enumeration of types encountered during development + documentation
    """
    # CKAN web interface
    Text = 1
    Numeric = 2
    TimeStamp = 3
    # [CKAN documentation](https://docs.ckan.org/en/2.9/maintaining/datastore.html#field-types)
    json = 30
    date = 11  # This type is used to represent a calendar date (year, month, day). The oldest date that can be represented is 4713 BC and the latest date is 5874897 AD. The resolution is 1 day.
    time = 12  # This type is used to represent a time of day without time zone. The low value is 00:00:00 and the high value is 24:00:00 with a resolution of 1 microsecond.
    int = 13
    float = 14
    bool = 15
    # [Postgre documentation](https://www.postgresql.org/docs/9.1/datatype.html)
    timetz = 20  # time of day, including time zone
    timestamptz = 21  # date and time, including time zone
    # structured data
    jsonb = 31
    xml = 32
    # numeric types
    int2 = 101
    integer = int  # alias
    int4 = int  # alias
    int8 = 102
    bigint = int8  # alias
    int16 = 103
    int32 = 104
    int64 = 105
    float4 = 106  # real
    float8 = 107  # double
    money = 108
    # decimal [ (p, s) ] : exact numeric of selectable precision
    # other types
    bit = 200  # fixed-length bit string
    char = 201  # This type is used to represent fixed-length, space padded strings of the specified width. Storing character strings longer than the specified length will result in an error unless the excess characters are spaces, in which case the string will be truncated to the maximum length. If the string to be stored is shorter than the declared length, the value will be space padded.
    varbit = 202  # variable-length
    varchar = 203  # variable-length
    bytea = 204  # This type is used to represent binary strings (a “byte array”). A binary string is a sequence of octets (or bytes). Unlike character strings, binary strings allow storing octets of value zero and other non-printable octets (outside the range 32 to 126).
    # identifiers
    serial4 = 150  # This type is used to represent an auto incrementing four-byte integer. The range is 1 to 2147483647. This is similar to specifying an integer column that has default values to be assigned from a sequence generator. It also has a NOT NULL constraint applied to it.
    serial = serial4  # alias
    serial8 = 151
    bigserial = serial8  # alias
    uuid = 152  # This type is used to represent Universally Unique Identifiers (UUIDs). These identifiers are 128-bit values generated by an algorithm. A UUID is a sequence of lower-case hexadecimal digits in several groups separated by hyphens. Specifically, it is a group of 8 digits, followed by three groups of 4 digits, followed by a group of 12 digits. An example of a UUID in this standard form is a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11.
    oid = 153  #  numeric object identifier. It is currently implemented as an unsigned four-byte integer. Its use as a primary key in a user-created table is discouraged. OIDs are best used only for references to system tables.
    macaddr = 154
    inet = 155
    cidr = 156
    # geometries, on a plane
    point = 220
    path = 221
    polygon = 222
    box = 223
    circle = 224
    lseg = 225  # line segment
    line = 226
    # PostGRE extensions:
    geometry = 228  # PostGIS
    bson = 230

    def __str__(self):
        return self.name.lower()

    @staticmethod
    def from_str(s):
        s = s.lower().strip()
        if s == "text":
            return CkanFieldTypeEnum.Text
        elif s == "numeric":
            return CkanFieldTypeEnum.Numeric
        elif s == "timestamp":
            return CkanFieldTypeEnum.TimeStamp
        # elif s == "" or ((not isinstance(s, str)) and np.isnan(s)):
        #     return CkanFieldTypeEnum.Default
        elif hasattr(CkanFieldTypeEnum, s):  # other attributes are lower case
            return getattr(CkanFieldTypeEnum, s)
        else:
            raise ValueError(s)


# List to suppress warnings about field type override inconsistency
# synonyms or near match
field_type_synonyms = {
    "text": {"text"},
    "boolean": {"boolean", "bool"},
    "integer": {"integer", "int4", "int"},
    "bigint": {"bigint", "int8"},
    "double precision": {"double precision", "double", "float8", "float"},
    "timestamp": {"timestamp", "timestamp without time zone"}
}
# geometry: special treatment
# see also: data cleaner ckanapi_harvesters.harvesters.data_cleaner

