"""
Doctest.py, module definition of Doctest class.

This is a class designed for performing introspective tests
by means of special docstrings.
"""

# Copyright (C) 2015  Stefano Zaghi
#
# This file is part of FoBiS.py.
#
# FoBiS.py is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# FoBiS.py 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 for more details.
#
# You should have received a copy of the GNU General Public License
# along with FoBiS.py. If not, see <http://www.gnu.org/licenses/>.
# from builtins import *
import os
import re

__remodule__ = re.compile(
    r"^(\s*)"  # eventual initial white spaces
    + r"([Mm][Oo][Dd][Uu][Ll][Ee])"  # keyword "module"
    + r"(\s+)"  # 1 or more white spaces
    + r"(?P<name>[a-zA-Z][a-zA-Z0-9_]*)"  # module name
    + r"(?P<eol>\s*!.*|\s*)?$"
)  # eventual eol white space and or comment
__redoctest__ = re.compile(r"(?P<doctest>\!.```fortran(?P<test>.*?)\!.```.*?\!\=\>(?P<result>.*?)\<\<\<)", re.DOTALL)


class Doctest:
    """Doctest is an object that handles introspective doc-tests for Fortran procedures,
    like the Python doctest module does for Python codes.

    The syntax of a FoBiS.py doctest for Fortran procedures is:
    !$```fortran
    !$ ! valid fortran code for testing the procedure
    !$ ! the doctest must end with printing the expected result to the standard output
    !$```
    !=> !expected results <<<

    Note that the character "$" can be any character, likely it should be the "docmark" you
    select for producing the API documentation by means of the great FORD autodocumentation
    Fortran tool.
    """

    def __init__(self):
        """
        Parameters
        ----------
        """
        self.to_test = False
        self.tests = []

    def parse(self, source):
        """Parse source string for doctest occurences.

        Parameters
        ----------
        source : str
          source string with eventual doctests
        """
        modules = []
        for line in source.split("\n"):
            match = re.match(__remodule__, line)
            if match:
                modules.append(match)
        if len(modules) > 0:
            for match in re.finditer(__redoctest__, source):
                lines = match.group("test").split("\n")
                lines = [line.strip()[2:] for line in lines if not line.strip() == ""]
                module_name = None
                for module in modules:
                    if module.start() < match.start():
                        module_name = module.group("name")
                self.tests.append({"module": module_name, "source": lines, "result": match.group("result").strip()})

    def make_volatile_programs(self):
        """Make a 'volatile' program test for each of the parsed doctests."""
        if len(self.tests) > 0:
            self.to_test = True
            for test in self.tests:
                program_source = ["program volatile_doctest"]
                program_source.append("use " + test["module"])
                for line in test["source"]:
                    program_source.append(line)
                program_source.append("endprogram volatile_doctest")
                test["program_source"] = "\n".join(program_source)

    def save_volatile_programs(self, build_dir):
        """Save a 'volatile' program test for each of the parsed doctests.

        Parameters
        ----------
        build_dir : str
          build directory where volatile programs are placed

        Returns
        -------
        doctest_root_dir : str
          directory where doctests are saved
        """
        if len(self.tests) > 0:
            doctest_root_dir = os.path.join(build_dir, "doctests-src")
            for t, test in enumerate(self.tests):
                doctest_dir = os.path.join(doctest_root_dir, test["module"])
                if not os.path.exists(doctest_dir):
                    os.makedirs(doctest_dir)
                doctest_file = os.path.join(doctest_dir, test["module"] + "-doctest-" + str(t + 1) + ".f90")
                with open(doctest_file, "w") as doc:
                    doc.writelines(test["program_source"])
                doctest_file = os.path.join(doctest_dir, test["module"] + "-doctest-" + str(t + 1) + ".result")
                with open(doctest_file, "w") as doc:
                    doc.writelines(test["result"])
            return doctest_root_dir
