
# Python 2.7 Directives
from __future__ import absolute_import

# Python 2.7 Standard Library
import ast
import doctest
import json
from subprocess import Popen, PIPE
import sys

# Local Library
import pandoc
from pandoc.types import *

# This doctest extension require pandoc 1.16
from subprocess import Popen, PIPE
p = Popen(["pandoc", "-v"], stdout=PIPE)
if b"pandoc 1.16" not in p.communicate()[0]:
    raise RuntimeError("pandoc 1.16 not found")

# TODO: 
#    - optional round-trip check (global setting)
#    - "inject" pandoc.read errors in the doctest so that we can catch them.
#      Mmmm we probably can't do it, unless we do some fugly stack patching.
#      So what ? pandoc.read on any output JSON generated by pandoc is not 
#      supposed to fail ... Implement a "safe" read on top of it ?
#      Return a reference error message ? (maybe the full traceback?)
#      And make sure that after that, it is properly handled?

# ------------------------------------------------------------------------------

# Global Variables
check_round_trip = True

# New Doctest Directive: PANDOC 
PANDOC = doctest.register_optionflag("PANDOC")
doctest.PANDOC = PANDOC
doctest.__all__.append("PANDOC")
doctest.COMPARISON_FLAGS = doctest.COMPARISON_FLAGS | PANDOC

# Helpers
from subprocess import Popen, PIPE
import json
def to_json(txt):
    p = Popen(["pandoc", "-tjson"], 
              stdout=PIPE, stdin=PIPE, stderr=PIPE)
    json_string = p.communicate(input=txt.encode("utf-8"))[0].decode("utf-8")
    json_doc = json.loads(json_string)
    return json_doc

def linebreak(text, length=80):
    text = text.replace(u"\n", "")
    chunks = [text[i:i+length] for i in range(0, len(text), length)]
    return "\n".join(chunks) + "\n"

# Implement the pandoc output checker and monkey-patch doctest:
_doctest_OutputChecker = doctest.OutputChecker
class PandocOutputChecker(_doctest_OutputChecker):

    def round_trip_check(self, json_doc):
        json_doc_2 = None
        try:
            doc = pandoc.read(json_doc)
            json_doc_2 = pandoc.write(doc)
        except Exception as error:
            jsond_doc_2 = error
        if json_doc == json_doc_2 :
            return True
        else:
            return json_doc, json_doc_2

    def text_repr_to_docs(self, text_repr):
        text = ast.literal_eval(text_repr)
        if not isinstance(text, str):
            raise TypeError("{0!r} is not a string".format(text))
        json_doc = to_json(text)
        doc = pandoc.read(json_doc)
        return doc, json_doc

    def str_error(self, error):
        message = ""
        if len(error.args):
            message = error.args[0]
        return "Traceback (most recent call last)\n" + \
               "    ...\n"                           + \
               "{0}: {1}\n".format(type(error).__name__, message) 

    # TODO: need an eval-based checker for Python2/3 compatibility.
    def check_output(self, want, got, optionflags):
        if optionflags & PANDOC:
            try:
                want = eval(want.replace("\n", ""))
                doc, json_doc = self.text_repr_to_docs(got)
                got  = doc
                return got == want
            except Exception as error:
                got = self.str_error(error)
                return False
        super_check_output = _doctest_OutputChecker.check_output
        check = super_check_output(self, want, got, optionflags)
        if optionflags & PANDOC & check_round_trip:
            check = check and (self.round_trip_check(json_got) is True)
        return check

    def output_difference(self, example, got, optionflags):
        if optionflags & PANDOC:
            example.want = linebreak(example.want, 76)
            doc, json_doc = None, None
            try:
                doc, json_doc = self.text_repr_to_docs(got)
                got  = linebreak(repr(doc), 76)
                if check_round_trip:
                    check = self.round_trip_check(json_doc)
                    if check is not True:
                        json_doc, json_doc_2 = check
                        json_doc = linebreak(repr(json_doc), 72).strip()
                        if isinstance(json_doc_2, Exception):
                            json_doc_2 = self.str_error(json_doc_2)
                            json_doc_2 = "\n".join([4*" " + line for line in json_doc2.split("\n")])
                        else:
                            json_doc_2 = linebreak(repr(json_doc_2), 72).strip()
                        got = ("Traceback (most recent call last)\n" +\
                               "    ....\n"                          +\
                               "ValueError: failed JSON read+write round-trip:\n" +\
                               "    {0}\n" +\
                               "vs:\n    {1}\n").format(json_doc, json_doc_2)
            except Exception as error:
                got = self.str_error(error)
        super_output_difference = _doctest_OutputChecker.output_difference
        return super_output_difference(self, example, got, optionflags)

doctest.OutputChecker = PandocOutputChecker

