#
#    Copyright 2022 - Carlos A. <https://github.com/dealfonso>
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.
#
from datetime import datetime, timedelta
import re
import sys

__verbose = False

def setVerbose(set = 1):
    global __verbose
    __verbose = set

def getVerbose():
    return __verbose

__now = None

# A function to make that "now" is always the same in an execution. The "now" time will be generated by the first call to this function
# @return datetime the current time (now)
def NOW():
    global __now
    if __now is None:
        __now = datetime.now()
    return __now

def p_debug(*args):
    global __verbose
    if __verbose > 0:
        for x in args:
            msg = "[DEBUG - {}] {}".format(datetime.now(), x)
            sys.stderr.write(msg + "\n")
def p_debugv(*args):
    global __verbose
    if __verbose > 1:
        p_debug(*args)
def s_error(x):
    msg = "[ERROR - {}] {}".format(datetime.now(), x)
    return msg
def p_error(x):
    sys.stderr.write(s_error(x) + "\n")
def p_info(x):
    msg = "[INFO - {}] {}".format(datetime.now(), x)
    sys.stderr.write(msg + "\n")
def p_warning(x):
    msg = "[WARNING - {}] {}".format(datetime.now(), x)
    sys.stderr.write(msg + "\n")

def toBytes(e):
    # Function that converts an expression with a suffix (K, G, M, etc.) to bytes
    # e.g. "1.5G" -> 1500000000
    valid = re.compile(r'^[0-9]{1,}[BKMGTPEZY]{0,1}$')
    try:
        if not isinstance(e, str):
            return e
        if not valid.match(e):
            return None
        if e.endswith("B"):
            return float(e[:-1])
        if e.endswith("K"):
            return float(e[:-1]) * 1024
        if e.endswith("M"):
            return float(e[:-1]) * 1024 * 1024
        if e.endswith("G"):
            return float(e[:-1]) * 1024 * 1024 * 1024
        if e.endswith("T"):
            return float(e[:-1]) * 1024 * 1024 * 1024 * 1024
        return float(e)
    except ValueError:
        return None

def toSeconds(e):
    try:
        if not isinstance(e, str):
            return e
        if e.endswith("s"):
            return float(e[:-1])
        if e.endswith("m"):
            return float(e[:-1]) * 60
        if e.endswith("h"):
            return float(e[:-1]) * 60 * 60
        if e.endswith("d"):
            return float(e[:-1]) * 60 * 60 * 24
        if e.endswith("w"):
            return float(e[:-1]) * 60 * 60 * 24 * 7
        if e.endswith("M"):
            return float(e[:-1]) * 60 * 60 * 24 * 30
        if e.endswith("Y"):
            return float(e[:-1]) * 60 * 60 * 24 * 365
        return float(e)
    except ValueError:
        return None

def toDate(e, beginTime = None, endTime = None):
    p_debugv("convert to date: {}".format(e))
    if not isinstance(e, str):
        return e
    try:
        return datetime.strptime(e, "%Y-%m-%dT%H:%M:%S")
    except ValueError:
        pass

    try:
        return datetime.strptime(e, "%Y-%m-%dT%H:%M:%S.%fZ")
    except ValueError:
        pass

    try:
        return datetime.strptime(e, "%Y-%m-%dT%H:%M:%S,%fZ")
    except ValueError:
        pass

    if ["begin", "end", "now", "lastweek", "lastmonth", "lastyear"].count(e.lower()) > 0:
        e = "{}-0d".format(e.lower())

    valid = r"^((?P<base>now|begin|end|lastweek|lastmonth|lastyear)-|)(?P<count>[0-9]{1,})(?P<units>[smhdwMY]{0,1})$"
    m = re.match(valid, e)
    if m is None:
        return None

    basetime = NOW()
    base = m.group("base")
    if (base == "now") or (base is None) or (base == ""):
        basetime = NOW()
    elif base == "begin":
        if beginTime is None:
            p_error("could not get the beggining of the time")
            return None
        basetime = beginTime
    elif base == "end":
        if endTime is None:
            p_error("could not get the end of the time")
            return None
        basetime = endTime
    elif base == "lastweek":
        basetime = NOW() - timedelta(days=7)
    elif base == "lastmonth":
        basetime = NOW() - timedelta(days=30)
    elif base == "lastyear":
        basetime = NOW() - timedelta(days=365)
    else:
        return None

    count = int(m.group("count"))
    units = m.group("units")
    if units == "s" or units == "":
        delta = timedelta(seconds=count)
    elif units == "m":
        delta = timedelta(minutes=count)
    elif units == "h":
        delta = timedelta(hours=count)
    elif units == "d":
        delta = timedelta(days=count)
    elif units == "w":
        delta = timedelta(days=count * 7)
    elif units == "M":
        delta = timedelta(days=count * 30)
    elif units == "Y":
        delta = timedelta(days=count * 365)
    else:
        return None

    return basetime - delta

def csvdump(_data):

    def _process(_data):
        result = []
        for row in _data:
            r = []
            for d in row:
                try:
                    f = float(d)
                    r.append(str(f).replace(".", ","))
                except:
                    r.append(d)
            result.append(r)
        return result


    import io
    import csv
    output = io.StringIO()
    writer = csv.writer(output, delimiter=";")
    writer.writerows(_process(_data))
    return output.getvalue()

def p_debug_csv(data):
    p_debug("\n" + csvdump(data))

# Function that returns the values of a set of fields, using the notation "f1.f2.f3" where f1, f2 and f3 are objects
#
# Example:
#   given data = { a:1, b:2, c:{ d:3, e:4, f:{ g:5, h:6 } } }, and fields = [ "c.d", "a", "c.f.g" ] the return will be [ 3, 1, 5 ]
#
#   * if a field does not exist, the value will be None
#
# @param data is the original data from which to extract the values of the fields
# @param fields is a list of fields to extract
# @return the list of values of the requested fields
def get_fields(_data, fields):
    result = []
    for field in fields:
        if isinstance(field, str):
            data = _data
            for d in field.split('.'):
                if data is None:
                    # This is the case when a field is not found
                    break
                if d in data:
                    data = data[d]
                else:
                    data = None
                    break
            if isinstance(data, list):
                result = [ *result, *data ]
            else:
                result.append(data)
        else:
            result.append(field)
        
    # result = list(filter(lambda x: x is not None, result))
    return result

def strtobool (val, yesvals = ["yes", "y"], novals = ["no", "n"]):
    """Convert a string representation of truth to true (1) or false (0).
    Raises ValueError if 'val' is anything else."""
    val = val.lower()
    if val in yesvals:
        return 1
    elif val in novals:
        return 0
    else:
        raise ValueError("invalid truth value {}".format(val))

def user_yes_no_query(question, default='y'):
    choices = 'Y/n' if default.lower() in ('y', 'yes') else 'y/N'
    sys.stdout.write('{} ({})\n'.format(question, choices))
    while True:
        try:
            choice = input().lower()
            if choice == '':
                choice = default.lower()
            return strtobool(choice)
        except ValueError:
            sys.stdout.write('Please respond with \'y\' or \'n\'.\n')

if __name__ == '__main__':
    p_debug(toDate("now-10d"))
    p_debug(toDate("10d"))
    p_debug(toDate("last-10"))
