"""
GraphViz Tag
---------
This implements a Liquid-style graphviz tag for Pelican. You can use different
Graphviz programs like dot, neato, twopi etc. [1]


[1] http://www.graphviz.org/

Syntax
------
{% graphviz 
    <program> {
        <DOT code>
    }
%}

Examples
--------
{% graphviz 
    dot {
        digraph graphname {
            a -> b -> c;
            b -> d;
        }
    }
%}


{% graphviz 
    twopi {
        <code goes here>
    }
%}


{% graphviz 
    neato {
        <code goes here>
    }
%}

...


Output
------
<div class="graphviz" style="align: center;"><img src="data:image/png;base64,_BASE64_IMAGE DATA_/></div>

"""

import base64
import re
from .mdx_liquid_tags import LiquidTags

SYNTAX = '{% dot graphviz [program] [dot code] %}'
DOT_BLOCK_RE = re.compile(r'^\s*(?P<program>\w+)\s*\{\s*(?P<code>.*\})\s*\}$', re.MULTILINE | re.DOTALL)


def run_graphviz(program, code, options=[], format='png'):
    """ Runs graphviz programs and returns image data 
        
        Copied from https://github.com/tkf/ipython-hierarchymagic/blob/master/hierarchymagic.py
    """
    import os
    from subprocess import Popen, PIPE

    dot_args = [program] + options + ['-T', format]

    if os.name == 'nt':
        # Avoid opening shell window.
        # * https://github.com/tkf/ipython-hierarchymagic/issues/1
        # * http://stackoverflow.com/a/2935727/727827
        p = Popen(dot_args, stdout=PIPE, stdin=PIPE, stderr=PIPE, creationflags=0x08000000)
    else:
        p = Popen(dot_args, stdout=PIPE, stdin=PIPE, stderr=PIPE)
        wentwrong = False

    try:
        # Graphviz may close standard input when an error occurs,
        # resulting in a broken pipe on communicate()
        stdout, stderr = p.communicate(code.encode('utf-8'))
    except (OSError, IOError) as err:
        if err.errno != EPIPE:
            raise
        wentwrong = True
    except IOError as err:
        if err.errno != EINVAL:
            raise
        wentwrong = True

    if wentwrong:
    # in this case, read the standard output and standard error streams
    # directly, to get the error message(s)
        stdout, stderr = p.stdout.read(), p.stderr.read()
        p.wait()

    if p.returncode != 0:
        raise RuntimeError('dot exited with error:\n[stderr]\n{0}'.format(stderr.decode('utf-8')))

    return stdout


@LiquidTags.register('graphviz')
def graphviz_parser(preprocessor, tag, markup):
    """ Simple Graphviz parser """

    # Parse the markup string
    m = DOT_BLOCK_RE.search(markup)
    if m:
        # Get program and DOT code
        code = m.group('code')
        program = m.group('program').strip()

        # Run specified program with our markup
        output = run_graphviz(program, code)

        # Return Base64 encoded image
        return '<div class="graphviz" style="align: center;"><img src="data:image/png;base64,%s"></div>' % base64.b64encode(output)

    else:
        raise ValueError('Error processing input. '
                         'Expected syntax: {0}'.format(SYNTAX))

#----------------------------------------------------------------------
# This import allows image tag to be a Pelican plugin
from .liquid_tags import register


@@ -202,26 +202,25 @@
 %0A%7B%25 graphviz
- 
 %0A
+
     %3Cprogram
@@ -277,25 +277,24 @@
 %0A%7B%25 graphviz
- 
 %0A    dot %7B%0A 
@@ -393,25 +393,24 @@
 %0A%7B%25 graphviz
- 
 %0A    twopi %7B
@@ -457,17 +457,16 @@
 graphviz
- 
 %0A    nea
@@ -525,16 +525,16 @@
 %0A------%0A
-
 %3Cdiv cla
@@ -546,32 +546,37 @@
 raphviz%22 style=%22
+text-
 align: center;%22%3E
@@ -976,26 +976,17 @@
 age data
- %0A        
+%0A
 %0A       
@@ -2810,16 +2810,21 @@
  style=%22
+text-
 align: c

Fix alignment in {% graphviz %} output