from __future__ import unicode_literals

import codecs
from hashlib import md5
import os
import re

from path import Path

from django.core.management import ManagementUtility
from django.core.management.commands.makemessages import Command as BaseCommand
from django.utils.six import text_type
from django.utils.text import smart_split


I18N_HELPERS = {
    # helper: min valid expression len
    'gettext': 2,
    'ngettext': 4,
    'gettext_noop': 2,
    'pgettext': 3,
    'npgettext': 5
}

HBS_HELPERS = ('unbound', 'if')
HBS_EXPRESSION = re.compile(r'({{{(.*?)}}})|({{(.*?)}})')


class HandlebarsExpression(object):
    def __init__(self, unparsed_expression):
        cleaned_expression = self.clean_expression(unparsed_expression)
        all_helpers = self.parse_expression(
            unparsed_expression, cleaned_expression)

        self.i18n_helpers = self.clean_helpers(all_helpers)

    def get_i18n_helpers(self):
        return self.i18n_helpers

    def clean_expression(self, unparsed):
        cleaned = ''

        for piece in smart_split(unparsed):
            if not cleaned and piece in HBS_HELPERS:
                continue
            if not piece.startswith('=') and not cleaned.endswith('='):
                cleaned += ' '
            cleaned += piece

        return cleaned.strip()

    def parse_expression(self, unparsed, cleaned):
        helper = []
        helpers = [helper]
        stack = [helper]

        for piece in smart_split(cleaned):
            if piece.endswith(')'):
                stack[-1].append(piece.rstrip(')').strip())
                while piece.endswith(')'):
                    piece = piece[:-1].strip()
                    stack.pop()
                continue

            if not piece.startswith(('\'', '"')):
                if piece.startswith('('):
                    piece = piece[1:].strip()
                    if piece.startswith('('):
                        continue
                    else:
                        helper = [piece]
                        helpers.append(helper)
                        stack.append(helper)
                else:
                    is_kwarg = re.match(r'^[_a-zA-Z]+([_a-zA-Z0-9]+?)=', piece)
                    if is_kwarg and not piece.endswith('='):
                        piece = piece[len(is_kwarg.group(0)):]
                        if piece.startswith('('):
                            helper = [piece[1:].strip()]
                            helpers.append(helper)
                            stack.append(helper)
                    else:
                        stack[-1].append(piece)
            else:
                stack[-1].append(piece)

        return helpers

    def clean_helpers(self, all_helpers):
        i18n_helpers = []
        for helper in all_helpers:
            i18n_helper_len = I18N_HELPERS.get(helper[0])
            if i18n_helper_len and len(helper) >= i18n_helper_len:
                i18n_helpers.append(helper[:i18n_helper_len])
        return i18n_helpers


class HandlebarsTemplate(object):
    def __init__(self, content):
        self.expressions = {}
        self.content = content

    def get_converted_content(self):
        stripped_content = self.strip_expressions(self.content)
        stripped_content = self.strip_non_expressions(stripped_content)
        replaced_content = self.replace_expressions(stripped_content)
        return replaced_content

    def strip_expressions(self, content):
        def replace_expression(matchobj):
            trimmed_expression = matchobj.group(0).lstrip('{').rstrip('}')
            parsed_expression = HandlebarsExpression(trimmed_expression)

            expression_i18n_helpers = parsed_expression.get_i18n_helpers()

            if expression_i18n_helpers:
                self.expressions[matchobj.group(0)] = expression_i18n_helpers
                return matchobj.group(0)
            else:
                return ''

        return HBS_EXPRESSION.sub(replace_expression, self.content)

    def strip_non_expressions(self, content):
        stripped = ''

        cursor = 0
        for expression in HBS_EXPRESSION.finditer(content):
            position = content.find(expression.group(0), cursor)

            content_slice = content[cursor:position]
            if content_slice:
                slice_lines = len(content_slice.splitlines())
                if slice_lines:
                    stripped += '\n' * (slice_lines - 1)

            stripped += expression.group(0)
            cursor = position + len(expression.group(0))

        return stripped

    def replace_expressions(self, content):
        def replace_expression(matchobj):
            js_functions = []
            for helper in self.expressions.get(matchobj.group(0)):
                function, args = helper[0], helper[1:]
                js_functions.append('%s(%s);' % (function, ', '.join(args)))
            return ' '.join(js_functions)

        return HBS_EXPRESSION.sub(replace_expression, content)


class HandlebarsFile(object):
    def __init__(self, hbs_path, make_js_file=True):
        self.hbs_path = hbs_path
        self.path_suffix = self.make_js_path_suffix(hbs_path)
        self.js_path = self.make_js_path(hbs_path, self.path_suffix)

        if make_js_file:
            self.make_js_file(self.hbs_path, self.js_path)

    def make_js_path_suffix(self, hbs_path):
        signature = md5(text_type(hbs_path).encode("utf-8")).hexdigest()[:8]
        return '%s.makemessages.js' % signature

    def make_js_path(self, hbs_path, path_suffix):
        return Path('%s.%s' % (text_type(hbs_path), path_suffix))

    def make_js_file(self, hbs_path, js_path):
        file_content = ''
        with codecs.open(hbs_path, encoding='utf-8', mode="r") as hbs_file:
            file_content = hbs_file.read()

        js_file = codecs.open(js_path, encoding='utf-8', mode='w')
        js_file.write(HandlebarsTemplate(file_content).get_converted_content())
        js_file.close()

    def delete(self):
        if self.js_path.exists() and self.js_path.isfile():
            self.js_path.unlink()


class Command(BaseCommand):
    help = ("Runs over the entire source tree of the current directory and "
"pulls out all strings marked for translation. It creates (or updates) a message "
"file in the conf/locale (in the django tree) or locale (for projects and "
"applications) directory.\n\nIf command is executed for djangojs domain, it "
"also pulls strings from .hbs and .handlebars files.\n\nYou must run this "
"command with one of either the --locale, --exclude or --all options.")

    JS_TEMPLATES = ('.hbs', '.handlebars')

    def handle(self, *args, **options):
        locales = options.get('locale')
        self.domain = options.get('domain')

        cdw = Path(os.getcwd())
        subdirs = [text_type(d.basename()) for d in cdw.dirs()]
        use_subroutines = 'locale' in subdirs and self.domain == 'djangojs'

        tmp_js_files = []
        if use_subroutines:
            # fake js files from templates
            tmp_js_files = self.prepare_tmp_js_files(cdw);

        super(Command, self).handle(*args, **options)

        if use_subroutines:
            # cleanup everything
            self.cleanup_tmp_js_templates(tmp_js_files);
            self.cleanup_po_files(cdw, locales, tmp_js_files);

    def prepare_tmp_js_files(self, cdw=None):
        files = []
        for extension in self.JS_TEMPLATES:
            for hbs_file in cdw.walkfiles('*' + extension):
                files.append(HandlebarsFile(hbs_file))
        return files

    def cleanup_po_files(self, cdw, locales, tmp_js_files):
        strip_tokens = [js_file.path_suffix for js_file in tmp_js_files]

        for po_file in cdw.walkfiles('djangojs.po'):
            if not locales or po_file.splitall()[-3] in locales:
                self.cleanup_po_file(po_file, strip_tokens)

    def cleanup_po_file(self, po_path, strip_tokens):
        file_content = ''
        with codecs.open(po_path, encoding='utf-8', mode="r") as po_file:
            file_content = po_file.read()

        for token in strip_tokens:
            file_content = file_content.replace(token, '')

        po_file = codecs.open(po_path, encoding='utf-8', mode='w')
        po_file.write(file_content)
        po_file.close()

    def cleanup_tmp_js_templates(self, tmp_js_files):
        for js_file in tmp_js_files:
            js_file.delete()


class Admin(ManagementUtility):
    """
    Thin wrapper for django-admin.py that swaps Django's makemessages for ours
    """
    def fetch_command(self, subcommand):
        if subcommand == "makemessages":
            return Command()
        else:
            return super(Admin, self).fetch_command(subcommand)
