# coding=utf-8

__author__ = 'Ilya.Kazakevich'
import fnmatch
import os
import re
import sys


class FileChangesTracker(object):
    """
     On the instantiation the class records the timestampts of files stored in the folder.
     #get_changed_files() return the list of files that have a timestamp different from the one they had during the class instantiation


    """

    def __init__(self, folder, patterns="*"):
        self.old_files = self._get_changes_from(folder, patterns)
        self.folder = folder
        self.patterns = patterns

    def get_changed_files(self):
        assert self.folder, "No changes recorded"
        new_files = self._get_changes_from(self.folder, patterns=self.patterns)
        return filter(lambda f: f not in self.old_files or self.old_files[f] != new_files[f], new_files.keys())

    @staticmethod
    def _get_changes_from(folder, patterns):
        result = {}
        for tmp_folder, sub_dirs, files in os.walk(folder):
            sub_dirs[:] = [s for s in sub_dirs if not s.startswith(".")]
            if any(fnmatch.fnmatch(os.path.basename(tmp_folder), p) for p in patterns):
                for file in map(lambda f: os.path.join(tmp_folder, f), files):
                    try:
                        result.update({file: os.path.getmtime(file)})
                    except OSError:  # on Windows long path may lead to it: PY-23386
                        message = "PyCharm can't check if the following file been updated: {0}\n".format(str(file))
                        sys.stderr.write(message)
        return result


def jb_escape_output(output):
    """
    Escapes text in manner that is supported on Java side with CommandLineConsoleApi.kt#jbFilter
    Check jbFilter doc for more info

    :param output: raw text
    :return: escaped text
    """
    return "##[jetbrains{0}".format(output)


class OptionDescription(object):
    """
    Wrapper for argparse/optparse option (see VersionAgnosticUtils#get_options)
    """

    def __init__(self, name, description, action=None):
        self.name = name
        self.description = description
        self.action = action


class VersionAgnosticUtils(object):
    """
    "six" emulator: this class fabrics appropriate tool to use regardless python version.
    Use it to write code that works both on py2 and py3. # TODO: Use Six instead
    """

    @staticmethod
    def is_py3k():
        return sys.version_info >= (3, 0)

    @staticmethod
    def __new__(cls, *more):
        """
        Fabrics Py2 or Py3 instance based on py version
        """
        real_class = _Py3KUtils if VersionAgnosticUtils.is_py3k() else _Py2Utils
        return super(cls, real_class).__new__(real_class, *more)

    def to_unicode(self, obj):
        """

        :param obj: string to convert to unicode
        :return: unicode string
        """

        raise NotImplementedError()

    def get_options(self, *args):
        """
        Hides agrparse/optparse difference
        
        :param args:  OptionDescription
        :return: options namespace
        """
        raise NotImplementedError()


class _Py2Utils(VersionAgnosticUtils):
    """
    Util for Py2
    """

    def to_unicode(self, obj):
        if isinstance(obj, unicode):
            return obj
        try:
            return unicode(obj)  # Obj may have its own __unicode__
        except (UnicodeDecodeError, AttributeError):
            return unicode(str(obj).decode("utf-8"))  # or it may have __str__

    def get_options(self, *args):
        import optparse

        parser = optparse.OptionParser()
        for option in args:
            assert isinstance(option, OptionDescription)
            parser.add_option(option.name, help=option.description, action=option.action)
        (options, _) = parser.parse_args()
        return options


class _Py3KUtils(VersionAgnosticUtils):
    """
    Util for Py3
    """

    def to_unicode(self, obj):
        return str(obj)

    def get_options(self, *args):
        import argparse

        parser = argparse.ArgumentParser()
        for option in args:
            assert isinstance(option, OptionDescription)
            parser.add_argument(option.name, help=option.description, action=option.action)
        return parser.parse_args()


def _parse_parametrized(part):
    """

    Support nose generators / pytest parameters and other functions that provides names like foo(1,2)
    Until https://github.com/JetBrains/teamcity-messages/issues/121, all such tests are provided
    with parentheses.

    Tests with docstring are reported in similar way but they have space before parenthesis and should be ignored
    by this function

    """
    match = re.match("^([^\\s)(]+)(\\(.*\\))$", part)
    if not match:
        return [part]
    else:
        return [match.group(1), match.group(2)]


def test_to_list(test_name):
    """
    Splits test name to parts to use it as list.
    It most cases dot is used, but runner may provide custom function
    """
    parts = test_name.split(".")
    result = []
    for part in parts:
        result += _parse_parametrized(part)
    return result