# Copyright 2011-2013 by Peter Cock.  All rights reserved.
# This code is part of the Biopython distribution and governed by its
# license.  Please see the LICENSE file that should have been included
# as part of this package.

# This will apply to all the doctests too:
from __future__ import print_function
from Bio._py3k import _universal_read_mode

import unittest
import doctest
import os
import sys
import warnings
from Bio import BiopythonExperimentalWarning

warnings.simplefilter('ignore', BiopythonExperimentalWarning)

if sys.version_info[0] >= 3:
    from lib2to3 import refactor
    fixers = refactor.get_fixers_from_package("lib2to3.fixes")
    fixers.remove("lib2to3.fixes.fix_print")  # Already using print function
    rt = refactor.RefactoringTool(fixers)
    assert rt.refactor_docstring(">>> print(2+2)\n4\n", "example1") == \
                                 ">>> print(2+2)\n4\n"
    assert rt.refactor_docstring('>>> print("Two plus two is", 2+2)\n'
                                 'Two plus two is 4\n', "example2") == \
                                 '>>> print("Two plus two is", 2+2)\nTwo plus two is 4\n'


tutorial = os.path.join(os.path.dirname(sys.argv[0]), "../Doc/Tutorial.tex")
if not os.path.isfile(tutorial):
    from Bio import MissingExternalDependencyError
    raise MissingExternalDependencyError("Could not find ../Doc/Tutorial.tex file")
files = [tutorial]
for latex in os.listdir("../Doc/Tutorial/"):
    if latex.startswith("chapter_") and latex.endswith(".tex"):
        files.append(os.path.join(os.path.dirname(sys.argv[0]), "../Doc/Tutorial", latex))

tutorial_base = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "../Doc/"))
original_path = os.path.abspath(".")


def _extract(handle):
    line = handle.readline()
    if line != "\\begin{verbatim}\n":
        raise ValueError("Any '%doctest' or '%cont-doctest' line should be followed by '\\begin{verbatim}'")
    lines = []
    while True:
        line = handle.readline()
        if not line:
            if lines:
                print("".join(lines[:30]))
                raise ValueError("Didn't find end of test starting: %r", lines[0])
            else:
                raise ValueError("Didn't find end of test!")
        elif line.startswith("\end{verbatim}"):
            break
        else:
            lines.append(line)
    return lines


def extract_doctests(latex_filename):
    """Scans LaTeX file and pulls out marked doctests as strings.

    This is a generator, yielding one tuple per doctest.
    """
    base_name = os.path.splitext(os.path.basename(latex_filename))[0]
    handle = open(latex_filename, _universal_read_mode)
    line_number = 0
    in_test = False
    lines = []
    name = None
    while True:
        line = handle.readline()
        line_number += 1
        if not line:
            # End of file
            break
        elif line.startswith("%cont-doctest"):
            x = _extract(handle)
            lines.extend(x)
            line_number += len(x) + 2
        elif line.startswith("%doctest"):
            if lines:
                if not lines[0].startswith(">>> "):
                    raise ValueError("Should start '>>> ' not %r" % lines[0])
                yield name, "".join(lines), folder, deps
                lines = []
            deps = [x.strip() for x in line.split()[1:]]
            if deps:
                folder = deps[0]
                deps = deps[1:]
            else:
                folder = ""
            name = "test_%s_line_%05i" % (base_name, line_number)
            x = _extract(handle)
            lines.extend(x)
            line_number += len(x) + 2
    handle.close()
    if lines:
        if not lines[0].startswith(">>> "):
            raise ValueError("Should start '>>> ' not %r" % lines[0])
        yield name, "".join(lines), folder, deps
    # yield "dummy", ">>> 2 + 2\n5\n"


class TutorialDocTestHolder(object):
    """Python doctests extracted from the Biopython Tutorial."""
    pass


def check_deps(dependencies):
    missing = []
    for dep in dependencies:
        assert dep.startswith("lib:"), dep
        lib = dep[4:]
        try:
            tmp = __import__(lib)
            del tmp
        except ImportError:
            missing.append(lib)
    return missing

# Create dummy methods on the object purely to hold doctests
missing_deps = set()
for latex in files:
    # print("Extracting doctests from %s" % latex)
    for name, example, folder, deps in extract_doctests(latex):
        missing = check_deps(deps)
        if missing:
            missing_deps.update(missing)
            continue

        if sys.version_info[0] >= 3:
            example = ">>> from __future__ import print_function\n" + example
            example = rt.refactor_docstring(example, name)

        def funct(n, d, f):
            global tutorial_base
            method = lambda x: None
            if f:
                p = os.path.join(tutorial_base, f)
                method.__doc__ = "%s\n\n>>> import os\n>>> os.chdir(%r)\n%s\n" \
                    % (n, p, d)
            else:
                method.__doc__ = "%s\n\n%s\n" % (n, d)
            method._folder = f
            return method

        setattr(TutorialDocTestHolder,
                "doctest_%s" % name.replace(" ", "_"),
                funct(name, example, folder))
        del funct


# This is a TestCase class so it is found by run_tests.py
class TutorialTestCase(unittest.TestCase):
    """Python doctests extracted from the Biopython Tutorial."""
    # Single method to be invoked by run_tests.py
    def test_doctests(self):
        """Run tutorial doctests."""
        runner = doctest.DocTestRunner()
        failures = []
        for test in doctest.DocTestFinder().find(TutorialDocTestHolder):
            failed, success = runner.run(test)
            if failed:
                name = test.name
                assert name.startswith("TutorialDocTestHolder.doctest_")
                failures.append(name[30:])
                # raise ValueError("Tutorial doctest %s failed" % test.name[30:])
        if failures:
            raise ValueError("%i Tutorial doctests failed: %s" %
                             (len(failures), ", ".join(failures)))

    def tearDown(self):
        global original_path
        os.chdir(original_path)


# This is to run the doctests if the script is called directly:
if __name__ == "__main__":
    if missing_deps:
        print("Skipping tests needing the following:")
        for dep in sorted(missing_deps):
            print(" - %s" % dep)
    print("Running Tutorial doctests...")
    import doctest
    tests = doctest.testmod()
    if tests[0]:
        # Note on Python 2.5+ can use tests.failed rather than tests[0]
        raise RuntimeError("%i/%i tests failed" % tests)
    print("Tests done")
