import os
import markdown
import codecs
import difflib
import warnings
try:
    import nose
except ImportError as e:
    msg = e.args[0]
    msg = msg + ". The nose testing framework is required to run the Python-" \
        "Markdown tests. Run `pip install nose` to install the latest version."
    e.args = (msg,) + e.args[1:]
    raise
from .plugins import HtmlOutput, Markdown, MarkdownSyntaxError
try:
    import tidylib
except ImportError:
    tidylib = None
try:
    import yaml
except ImportError as e:
    msg = e.args[0]
    msg = msg + ". A YAML library is required to run the Python-Markdown " \
        "tests. Run `pip install pyyaml` to install the latest version."
    e.args = (msg,) + e.args[1:]
    raise

test_dir = os.path.abspath(os.path.dirname(__file__))


class YamlConfig():
    def __init__(self, defaults, filename):
        """ Set defaults and load config file if it exists. """
        self.DEFAULT_SECTION = 'DEFAULT'
        self._defaults = defaults
        self._config = {}
        if os.path.exists(filename):
            with codecs.open(filename, encoding="utf-8") as f:
                self._config = yaml.load(f)

    def get(self, section, option):
        """ Get config value for given section and option key. """
        if section in self._config and option in self._config[section]:
            return self._config[section][option]
        return self._defaults[option]

    def get_section(self, file):
        """ Get name of config section for given file. """
        filename = os.path.basename(file)
        if filename in self._config:
            return filename
        else:
            return self.DEFAULT_SECTION

    def get_args(self, file):
        """ Get args to pass to markdown from config for a given file. """
        args = {}
        section = self.get_section(file)
        if section in self._config:
            for key in self._config[section].keys():
                # Filter out args unique to testing framework
                if key not in self._defaults.keys():
                    args[key] = self.get(section, key)
        return args


def get_config(dir_name):
    """ Get config for given directory name. """
    defaults = {
        'normalize': False,
        'skip': False,
        'input_ext': '.txt',
        'output_ext': '.html'
    }
    config = YamlConfig(defaults, os.path.join(dir_name, 'test.cfg'))
    return config


def normalize(text):
    """ Normalize whitespace for a string of html using tidylib. """
    output, errors = tidylib.tidy_fragment(text, options={
        'drop_empty_paras': 0,
        'fix_backslash': 0,
        'fix_bad_comments': 0,
        'fix_uri': 0,
        'join_styles': 0,
        'lower_literals': 0,
        'merge_divs': 0,
        'output_xhtml': 1,
        'quote_ampersand': 0,
        'newline': 'LF'
    })
    return output


class CheckSyntax(object):
    def __init__(self, description=None):
        if description:
            self.description = 'TestSyntax: "%s"' % description

    def __call__(self, file, config):
        """ Compare expected output to actual output and report result. """
        cfg_section = config.get_section(file)
        if config.get(cfg_section, 'skip'):
            raise nose.plugins.skip.SkipTest('Test skipped per config.')
        input_file = file + config.get(cfg_section, 'input_ext')
        with codecs.open(input_file, encoding="utf-8") as f:
            input = f.read()
        output_file = file + config.get(cfg_section, 'output_ext')
        with codecs.open(output_file, encoding="utf-8") as f:
            # Normalize line endings
            # (on windows, git may have altered line endings).
            expected_output = f.read().replace("\r\n", "\n")
        output = markdown.markdown(input, **config.get_args(file))
        if tidylib and config.get(cfg_section, 'normalize'):
            # Normalize whitespace with tidylib before comparing.
            expected_output = normalize(expected_output)
            output = normalize(output)
        elif config.get(cfg_section, 'normalize'):
            # Tidylib is not available. Skip this test.
            raise nose.plugins.skip.SkipTest(
                'Test skipped. Tidylib not available on system.'
            )
        diff = [l for l in difflib.unified_diff(
            expected_output.splitlines(True),
            output.splitlines(True),
            output_file,
            'actual_output.html',
            n=3
        )]
        if diff:
            raise MarkdownSyntaxError(
                'Output from "%s" failed to match expected '
                'output.\n\n%s' % (input_file, ''.join(diff))
            )


def TestSyntax():
    for dir_name, sub_dirs, files in os.walk(test_dir):
        # Get dir specific config settings.
        config = get_config(dir_name)
        # Loop through files and generate tests.
        for file in files:
            root, ext = os.path.splitext(file)
            if ext == config.get(config.get_section(file), 'input_ext'):
                path = os.path.join(dir_name, root)
                check_syntax = CheckSyntax(
                    description=os.path.relpath(path, test_dir)
                )
                yield check_syntax, path, config


def generate(file, config):
    """ Write expected output file for given input. """
    cfg_section = config.get_section(file)
    if config.get(cfg_section, 'skip') or config.get(cfg_section, 'normalize'):
        print('Skipping:', file)
        return None
    input_file = file + config.get(cfg_section, 'input_ext')
    output_file = file + config.get(cfg_section, 'output_ext')
    if not os.path.isfile(output_file) or \
            os.path.getmtime(output_file) < os.path.getmtime(input_file):
        print('Generating:', file)
        markdown.markdownFromFile(input=input_file, output=output_file,
                                  encoding='utf-8', **config.get_args(file))
    else:
        print('Already up-to-date:', file)


def generate_all():
    """ Generate expected output for all outdated tests. """
    for dir_name, sub_dirs, files in os.walk(test_dir):
        # Get dir specific config settings.
        config = get_config(dir_name)
        # Loop through files and generate tests.
        for file in files:
            root, ext = os.path.splitext(file)
            if ext == config.get(config.get_section(file), 'input_ext'):
                generate(os.path.join(dir_name, root), config)


def run():
    # Warnings should cause tests to fail...
    warnings.simplefilter('error')
    # Except for the warnings that shouldn't
    warnings.filterwarnings('default', category=PendingDeprecationWarning)
    warnings.filterwarnings('default', category=DeprecationWarning, module='markdown')

    nose.main(addplugins=[HtmlOutput(), Markdown()])
