#!/usr/bin/env python
"""Script to convert templates from Mako to Jinja2."""

import io
import glob
import sys
import os
import re
import json
import shutil
import tempfile

import jinja2

dumb_replacements = [
    ["{% if any(post.has_math for post in posts) %}", '{% if posts|selectattr("has_math")|list %}'],
    ["json.dumps(title)", "title|tojson"],
    ["{{ parent.extra_head() }}", "{{ super() }}"],
    ["{{ parent.content() }}", "{{ super() }}"],
    ["prefix='\\", "prefix='"],
    ["og: http://ogp.me/ns# \\", "og: http://ogp.me/ns#"],
    ["article: http://ogp.me/ns/article# \\", "article: http://ogp.me/ns/article#"],
    ["fb: http://ogp.me/ns/fb# \\", "fb: http://ogp.me/ns/fb#"],
    ['dir="rtl" \\', 'dir="rtl"'],
    ['sorted(translations)', 'translations|sort'],
    ['abs(i - current_page)', '(i - current_page)|abs'],
    ['loop.index', 'loop.index0'],
    ['is None', 'is none'],
    ['is not None', 'is not none'],
    [
        "{% import 'comments_helper_{{ context['comment_system'] }}.tmpl' as comments_helper_impl with context %}",
        "{% import 'comments_helper_%s.tmpl' % comment_system as comments_helper_impl with context %}"
    ]
]

dumber_replacements = [
    ['<%! import json %>\n\n', ''],
    ["<html\n\\", "<html\n"],
    ["\n'\\\n", "\n'\n"],
    ["{% endif %}\n\\", "{% endif %}\n"]
]


def jinjify(in_theme, out_theme):
    """Convert in_theme into a jinja version and put it in out_theme"""

    in_templates_path = os.path.join(in_theme, "templates")
    out_templates_path = os.path.join(out_theme, "templates")
    try:
        os.makedirs(out_templates_path)
    except Exception:
        pass
    lookup = jinja2.Environment()
    lookup.filters['tojson'] = json.dumps
    lookup.loader = jinja2.FileSystemLoader([out_templates_path], encoding='utf-8-sig')
    for template in glob.glob(os.path.join(in_templates_path, "*.tmpl")):
        out_template = os.path.join(out_templates_path, os.path.basename(template))
        with io.open(template, "r", encoding="utf-8") as inf:
            data = mako2jinja(inf)

        lines = []
        for line in data.splitlines():
            for repl in dumb_replacements:
                line = line.replace(*repl)
            lines.append(line)
        data = '\n'.join(lines)

        for repl in dumber_replacements:
            data = data.replace(*repl)

        with io.open(out_template, "w+", encoding="utf-8", newline="\n") as outf:
            outf.write(data + '\n')

        # Syntax check output
        source, filename = lookup.loader.get_source(lookup, os.path.basename(template))[:2]
        try:
            lookup.parse(source)
        except Exception as e:
            error("Syntax error in {0}:{1}".format(out_template, e.lineno))

    parent = os.path.basename(in_theme.rstrip('/'))
    child = os.path.basename(out_theme.rstrip('/'))
    mappings = {
        'base-jinja': 'base',
        'bootstrap4-jinja': 'base-jinja',
    }

    if child in mappings:
        parent = mappings[child]

    # Copy assets in bootstrap/bootstrap4
    if child == 'bootstrap4-jinja':
        assets_dir = os.path.join(out_theme, "assets")
        if os.path.exists(assets_dir):
            shutil.rmtree(assets_dir)
        shutil.copytree(
            os.path.join(in_theme, "assets"), os.path.join(out_theme, "assets"),
            symlinks=True)

    # Copy bundles
    # shutil.copy(os.path.join(in_theme, "bundles"), os.path.join(out_theme, "bundles"))

    # Copy README
    if os.path.isfile(os.path.join(in_theme, "README.md")):
        shutil.copy(os.path.join(in_theme, "README.md"), os.path.join(out_theme, "README.md"))


def error(msg):
    print("\033[1;31mERROR: {0}\033[0m".format(msg))


def mako2jinja(input_file):

    output = ''

    # TODO: OMG, this code is so horrible. Look at it; just look at it:

    macro_start = re.compile(r'(.*)<%\s*def name="([^"]*?)"\s*>(.*)', re.IGNORECASE)
    macro_end = re.compile(r'(.*)</%def>(.*)', re.IGNORECASE)

    if_start = re.compile(r'(.*)% *if (.*):(.*)', re.IGNORECASE)
    if_else = re.compile(r'(.*)% *else.*:(.*)', re.IGNORECASE)
    if_elif = re.compile(r'(.*)% *elif (.*):(.*)', re.IGNORECASE)
    if_end = re.compile(r'(.*)% *endif(.*)', re.IGNORECASE)

    for_start = re.compile(r'(.*)% *for (.*):(.*)', re.IGNORECASE)
    for_end = re.compile(r'(.*)% *endfor(.*)', re.IGNORECASE)

    namespace = re.compile(r'(.*)<% *namespace name="(.*?)".* file="(.*?)".*/>(.*)', re.IGNORECASE)
    inherit = re.compile(r'(.*)<% *inherit file="(.*?)".*/>(.*)', re.IGNORECASE)

    block_single_line = re.compile(r'(.*)<% *block.*name="(.*?)".*>(.*)</% *block>(.*)', re.IGNORECASE)
    block_start = re.compile(r'(.*)<% *block.*name="(.*?)".*>(.*)', re.IGNORECASE)
    block_end = re.compile(r'(.*)</%block>(.*)', re.IGNORECASE)

    val = re.compile(r'\$\{(.*?)\}', re.IGNORECASE)
    func_len = re.compile(r'len\((.*?)\)', re.IGNORECASE)
    filter_h = re.compile(r'\|h', re.IGNORECASE)
    filter_striphtml = re.compile(r'\|striphtml', re.IGNORECASE)
    filter_u = re.compile(r'\|u', re.IGNORECASE)

    comment_single_line = re.compile(r'^.*##(.*?)$', re.IGNORECASE)

    for line in input_file:

        # Process line for repeated inline replacements
        m_val = val.search(line)
        m_func_len = func_len.search(line)
        m_filter_h = filter_h.search(line)
        m_filter_striphtml = filter_striphtml.search(line)
        m_filter_u = filter_u.search(line)

        if m_val:
            line = val.sub(r'{{ \1 }}', line)

        if m_filter_h:
            line = filter_h.sub(r'|e', line)

        if m_filter_striphtml:
            line = filter_striphtml.sub(r'|e', line)

        if m_filter_u:
            line = filter_u.sub(r'|urlencode', line)

        if m_func_len:
            line = func_len.sub(r'\1|length', line)

        # Macro start/end
        m_macro_start = macro_start.search(line)
        if m_macro_start:
            line = m_macro_start.expand(r'\1{% macro \2 %}\3') + '\n'
        m_macro_end = macro_end.search(line)
        if m_macro_end:
            line = m_macro_end.expand(r'\1{% endmacro %}\2') + '\n'

        # Process line for single 'whole line' replacements
        m_macro_start = macro_start.search(line)
        m_macro_end = macro_end.search(line)
        m_if_start = if_start.search(line)
        m_if_else = if_else.search(line)
        m_if_elif = if_elif.search(line)
        m_if_end = if_end.search(line)
        m_for_start = for_start.search(line)
        m_for_end = for_end.search(line)
        m_namspace = namespace.search(line)
        m_inherit = inherit.search(line)
        m_block_single_line = block_single_line.search(line)
        m_block_start = block_start.search(line)
        m_block_end = block_end.search(line)

        m_comment_single_line = comment_single_line.search(line)

        if m_comment_single_line:
            output += m_comment_single_line.expand(r'{# \1 #}') + '\n'

        elif m_if_start:
            output += m_if_start.expand(r'\1{% if \2 %}\3') + '\n'
        elif m_if_else:
            output += m_if_else.expand(r'\1{% else %}\2') + '\n'
        elif m_if_elif:
            output += m_if_elif.expand(r'\1{% elif \2 %}\3') + '\n'
        elif m_if_end:
            output += m_if_end.expand(r'\1{% endif %}\2') + '\n'

        elif m_for_start:
            output += m_for_start.expand(r'\1{% for \2 %}\3') + '\n'
        elif m_for_end:
            output += m_for_end.expand(r'\1{% endfor %}\2') + '\n'

        elif m_namspace:
            output += m_namspace.expand(r"\1{% import '\3' as \2 with context %}\4") + '\n'
        elif m_inherit:
            output += m_inherit.expand(r"{% extends '\2' %}\3") + '\n'

        elif m_block_single_line:
            output += m_block_single_line.expand(r'\1{% block \2 %}\3{% endblock %}\4') + '\n'
        elif m_block_start:
            output += m_block_start.expand(r'\1{% block \2 %}\3') + '\n'
        elif m_block_end:
            output += m_block_end.expand(r'\1{% endblock %}\2') + '\n'

        else:
            # Doesn't match anything we're going to process, pass though
            output += line

    return output


def jinjify_shortcodes(in_dir, out_dir):
    for fname in os.listdir(in_dir):
        if not fname.endswith('.tmpl'):
            continue
        in_file = os.path.join(in_dir, fname)
        out_file = os.path.join(out_dir, fname)
        with open(in_file) as inf:
            data = mako2jinja(inf)
        with open(out_file, 'w') as outf:
            outf.write(data)


def usage():
    print("Usage: python {} [in-dir] [out-dir]".format(sys.argv[0]))
    print("OR")
    print("Usage: python {} [in-file] [out-file]".format(sys.argv[0]))

if __name__ == "__main__":
    if len(sys.argv) == 1:
        print('Performing standard conversions:')
        for m, j in (
            ('nikola/data/themes/base', 'nikola/data/themes/base-jinja'),
            ('nikola/data/themes/bootstrap4', 'nikola/data/themes/bootstrap4-jinja'),
            ('nikola/data/themes/bootblog4', 'nikola/data/themes/bootblog4-jinja'),
        ):
            print('    {0} -> {1}'.format(m, j))
            jinjify(m, j)
        jinjify_shortcodes('nikola/data/shortcodes/mako', 'nikola/data/shortcodes/jinja')
    elif len(sys.argv) != 3:
        print('ERROR: needs input and output directory (file), or no arguments for default conversions.')
        usage()
    elif os.path.isdir(sys.argv[1]) and (os.path.isdir(sys.argv[2]) or not os.path.exists(sys.argv[2])):
        jinjify(sys.argv[1], sys.argv[2])
    elif os.path.isfile(sys.argv[1]) and (os.path.isfile(sys.argv[2]) or not os.path.exists(sys.argv[2])):
        tmpdir = tempfile.mkdtemp()
        indir = os.path.sep.join((tmpdir, 'in', 'templates'))
        outdir = os.path.sep.join((tmpdir, 'out', 'templates'))
        os.makedirs(indir)
        shutil.copy(sys.argv[1], indir)
        jinjify(os.path.dirname(indir), os.path.dirname(outdir))
        shutil.move(os.path.sep.join((outdir, os.path.basename(sys.argv[1]))), sys.argv[2])
    else:
        print('ERROR: the two arguments must be both directories or files')
        usage()
