#!/usr/bin/python

# Butteur, a preprocessor to write more easily beamer slides
# Copyright (C) 2012  Laurent Peuch cortex@worlddomination.be
#
# This program 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.
#
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

import os
import sys
import jinja2
from ply import lex, yacc

template = r"""
\documentclass{beamer}
\usepackage[utf8]{inputenc}
\usepackage{verbatim}
\usepackage{ulem}
\usepackage{fancyvrb}
\usepackage{color}
\usepackage{tikz}

%- if theme
\usetheme{<theme>}
%- endif

\begin{document}

%- if title
\title{<title>}
%- endif
%- if author
\author{<author>}
%- endif

%- if title or author
\maketitle{}
%- endif

%- for slide in slides
\begin{frame}[fragile]{<slide.title>}
\BLOCK{for line in slide.lines}
    <line>

\BLOCK{endfor}
\end{frame}
%- endfor

\end{document}
"""

tokens = (
    'TITLE',
    'AUTHOR',
    'THEME',
    'SLIDE',
    'LINE',
)

states = (
    ('content', 'exclusive'),
)

def t_TITLE(t):
    r'title .+'
    t.value = t.value[6:]
    return t

def t_AUTHOR(t):
    r'author .+'
    t.value = t.value[7:]
    return t

def t_THEME(t):
    r'theme .+'
    t.value = t.value[6:]
    return t

def t_SLIDE(t):
    r'slide ?.+'
    t.value = t.value[6:]
    return t

def t_content_LINE(t):
    r'\w.+'
    if not t.lexer.indentation:
        raise SyntaxError('invalid indentation')
    t.value = t.value
    return t

def t_ANY_newline(t):
    r'\n\ *'
    indentation = len(t.value[1:])
    handle_indentation(t, indentation)

def t_ANY_error(t):
    raise SyntaxError("Error on: '%s'" % t.value)

def handle_indentation(t, indentation):
    # beeeeeh ugly zillion of conditions!
    if not indentation and not t.lexer.indentation:
        return # nothing to do
    elif indentation and not t.lexer.indentation:
        t.lexer.indentation.append(indentation)
        t.lexer.begin('content')
    elif indentation and t.lexer.indentation:
        if indentation == t.lexer.indentation[-1]:
            pass # indentation doesn't change
        elif indentation > t.lexer.indentation[-1]:
            t.lexer.indentation.append(indentation)
        elif indentation < t.lexer.indentation[-1]:
            if indentation not in t.lexer.indentation:
                raise SyntaxError("Unexpected new indentation")
            while t.lexer.indentation[-1] != indentation:
                t.lexer.indentation.pop()
        else:
            raise Exception("Shouldn't happen!")
    elif not indentation and t.lexer.indentation:
        t.lexer.indentation = []
        t.lexer.begin('INITIAL')
    else:
        raise Exception("Shouldn't happen!")

t_ANY_ignore = "\t"

def p_result(p):
    'result : result expression'
    if isinstance(p[1], tuple):
        p[0] = [p[1], p[2]]
    else:
        p[0] = p[1] + [p[2]]

def p_result_expression(p):
    '''result : expression'''
    p[0] = [p[1]]

def p_expression_title(p):
    'expression : TITLE'
    p[0] = ('title', p[1])

def p_expression_author(p):
    'expression : AUTHOR'
    p[0] = ('author', p[1])

def p_expression_theme(p):
    'expression : THEME'
    p[0] = ('theme', p[1])

def p_expression_line(p):
    'expression : LINE'
    p[0] = ('line', p[1])

def p_expression_slide(p):
    'expression : slide'
    p[0] = p[1]

def p_side(p):
    'slide : SLIDE content'
    p[0] = ('slide', p[1], p[2])

def p_content(p):
    'content : LINE content'
    p[0] = [p[1]] + p[2]

def p_content_line(p):
    'content : LINE'
    p[0] = [p[1]]

def p_error(p):
    print "Parsing failed"
    #from ipdb import set_trace; set_trace()
    print p

def build_document(result):
    document = {
        "title": None,
        "author": None,
        "theme": None,
        "slides": [],
    }

    for i in result:
        if i[0] in ('title', 'author', 'theme'):
            #if document[i[0]] is not None:
                #print("Warning: duplicated declaration of %s" % i[0])
            document[i[0]] = i[1]
        elif i[0] == "slide":
            document["slides"].append({"title": i[1], "lines": i[2]})
        else:
            raise

    return document

def generate_latex(document):
    env = jinja2.Environment(
        block_start_string = '\BLOCK{',
        block_end_string = '}',
        variable_start_string = '<',
        variable_end_string = '>',
        comment_start_string = '\#{',
        comment_end_string = '}',
        line_statement_prefix = '%-',
        line_comment_prefix = '%#',
        trim_blocks = True,
        autoescape = False,
    )

    return env.from_string(template).render(**document)

if __name__ == "__main__":
    if len(sys.argv) == 1:
        print "Give me a filename!"
        sys.exit(1)

    lexer = lex.lex()
    lexer.indentation = []
    parser = yacc.yacc(debug=1)
    tree = parser.parse(open(sys.argv[1], "r").read().decode("Utf-8"))

    document = build_document(tree)
    result = generate_latex(document)
    open(sys.argv[1] + ".tex", "w").write(result.encode("Utf-8"))
    os.system("pdflatex %s" % sys.argv[1] + ".tex")
