from collections import UserList
from pathlib import Path

from parsimonious.grammar import Grammar
from parsimonious.nodes import NodeVisitor

from ._chord import Chord
from ._require import require

Pattern = require('Patterns').Pattern
PRand = require('Patterns').Generators.PRand
PGroupStar = require('Patterns').PGroups.PGroupStar
TimeVar = require('TimeVar').TimeVar
asStream = require('Patterns').Main.asStream


class Extendable(UserList):
    """Should be used to [].extend()."""


def arpeggio(value, dur):
    value = Extendable(value.notes)
    cur_dur = dur[0]
    if cur_dur == 1.0:
        dur = [cur_dur for _ in range(len(value))]
    elif cur_dur.is_integer():
        value = Extendable([v for _ in range(int(cur_dur)) for v in value])
        dur = [1.0 for _ in range(len(value))]
    return value, dur


def repeat(dur, rep_val):
    return [rep_val for _ in range(len(dur))]


grammar = Grammar(
    (Path(__file__).resolve().parent / 'grammar.peg').read_text()
)


class Visitor(NodeVisitor):
    """Visitor nodes."""

    def visit_root(self, _node, children):
        """Root visitor."""
        _ws, sequence, _ws = children
        return sequence

    def visit_group_nests(self, node, children):
        """Group nests visitor.

        e.g. c['(C D)']
        """
        _, _, (value, dur), _, _ = children
        return {'value': [value], 'dur': [dur]}

    def visit_group_square(self, node, children):
        """Group square visitor.

        e.g. c['[C D]']
        """
        _, _, (value, dur), _, _ = children
        return {'value': [PGroupStar(value)], 'dur': asStream(dur)}

    def visit_group_curly(self, node, children):
        """Group curly visitor.

        e.g. c['{C D}']
        """
        _, _, (value, dur), _, _ = children
        return {'value': [PRand(value)], 'dur': asStream(dur)}

    def visit_group_arrow(self, node, children):
        """Group arrow visitor.

        e.g. c['<C!2 Dm G><Bm7@>']
        """
        (value, dur), extra = children

        nested = False
        if isinstance(extra, list):
            for ext in extra:
                nested = True
                _, (z_value, z_dur), _ = ext
                value = value & asStream(z_value)
                dur.append(z_dur)

        if not nested:
            data = value.data
        else:
            data = [tuple(value.data[0].data)]

        return {'value': data, 'dur': [dur]}

    def visit_arrow(self, node, children):
        """Arrow visitor."""
        _, _, (value, dur), _, _ = children
        return Pattern(value), dur

    def visit_group(self, _node, groups):
        """Group visitor."""
        items = {'value': [], 'dur': []}

        def extend(ext):
            items['value'].extend(ext['value'])
            items['dur'].extend(ext['dur'])

        element, extras = groups
        if isinstance(element, list):
            for ext in element:
                extend(ext)

        if not isinstance(extras, list):
            return items

        if not isinstance(extras[0], list):
            ((_ws, ext),) = extras
            extras = [(_ws, ext)]

        for _ws, ext in extras:
            if isinstance(ext, list):
                for e in ext:
                    extend(e)

        return items

    def visit_sequence(self, _node, children):
        """Sequence visitor."""
        value, groups, seqs = children

        items = value['value']
        durs = value['dur']

        if isinstance(groups, list):
            for ext in groups:
                items.extend(groups['value'])
                durs.extend(groups['dur'])

        return items, durs

    def visit_term(self, _node, children):
        """Term visitor."""
        value, repeat = children

        if value and isinstance(value[0], dict):
            value = value[0]

        if repeat and isinstance(repeat, list) and isinstance(value, dict):
            value['dur'] = float(repeat[0]['value'])

        return value

    def visit_term_chord(self, _node, children):
        """Visit term chord."""
        value, (mods,) = children

        first_op = second_op = None
        if isinstance(mods, list):
            first_op, second_op, _ = mods

        modifiers = []

        if isinstance(first_op, list):
            modifiers += first_op
        elif isinstance(first_op, dict):
            modifiers += [first_op]

        if isinstance(second_op, list):
            modifiers += second_op
        elif isinstance(second_op, dict):
            modifiers += [second_op]

        dur = [1.0]
        for mod in modifiers:
            if mod['op'] == 'arpeggio':
                value, dur = arpeggio(value, dur)
            elif mod['op'] == 'repeat':
                dur = repeat(dur, mod['value'])

        return {
            'value': [value] if isinstance(value, Chord) else value,
            'dur': dur,
        }

    def visit_term_note(self, node, children):
        """Term note visitor."""
        # TODO: parse note number, e.g. c['[c2 ab4 f#] Cm7']
        return children or node

    def visit_repeat(self, node, _children):  # TODO: fix on nested groups
        """Repeat visitor."""
        count = node.text.replace('!', '')
        return {'op': node.expr_name, 'value': float(count)}

    def visit_arpeggio(self, node, _children):
        """Arpeggio visitor."""
        return {'op': node.expr_name}

    def visit_chord(self, node, _children):
        """Chords visitor."""
        return Chord(node.text)

    def visit_number(self, _node, children):
        """Numbers visitor."""
        return {'value': [children[0]], 'dur': [1.0]}

    def visit_floating(self, node, _children):
        """Float visitor."""
        return float(node.text)

    def visit_integer(self, node, _children):
        """Integer visitor."""
        return int(node.text)

    def visit_ws(self, node, children):
        """White space visitor."""
        return

    def generic_visit(self, node, children):
        """Genericing visitor."""
        return children or node


def parse(data):
    """Parse expression."""
    return Visitor().visit(grammar.parse(data))
