"""
Provides functionality for validation of the data-types specified
for odml
"""

import sys

self = sys.modules[__name__].__dict__

import datetime
import binascii
import hashlib
from enum import Enum

class DType(str, Enum):
    string = 'string'
    text = 'text'
    int = 'int'
    float = 'float'
    url = 'url'
    datetime = 'datetime'
    date = 'date'
    time = 'time'
    boolean = 'boolean'
    person = 'person'
    binary = 'binary'

    def __str__(self):
        return self.name

_dtype_map = {'str': 'string', 'bool': 'boolean'}


def infer_dtype(value):
    dtype = (type(value)).__name__
    if dtype in _dtype_map:
        dtype = _dtype_map[dtype]

    if valid_type(dtype):
        if dtype == 'string' and '\n' in value:
            dtype = 'text'
        return dtype
    else:
        return None


def valid_type(dtype):
    """
    checks if *dtype* is a valid type
    """
    dtype = dtype.lower()
    if dtype in _dtype_map:
        dtype = _dtype_map[dtype]
    if hasattr(DType, dtype):
        return True
    if dtype is None:
        return True

    if dtype.endswith("-tuple"):
        try:
            int(dtype[:-6])
            return True
        except ValueError:
            pass

    return False


# TODO also take encoding into account
def validate(string, dtype):
    """
    checks if:

     * *dtype* is a valid type
     * *string* is a valid expression of type *dtype*
    """
    try:
        if not valid_type(dtype):
            if dtype.endswith("-tuple"):
                count = int(dtype[:-6])
                #try to parse it
                tuple_get(string, count=count)
                return True
                #try to parse it
            self.get(dtype + "_get", str_get)(string)
        else:
            return False
    except RuntimeError:
        #any error, this type ain't valid
        return False


def get(string, dtype=None, encoding=None):
    """
    convert *string* to the corresponding *dtype*
    """
    if not dtype: return str_get(string)
    if dtype.endswith("-tuple"): # special case, as the count-number is included in the type-name
        return tuple_get(string)
    if dtype == "binary":
        return binary_get(string, encoding)
    return self.get(dtype + "_get", str_get)(string)


def set(value, dtype=None, encoding=None):
    """
    serialize a *value* of type *dtype* to a unicode string
    """
    if not dtype:
        return str_set(value)
    if dtype.endswith("-tuple"):
        return tuple_set(value)
    if dtype == "binary":
        return binary_set(value, encoding)
    if type(value) in (str, unicode):
        return str_set(value)
    return self.get(dtype + "_set", str_set)(value)


def int_get(string):
    if not string: return 0
    try:
        return int(string)
    except ValueError:
        # convert to float first and then cast to int
        return int(float(string))


def float_get(string):
    if not string: return 0.0
    return float(string)


def str_get(string):
    return unicode(string)


def str_set(value):
    try:
        return unicode(value)
    except Exception as ex:
        fail = ex
        raise fail


def time_get(string):
    if not string: return None
    if type(string) is datetime.time:
        return datetime.datetime.strptime(string.isoformat(), '%H:%M:%S').time()
    else:
        return datetime.datetime.strptime(string, '%H:%M:%S').time()