# Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0,
# as published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms, as
# designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an additional
# permission to link the program and your derivative works with the
# separately licensed software that they have included with MySQL.
# This program is distributed in the hope that it will be useful,  but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
# the GNU General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

import re
from grt import log_error
from functools import reduce

def get_exe_path(cmd):
    import os
    import mforms

    filepath = mforms.App.get().get_executable_path(cmd).encode("utf8")
    if len(filepath) != 0:
        return filepath;
    
    filepath, filename = os.path.split(cmd)
    
    def is_executable(filepath):
        return os.path.isfile(filepath) and os.access(filepath, os.X_OK)
    
    if filepath:
        if is_executable(filepath):
            return cmd
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            path = path.strip('"')
            exe = os.path.join(path, cmd)
            if is_executable(exe):
                return exe
    return None

def human_size(num):
    for x in ['bytes','KiB','MiB','GiB']:
        if num < 1024.0:
            return "%3.1f %s" % (num, x)
        num /= 1024.0
    return "%3.1f %s" % (num, 'TiB')


def format_duration(t, skip_seconds=False):
    s = int(t % 60)
    m = int((t / 60) % 60)
    h = int((t / 3600) % 24)
    d = int(t / (3600*24))

    res = []
    if d > 0:
        if d == 1:
            res.append("%i day" % d)
        else:
            res.append("%i days" % d)

    if skip_seconds:
        res.append("%i:%02i" % (h, m))
    else:
        res.append("%i:%02i:%02i" % (h, m, s))

    return " ".join(res)



def find_object_with_name(list, name):
    """Finds an object with the given name within a list of objects (such as grt.List).

    Returns the found object or None if there was no object with the given name in the collection.
    """
    for obj in list:
        if obj.name == name:
            return obj
    return None

def find_object_with_old_name(list, name):
    """Finds an object with the given oldName within a list of objects (such as grt.List).

    Returns the found object or None if there was no object with the given name in the collection.
    """
    for obj in list:
        if obj.oldName == name:
            return obj
    return None


def replace_string_parameters(template_string, params):
    if isinstance(params, dict):
        params = list( params.items() )
    return reduce( lambda partial_template_string, rep_tuple: partial_template_string.replace('%'+rep_tuple[0]+'%', str(rep_tuple[1])),
                                [ template_string ] + params
                  )

def parameters_from_dsn(dsn):
    chunks = dsn.split(';')
    params = ( (name, value) for name, value in 
                    ( chunk.split('=', 1) for chunk in chunks if '=' in chunk )
                    if not (value.startswith('%') and value.endswith('%'))
             )
    return dict(params)

def dsn_parameters_to_connection_parameters(dsn_params):
    param_mapping = { 'DRIVER'  : 'driver',
                      'SERVER'  : 'hostName',
                      'UID'     : 'userName',
                      'PWD'     : 'password',
                      'PORT'    : 'port',
                      'DATABASE': 'schema',
                      'DSN'     : 'dsn',
                    }
    return dict( (param_mapping.get(dsn_key.upper(), dsn_key), dsn_value) for dsn_key, dsn_value in list(dsn_params.items()) )
    
    
def check_grt_subtree_consistency(value):
    pass

def server_version_str2tuple(version_str):
    match = re.match(r'^(\d+\.\d+(\.\d+)*).*$', version_str.strip())
    if match:
        return tuple(int(x) for x in match.group(1).split('.'))
    return tuple()

def server_os_path(server_profile):
    """Returns an os.path module specific for the server OS."""
    if server_profile.target_is_windows:
        return __import__('ntpath')
    else:
        return __import__('posixpath')



class Version:
    def __init__(self, major, minor, release=0):
        self.majorNumber = major
        self.minorNumber = minor
        self.releaseNumber = release

    def __str__(self):
        if self.releaseNumber >= 0:
            return "%i.%i.%i" % (self.majorNumber, self.minorNumber, self.releaseNumber)
        else:
            return "%i.%i" % (self.majorNumber, self.minorNumber)

    @classmethod
    def fromgrt(cls, v):
        return Version(v.majorNumber, v.minorNumber, v.releaseNumber)

    @classmethod
    def fromstr(cls, s):
        match = re.match(r'^(\d+\.\d+(\.\d+)*).*$', s.strip())
        if match:
            v = tuple(int(x) for x in match.group(1).split('.'))
        else:
            v = []

        if len(v) == 1:
            return Version(v[0])
        elif len(v) == 2:
            return Version(v[0], v[1])
        elif len(v) == 3:
            return Version(v[0], v[1], v[2])
        else:
            raise ValueError("Invalid version string %s" % s)
    
    def compare(self, other):
        other_version = None
        if isinstance(other, Version):
            other_version = other
        elif isinstance(other, str):
            other_version = Version.fromstr(other)
        else:
            raise TypeError("Unexpected type")

        this_version_number = self.majorNumber * 10000 + self.minorNumber * 100 + max(0, self.releaseNumber)
        other_version_number = other_version.majorNumber * 10000 + other_version.minorNumber * 100 + max(0, other_version.releaseNumber)
        
        if this_version_number < other_version_number:
            return -1
        elif this_version_number > other_version_number:
            return 1
        return 0
    
    def __lt__(self, other):
        return self.compare(other) < 0

    def __eq__(self, other):
        return self.compare(other) == 0

    def __ne__(self, other):
        return self.compare(other) != 0

    def __gt__(self, other):
        return self.compare(other) > 0

    def __ge__(self, other):
        return self.compare(other) >= 0

    def __le__(self, other):
        return self.compare(other) <= 0

    def is_supported_mysql_version(self):
        if (self.majorNumber == 5 and self.minorNumber in (1, 5, 6, 7)) or (self.majorNumber == 8 and self.minorNumber == 0):
            return True
        return False

    def is_supported_mysql_version_at_least(self, major, minor = None, release=-1):
        assert type(major) == int or isinstance(major, Version)
        if isinstance(major, Version):
            v = major
            major = v.majorNumber
            minor = v.minorNumber
            release = v.releaseNumber

        # if the version required is older (<) than 5.6, then any server that matches is fine
        # if the version required is newer (>=) than 5.6, then we can only guarantee that known servers versions have some specific feature
        
        if (major == 5 and minor >= 6) or (major == 8 and minor == 0):
            return self.is_supported_mysql_version() and self >= Version(major, minor, release)
        else:
            return self > Version(major, minor, release)

import threading
import queue

class QueueFile:
    def __init__(self):
        self._cond = threading.Condition()
        self.data = ""
        self._write_done = False

    def write(self, data):
        self._cond.acquire()
        self.data += data
        self._cond.notify()
        self._cond.release()

    def close(self):
        self._cond.acquire()
        self._write_done = True
        self._cond.notify()
        self._cond.release()

    def peek(self, size):
        data = ""
        self._cond.acquire()
        while size > len(self.data) and not self._write_done:
            self._cond.wait()
        if self._write_done:
            data = self.data
        else:
            data = self.data[:size]
        self._cond.release()
        return data

    def read(self, size):
        data = ""
        self._cond.acquire()
        while size > len(self.data) and not self._write_done:
            self._cond.wait()
        if self._write_done:
            data = self.data
            self.data = ""
        else:
            data = self.data[:size]
            self.data = self.data[size:]
        self._cond.release()
        return data

    def readline(self):
        data = ""
        self._cond.acquire()
        find_start = 0
        # in case of VERY long lines (several MBs) this loop can slow down things a lot, so we take a longer break every once in a while to
        # give the thread more time to feed data
        while self.data.find('\n', find_start) < 0 and not self._write_done:
            find_start = len(self.data)
            self._cond.wait()
        pos = self.data.find('\n')
        if self._write_done:
            if pos >= 0:
                pos += 1
                data = self.data[:pos]
                self.data = self.data[pos:]
            else:
                data = self.data
                self.data = ""
        else:
            pos += 1
            data = self.data[:pos]
            self.data = self.data[pos:]
        self._cond.release()
        return data


import multiprocessing
class QueueFileMP:
    def __init__(self, pipe):
        self._queue = pipe
        self._write_done = False
        self._data = ""

    def write(self, data):
        self._queue.put(data)

    def close(self):
        self._queue.put(None)

    def _readup(self, maxloops=4):
        tmp = self._queue.get()
        if tmp is None:
            self._write_done = True
        else:
            l = [tmp]
            # flush the queue
            for i in range(maxloops):
                try:
                    tmp = self._queue.get()
                    if tmp is None:
                        self._write_done = True
                        break
                    l.append(tmp)
                except multiprocessing.Queue.Empty:
                    break
            self._data += "".join(l)

    def peek(self, size):
        while size > len(self._data) and not self._write_done:
            self._readup(0)
        if self._write_done:
            data = self._data
        else:
            data = self._data[:size]
        return data

    def read(self, size):
        data = ""
        while size > len(self._data) and not self._write_done:
            self._readup()
        if self._write_done:
            data = self._data
            self._data = ""
        else:
            data = self._data[:size]
            self._data = self._data[size:]
        return data

    def readline(self):
        data = ""
        find_start = 0
        # in case of VERY long lines (several MBs) this loop can slow down things a lot, so we take a longer break every once in a while to
        # give the thread more time to feed data
        while self._data.find('\n', find_start) < 0 and not self._write_done:
            find_start = len(self._data)
            self._readup()
        pos = self._data.find('\n')
        if self._write_done:
            if pos >= 0:
                pos += 1
                data = self._data[:pos]
                self._data = self._data[pos:]
            else:
                data = self._data
                self._data = ""
        else:
            pos += 1
            data = self._data[:pos]
            self._data = self._data[pos:]
        return data



class WorkerThreadHelper:
    """Worker thread that executes a task and sends messages/updates to a message handler 
        that keeps running from the main thread until it's done."""
    def __init__(self, worker_func, message_handler_func):
        self.worker = worker_func
        self.message_handler = message_handler_func
        self.thread = threading.Thread()
        self.queue = queue.Queue()
        self.thread.run = self._run
        self._timeout_handle = None
        self._running = False


    def start(self, update_interval = 0.5):
        self.thread.start()
        self._running = True
        import mforms
        self._timeout_handle = mforms.Utilities.add_timeout(update_interval, self._timeout)

    def add_message(self, message):
        self.queue.put(message)

    def _timeout(self):
        while True:
            try:
                message = self.queue.get_nowait()
            except queue.Empty:
                break
            self.message_handler(message)
        return self._running and self.queue.empty()

    def _run(self):
        try:
            self.worker(self.add_message)
        except Exception as e:
            import traceback
            log_error("WorkerThreadHelper", "An exception occurred in the worker thread:\n%s\n" % traceback.format_exc())
            self.add_message(e)
        self._running = False

