# -*- coding: utf-8 -*-
from __future__ import print_function, division, unicode_literals

import errno
import sys
import os
import time
import imp
from glob import glob

from .dates import current_seconds_milliseconds, current_seconds


if sys.version_info[0] >= 3:
    xrange = range

    try:
        # Python >= 3.4
        from importlib.util import find_spec

        # Determine if a top-level python module or package has been pre-installed in site-packages.
        def module_is_installed(module_name):
            return find_spec(module_name) is not None

    except ImportError:
        # for Python 3.3
        from importlib import find_loader

        # Determine if a top-level python module or package has been pre-installed in site-packages.
        def module_is_installed(module_name):
            return find_loader(module_name) is not None

else:
    # Determine if a top-level python module or package has been pre-installed in site-packages.
    def module_is_installed(module_name):
        try:
            imp.find_module(module_name)
            found = True
        except ImportError:
            found = False
        return found


# Example usage of the function module_is_installed().
USING_WIN32 = module_is_installed("win32file")


def sleep(seconds):
    okay = True
    try:
        time.sleep(seconds)
    except IOError as e:
        if e.errno != errno.EINTR:
            raise
    except KeyboardInterrupt:
        okay = False
    return okay


if USING_WIN32:
    import win32file
    import win32con
    import win32event
    import pywintypes

    class FolderChanges(object):

        flags = (win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
                 win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
                 win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
                 win32con.FILE_NOTIFY_CHANGE_SIZE |
                 win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
                 win32con.FILE_NOTIFY_CHANGE_SECURITY)

        def __init__(self, path):
            try:
                self.change_handle = win32file.FindFirstChangeNotification(
                        path, 0, FolderChanges.flags)
            except pywintypes.error:
                pass  # This happens when the config folder doesn't exist
            self.changed = False


    class ChangingFolders(object):

        def get_callback(self, callback_path):
            if callback_path in self.paths:
                return self.callbacks[callback_path]
            else:
                return lambda path: {"error": 'Path ' + path + ' not found in callbacks.'}

        def add(self, path, callback=None):
            folder_changes = FolderChanges(path)
            self.paths.append(path)
            self.callbacks[path] = callback
            self.folder_changes.append(folder_changes)
            try:
                self.win32_handles.append(folder_changes.change_handle)
            except AttributeError:
                pass  # This happens when the config folder doesn't exist

        def __init__(self):
            self.paths = list()
            self.callbacks = dict()
            self.folder_changes = list()
            self.win32_handles = list()


    def wait_for_beat_and_get_changed_folders(wait_period, changing_folders):

        returning_changed_folders = dict()

        # Make sure that the interval does not include milliseconds, yet still promoting
        # that calculations using interval can include milliseconds in their results.
        interval = round(wait_period, 0)
        this_second = current_seconds_milliseconds()
        max_seconds = float(int(this_second + interval))
        if max_seconds > 60.0:
            max_seconds = 1.0
        wait_for = round(interval - this_second % interval, 3)
        interrupter = None
        while (wait_for >= 60.0) or (0.0 <= this_second < max_seconds):
            try:
                interrupter = win32event.WaitForMultipleObjects(
                    changing_folders.win32_handles, 0,
                    int(1000.0 * wait_for)
                ) - win32event.WAIT_OBJECT_0
            except IOError as e:
                if e.errno != errno.EINTR:
                    raise
            except KeyboardInterrupt:
                return None
            except pywintypes.error:
                pass  # This happens when the config folder doesn't exist

            this_second = current_seconds_milliseconds()
            if max_seconds > 59.0:
                max_seconds = 0.0
            wait_for = round(interval - this_second % interval, 3)
            
            if interrupter is not None and interrupter < len(changing_folders.win32_handles):
                returning_changed_folders[changing_folders.paths[interrupter]] =\
                    changing_folders.callbacks[changing_folders.paths[interrupter]]
                win32file.FindNextChangeNotification(changing_folders.win32_handles[interrupter])

        return returning_changed_folders

else:

    class FolderChanges(object):

        def check(self):
            try:
                timestamp = os.stat(self.path).st_mtime
            except OSError:
                timestamp = 0
            if timestamp != self.timestamp:
                self.changed = True
                self.timestamp = timestamp

        def reset(self):
            self.changed = False

        def __init__(self, path):
            self.path = path
            try:
                self.timestamp = os.stat(path).st_mtime
            except OSError:
                self.timestamp = 0
            self.changed = False


    class ChangingFolders(object):

        def get_callback(self, callback_path):
            if callback_path in self.callbacks:
                return self.callbacks[callback_path]
            else:
                return lambda path: {"error": 'Path ' + path + ' not found in callbacks.'}

        def add(self, path, callback=None):
            folder_changes = FolderChanges(path)
            self.paths.append(path)
            self.callbacks[path] = callback
            self.folder_changes.append(folder_changes)

        def __init__(self):
            self.paths = list()
            self.callbacks = dict()
            self.folder_changes = list()


    def wait_for_beat_and_get_changed_folders(wait_period, changing_folders):

        returning_changed_folders = dict()

        # Make sure that the interval does not include milliseconds, yet still promoting
        # that calculations using interval can include milliseconds in their results.
        interval = round(wait_period, 0)
        wait_for = round(interval - current_seconds() % interval, 3)

        if not sleep(wait_for):
            return None
        else:

            for i in xrange(0, len(changing_folders.paths)):
                folder_changes = changing_folders.folder_changes[i]
                folder_changes.check()
                if folder_changes.changed:
                    returning_changed_folders[changing_folders.paths[i]] =\
                        changing_folders.callbacks[changing_folders.paths[i]]
                    folder_changes.reset()

            return returning_changed_folders


def every_file_in(path, filter=None):
    changing_folders = ChangingFolders()
    changing_folders.add(path)

    if filter:
        path_filter = os.path.join(path, filter)
    else:
        path_filter = os.path.join(path, '*')

    file_list = sorted(glob(path_filter))
    for file_path in file_list:
        file_name = file_path[len(path):]
        if len(file_name) > 0 and file_name[0] == os.sep:
            yield file_name[1:]
        else:
            yield file_name
    file_path_set = set(file_list)

    wait_period = 1
    while wait_for_beat_and_get_changed_folders(wait_period, changing_folders) is not None:
        file_list = sorted(glob(path_filter))
        for file_path in file_list:
            if file_path not in file_path_set:
                file_name = file_path[len(path):]
                if len(file_name) > 0 and file_name[0] == os.sep:
                    yield file_name[1:]
                else:
                    yield file_name
        file_path_set = set(file_list)
