"""
Name: Converter.py
Author: Radovan Klembara

File consists of functions that convert automata to another automaton type.
"""

from PDA import PDA, PDAExpandRule
from PPDA import PPDA, PPDAExpandRule
from TPPD import TPPDA, TPPDAExpandRule
from itertools import product


def convert_pda_to_ppda(automaton: PDA) -> PPDA:
    """
    Converts deep pushdown automaton to parallel deep pushdown automata.

    :param automaton: Deep pushdown automaton.
    :return: Parallel deep pushdown automaton.
    """
    ppda_rules = []
    for rule in automaton.expand_rules:
        pushdown_permutations = [p for p in product(automaton.nonterminals, repeat=rule.depth - 1)]
        for pushdown_permutation in pushdown_permutations:
            ppda_rules.append(PPDAExpandRule(rule.source_state,
                                             list(pushdown_permutation) + [rule.pushdown_symbol],
                                             rule.goal_state,
                                             list(pushdown_permutation) + [rule.goal_string]))
    ppda_rules = list(set(ppda_rules))
    return PPDA(
        automaton.depth,
        automaton.states,
        automaton.input_alphabet,
        automaton.nonterminals,
        ppda_rules,
        automaton.start_state,
        automaton.start_pushdown,
        automaton.final_states
    )


def convert_ppda_to_pda(automaton: PPDA) -> PDA:
    """
    Converts parallel deep pushdown automata to deep pushdown automaton.

    :param automaton: Parallel deep pushdown automaton.
    :return: Deep pushdown automaton.
    """
    pda_rules = []
    for rule in automaton.expand_rules:
        rule_size = len(rule.pushdown_symbols) - 1
        i = rule_size
        while i > 0:
            pda_rules.append(PDAExpandRule(
                i,
                rule.goal_state,
                rule.pushdown_symbols[i - 1],
                rule.goal_state,
                rule.goal_strings[i - 1]
            ))
            i -= 1
        pda_rules.append(PDAExpandRule(
            rule_size + 1,
            rule.source_state,
            rule.pushdown_symbols[rule_size],
            rule.goal_state,
            rule.goal_strings[rule_size]
        ))
    pda_rules = list(set(pda_rules))
    return PDA(
        automaton.depth,
        automaton.states,
        automaton.input_alphabet,
        automaton.nonterminals,
        pda_rules,
        automaton.start_state,
        automaton.start_pushdown,
        automaton.final_states
    )


def convert_pda_to_tppda(automaton: PDA) -> TPPDA:
    """
    Converts deep pushdown automaton to tuple parallel deep pushdown automata.

    :param automaton: Deep pushdown automaton.
    :return: Tuple parallel deep pushdown automaton.
    """
    nonterminals = automaton.nonterminals + ["X"]
    start_pushdown_word = automaton.start_pushdown
    for i in range(0, automaton.depth):
        start_pushdown_word += "X"
    tppda_rules = []
    for rule in automaton.expand_rules:
        pushdown_permutations = [p for p in product(nonterminals, repeat=rule.depth - 1)]
        suffix_permutations = [p for p in product(nonterminals, repeat=automaton.depth - rule.depth)]
        for pushdown_permutation in pushdown_permutations:
            for suffix_permutation in suffix_permutations:
                source_symbols = list(pushdown_permutation) + [rule.pushdown_symbol] + list(suffix_permutation)
                goal_strings = list(pushdown_permutation) + [rule.goal_string] + list(suffix_permutation)
                tppda_rules.append(TPPDAExpandRule(rule.source_state,
                                                   source_symbols,
                                                   rule.goal_state,
                                                   goal_strings))
    tppda_rules = list(set(tppda_rules))
    max_filling = ["X" for _ in range(0, automaton.depth)]
    empty_filling = list(map(lambda _: "epsilon", max_filling))
    for state in automaton.states:
        tppda_rules.append(TPPDAExpandRule(state, max_filling, state, empty_filling))
    return TPPDA(
        automaton.depth,
        automaton.states,
        automaton.input_alphabet,
        nonterminals,
        tppda_rules,
        automaton.start_state,
        start_pushdown_word,
        automaton.final_states
    )


def convert_tppda_to_pda(automaton: TPPDA) -> PDA:
    """
    Converts tuple parallel deep pushdown automata to deep pushdown automaton.

    :param automaton: Parallel deep pushdown automaton.
    :return: Deep pushdown automaton.
    """
    nonterminals = automaton.nonterminals + ["X"]
    pda_rules = [PDAExpandRule(1, automaton.start_state, "X", automaton.start_state, automaton.start_pushdown)]
    old_rules = automaton.expand_rules
    while len(old_rules) > 0:
        rule = old_rules[0]
        old_rules = old_rules[1:]
        for i in range(1, automaton.depth):
            pda_rules.append(PDAExpandRule(
                i,
                rule.source_state,
                rule.pushdown_symbols[i - 1],
                rule.source_state,
                rule.goal_strings[i - 1]
            ))
        pda_rules.append(PDAExpandRule(
            automaton.depth,
            rule.source_state,
            rule.pushdown_symbols[automaton.depth - 1],
            rule.goal_state,
            rule.goal_strings[automaton.depth - 1]
        ))
    pda_rules = list(set(pda_rules))
    return PDA(
        automaton.depth,
        automaton.states,
        automaton.input_alphabet,
        nonterminals,
        pda_rules,
        automaton.start_state,
        "X",
        automaton.final_states
    )
