from __future__ import unicode_literals
from . import ast


def indent_except_first_line(content):
    return "    ".join(
        content.splitlines(True)
    )


def includes_new_line(elem):
    return isinstance(elem, ast.TextElement) and "\n" in elem.value


def is_select_expr(elem):
    return (
        isinstance(elem, ast.Placeable) and
        isinstance(elem.expression, ast.SelectExpression))


def should_start_on_new_line(pattern):
    is_multiline = any(is_select_expr(elem) for elem in pattern.elements) \
        or any(includes_new_line(elem) for elem in pattern.elements)

    if is_multiline:
        first_element = pattern.elements[0]
        if isinstance(first_element, ast.TextElement):
            first_char = first_element.value[0]
            if first_char in ("[", ".", "*"):
                return False
        return True
    return False


class FluentSerializer(object):
    """FluentSerializer converts :class:`.ast.SyntaxNode` objects to unicode strings.

    `with_junk` controls if parse errors are written back or not.
    """
    HAS_ENTRIES = 1

    def __init__(self, with_junk=False):
        self.with_junk = with_junk

    def serialize(self, resource):
        "Serialize a :class:`.ast.Resource` to a string."
        if not isinstance(resource, ast.Resource):
            raise Exception('Unknown resource type: {}'.format(type(resource)))

        state = 0

        parts = []
        for entry in resource.body:
            if not isinstance(entry, ast.Junk) or self.with_junk:
                parts.append(self.serialize_entry(entry, state))
                if not state & self.HAS_ENTRIES:
                    state |= self.HAS_ENTRIES

        return "".join(parts)

    def serialize_entry(self, entry, state=0):
        "Serialize an :class:`.ast.Entry` to a string."
        if isinstance(entry, ast.Message):
            return serialize_message(entry)
        if isinstance(entry, ast.Term):
            return serialize_term(entry)
        if isinstance(entry, ast.Comment):
            if state & self.HAS_ENTRIES:
                return "\n{}\n".format(serialize_comment(entry, "#"))
            return "{}\n".format(serialize_comment(entry, "#"))
        if isinstance(entry, ast.GroupComment):
            if state & self.HAS_ENTRIES:
                return "\n{}\n".format(serialize_comment(entry, "##"))
            return "{}\n".format(serialize_comment(entry, "##"))
        if isinstance(entry, ast.ResourceComment):
            if state & self.HAS_ENTRIES:
                return "\n{}\n".format(serialize_comment(entry, "###"))
            return "{}\n".format(serialize_comment(entry, "###"))
        if isinstance(entry, ast.Junk):
            return serialize_junk(entry)
        raise Exception('Unknown entry type: {}'.format(type(entry)))


def serialize_comment(comment, prefix="#"):
    prefixed = "\n".join([
        prefix if len(line) == 0 else "{} {}".format(prefix, line)
        for line in comment.content.split("\n")
    ])
    # Add the trailing line break.
    return '{}\n'.format(prefixed)


def serialize_junk(junk):
    return junk.content


def serialize_message(message):
    parts = []

    if message.comment:
        parts.append(serialize_comment(message.comment))

    parts.append("{} =".format(message.id.name))

    if message.value:
        parts.append(serialize_pattern(message.value))

    if message.attributes:
        for attribute in message.attributes:
            parts.append(serialize_attribute(attribute))

    parts.append("\n")
    return ''.join(parts)


def serialize_term(term):
    parts = []

    if term.comment:
        parts.append(serialize_comment(term.comment))

    parts.append("-{} =".format(term.id.name))
    parts.append(serialize_pattern(term.value))

    if term.attributes:
        for attribute in term.attributes:
            parts.append(serialize_attribute(attribute))

    parts.append("\n")
    return ''.join(parts)


def serialize_attribute(attribute):
    return "\n    .{} ={}".format(
        attribute.id.name,
        indent_except_first_line(serialize_pattern(attribute.value))
    )


def serialize_pattern(pattern):
    content = "".join(serialize_element(elem) for elem in pattern.elements)
    content = indent_except_first_line(content)

    if should_start_on_new_line(pattern):
        return '\n    {}'.format(content)

    return ' {}'.format(content)


def serialize_element(element):
    if isinstance(element, ast.TextElement):
        return element.value
    if isinstance(element, ast.Placeable):
        return serialize_placeable(element)
    raise Exception('Unknown element type: {}'.format(type(element)))


def serialize_placeable(placeable):
    expr = placeable.expression
    if isinstance(expr, ast.Placeable):
        return "{{{}}}".format(serialize_placeable(expr))
    if isinstance(expr, ast.SelectExpression):
        # Special-case select expressions to control the withespace around the
        # opening and the closing brace.
        return "{{ {}}}".format(serialize_expression(expr))
    if isinstance(expr, ast.Expression):
        return "{{ {} }}".format(serialize_expression(expr))


def serialize_expression(expression):
    if isinstance(expression, ast.StringLiteral):
        return '"{}"'.format(expression.value)
    if isinstance(expression, ast.NumberLiteral):
        return expression.value
    if isinstance(expression, ast.VariableReference):
        return "${}".format(expression.id.name)
    if isinstance(expression, ast.TermReference):
        out = "-{}".format(expression.id.name)
        if expression.attribute is not None:
            out += ".{}".format(expression.attribute.name)
        if expression.arguments is not None:
            out += serialize_call_arguments(expression.arguments)
        return out
    if isinstance(expression, ast.MessageReference):
        out = expression.id.name
        if expression.attribute is not None:
            out += ".{}".format(expression.attribute.name)
        return out
    if isinstance(expression, ast.FunctionReference):
        args = serialize_call_arguments(expression.arguments)
        return "{}{}".format(expression.id.name, args)
    if isinstance(expression, ast.SelectExpression):
        out = "{} ->".format(
            serialize_expression(expression.selector))
        for variant in expression.variants:
            out += serialize_variant(variant)
        return "{}\n".format(out)
    if isinstance(expression, ast.Placeable):
        return serialize_placeable(expression)
    raise Exception('Unknown expression type: {}'.format(type(expression)))


def serialize_variant(variant):
    return "\n{}[{}]{}".format(
        "   *" if variant.default else "    ",
        serialize_variant_key(variant.key),
        indent_except_first_line(serialize_pattern(variant.value))
    )


def serialize_call_arguments(expr):
    positional = ", ".join(
        serialize_expression(arg) for arg in expr.positional)
    named = ", ".join(
        serialize_named_argument(arg) for arg in expr.named)
    if len(expr.positional) > 0 and len(expr.named) > 0:
        return '({}, {})'.format(positional, named)
    return '({})'.format(positional or named)


def serialize_named_argument(arg):
    return "{}: {}".format(
        arg.name.name,
        serialize_expression(arg.value)
    )


def serialize_variant_key(key):
    if isinstance(key, ast.Identifier):
        return key.name
    if isinstance(key, ast.NumberLiteral):
        return key.value
    raise Exception('Unknown variant key type: {}'.format(type(key)))
