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

Reads automaton from file or from standard input.
"""

from AutomatonType import AutomatonType
from ConversionMode import ConversionMode
from PDA import PDA
from PPDA import PPDA
from TPPD import TPPDA
from Automaton import Automaton
from Helpers import check_string
from Error import Error


def source_automaton_type(for_conversion: ConversionMode) -> AutomatonType:
    """
    Converts ConversionMode to source automaton type.

    :param for_conversion: Conversion mode of application.
    :return: Source automaton type
    """
    if for_conversion == ConversionMode.PDA2PPDA or for_conversion == ConversionMode.PDA2TPPDA:
        return AutomatonType.PDA
    elif for_conversion == ConversionMode.PPDA2PDA:
        return AutomatonType.PPDA
    else:
        return AutomatonType.TPPDA


def read_from_stdin(conversion_mode: ConversionMode):
    """
    Reads automaton from standard input.

    :param conversion_mode: Conversion mode.
    :return: Read automaton.
    """
    print("Enter depth")
    depth = input()
    check_string(r'^\d+$', depth, f"Error: {depth} is not an integer number!")
    depth = int(depth)
    print("Enter states divided by one white space.")
    states = input()
    words_pattern = r'^[a-zA-Z0-9]+( [a-zA-Z0-9]+)*$'
    check_string(words_pattern, states, "Error: Entered states contains forbidden symbols!")
    print("Enter input alphabet divided by one white space.")
    input_alphabet = input()
    check_string(words_pattern, input_alphabet, "Error: Entered input alphabet contains forbidden symbols!")
    print("Enter pushdown alphabet divided by one white space.")
    pushdown_alphabet = input()
    check_string(words_pattern, pushdown_alphabet, "Error: Entered pushdown alphabet contains forbidden symbols!")
    print("Enter starting state")
    start = input()
    print("Enter starting pushdown symbol")
    start_symbol = input()
    print("Enter final states divided by one white space.")
    final_states = input()
    check_string(words_pattern, final_states, "Error: Entered final states contain forbidden symbols!")
    automata_type = source_automaton_type(conversion_mode)
    if automata_type == AutomatonType.PDA:
        if conversion_mode == ConversionMode.PDA2TPPDA:
            check_string(
                r'^([a-wy-zA-WY-Z0-9]+ ?)+$',
                pushdown_alphabet,
                "Error: Entered pushdown alphabet contains element \'X\' which is forbidden for this conversion mode!"
            )
        print("Enter rules in form \'depth source_state nonterminal goal_state goal_string\' divided by \', \'")
        rules = input()
        rule_pattern = r'^[0-9]+( [a-zA-Z0-9]+){4}(, [0-9]+( [a-zA-Z0-9]+){4})*$'
        check_string(rule_pattern, rules, "Error: Rules are not in correct format or contain forbidden symbols.")
        return create_pda(depth, rules, start, start_symbol, states, input_alphabet, pushdown_alphabet, final_states)
    elif automata_type == AutomatonType.PPDA:
        form = '\'source_state nonterminal_1 nonterminal_2 ... nonterminal_k : goal_state goal_string_1 ' \
               'goal_string_2 ... goal_string_k\''
        print(f"Enter rules in form {form} divided by \', \'.")
        rules = input()
        one_rule_pattern = '[a-zA-Z0-9]+( [a-zA-Z0-9]+)+ : [a-zA-Z0-9]+( [a-zA-Z0-9]+)+'
        rule_pattern = r'^' + one_rule_pattern + '(, ' + one_rule_pattern + ')*$'
        check_string(rule_pattern, rules, "Error: entered rules are not in correct form.")
        return create_ppda(depth, rules, start, start_symbol, states, input_alphabet, pushdown_alphabet, final_states)
    elif automata_type == AutomatonType.TPPDA:
        check_string(
            r'^([a-wy-zA-WY-Z0-9]+ ?)+$',
            pushdown_alphabet,
            "Error: Entered pushdown alphabet contains element \'X\' which is forbidden for this conversion mode!"
        )
        form = '\'source_state nonterminal_1 nonterminal_2 ... nonterminal_depth : goal_state goal_string_1 ' \
               'goal_string_2 ... goal_string_depth\''
        print(f"Enter rules in form {form} divided by \', \'.")
        rules = input()
        one_rule_pattern = '[a-zA-Z0-9]+( [a-zA-Z0-9]+)+ : [a-zA-Z0-9]+( [a-zA-Z0-9]+)+'
        rule_pattern = r'^' + one_rule_pattern + '(, ' + one_rule_pattern + ')*$'
        check_string(rule_pattern, rules, "Error: entered rules are not in correct form.")
        return create_tppda(depth, rules, start, start_symbol, states, input_alphabet, pushdown_alphabet, final_states)
    else:
        print("ERROR: Unknown automaton type!")
        exit(Error.AUTOMATA_TYPE)


def read_from_file(conversion_mode: ConversionMode, file: str):
    """
    Reads automaton from input file.

    :param conversion_mode: Conversion mode.
    :param file: Path to input file.
    :return: Read automaton.
    """
    try:
        with open(file, 'r') as file:
            lines = file.readlines()
            file.close()
            depth = lines[0].replace('\n', "")
            check_string(r'^\d+$', depth, f"Error: {depth} is not an integer number.")
            depth = int(depth)
            states = lines[1].replace('\n', "")
            words_pattern = r'^([a-zA-Z0-9]+ ?)+$'
            check_string(words_pattern, states, "Error: States contain forbidden symbols!")
            input_alphabet = lines[2].replace('\n', "")
            check_string(words_pattern, input_alphabet, "Error: Input alphabet contains forbidden symbols!")
            pushdown_alphabet = lines[3].replace('\n', "")
            check_string(words_pattern, pushdown_alphabet, "Error: Pushdown alphabet contains forbidden symbols!")
            start = lines[4].replace('\n', "")
            start_symbol = lines[5].replace('\n', "")
            final = lines[6].replace('\n', "")
            check_string(words_pattern, final, "Error: Final states contain forbidden symbols!")
            automata_type = source_automaton_type(conversion_mode)
            if automata_type == AutomatonType.PDA:
                if conversion_mode == ConversionMode.PDA2TPPDA:
                    check_string(
                        r'^([a-wy-zA-WY-Z0-9]+ ?)+$',
                        pushdown_alphabet,
                        "Error: Pushdown alphabet contains element \'X\' which is forbidden for this conversion mode!"
                    )
                rules = ""
                for line in lines[7:]:
                    rules = rules + line.replace("\n", ", ")
                rules = rules[:len(rules) - 2]
                rule_pattern = r'^[0-9]+( [a-zA-Z0-9]+){4}(, [0-9]+( [a-zA-Z0-9]+){4})*$'
                check_string(rule_pattern, rules, "Error: Rules are not in correct format or contain forbidden symbol.")
                return create_pda(depth, rules, start, start_symbol, states, input_alphabet, pushdown_alphabet, final)
            elif automata_type == AutomatonType.PPDA:
                rules = ""
                for line in lines[7:]:
                    rules = rules + line.replace("\n", ", ")
                rules = rules[:len(rules) - 2]
                one_rule_pattern = '[a-zA-Z0-9]+( [a-zA-Z0-9]+)+ : [a-zA-Z0-9]+( [a-zA-Z0-9]+)+'
                rule_pattern = r'^' + one_rule_pattern + '(, ' + one_rule_pattern + ')*$'
                check_string(rule_pattern, rules, "Error: Rules are not in correct form.")
                return create_ppda(depth, rules, start, start_symbol, states, input_alphabet, pushdown_alphabet, final)
            elif automata_type == AutomatonType.TPPDA:
                check_string(
                    r'^([a-wy-zA-WY-Z0-9]+ ?)+$',
                    pushdown_alphabet,
                    "Error: Pushdown alphabet contains element \'X\' which is forbidden for this conversion mode!"
                )
                rules = ""
                for line in lines[7:]:
                    rules = rules + line.replace("\n", ", ")
                rules = rules[:len(rules) - 2]
                one_rule_pattern = '[a-zA-Z0-9]+( [a-zA-Z0-9]+)+ : [a-zA-Z0-9]+( [a-zA-Z0-9]+)+'
                rule_pattern = r'^' + one_rule_pattern + '(, ' + one_rule_pattern + ')*$'
                check_string(rule_pattern, rules, "Error: Rules are not in correct form.")
                return create_tppda(depth, rules, start, start_symbol, states, input_alphabet, pushdown_alphabet, final)
            else:
                print("ERROR: Unknown automaton type!")
                exit(Error.AUTOMATA_TYPE)
    except IOError as _:
        print("Error: couldn't read input file!")
        exit(Error.FILE)


def create_ppda(depth: int,
                rules: str,
                start: str,
                start_symbol: str,
                states: str,
                input_alphabet: str,
                pushdown_alphabet: str,
                final_states: str) -> PPDA:
    """
    Creates parallel deep pushdown automaton.

    :param depth: Depth of automaton.
    :param rules: String representation of parallel deep pushdown automatons rules.
    :param start: String representation of starting state of automaton.
    :param start_symbol: String representation of starting pushdown symbol.
    :param states: String representation of automaton states
    :param input_alphabet: String representation of automatons input alphabet.
    :param pushdown_alphabet: String representation of automatons pushdown alphabet.
    :param final_states: String representation of final states.
    :return: Class representing parallel deep pushdown automaton.
    """
    automaton = Automaton(depth, states, input_alphabet, pushdown_alphabet, start, final_states)
    return PPDA.from_input(automaton, start_symbol, rules)


def create_tppda(depth: int,
                 rules: str,
                 start: str,
                 start_word: str,
                 states: str,
                 input_alphabet: str,
                 pushdown_alphabet: str,
                 final_states: str) -> PPDA:
    """
    Creates tuple parallel deep pushdown automaton.

    :param depth: Depth of automaton.
    :param rules: String representation of tuple parallel deep pushdown automatons rules.
    :param start: String representation of starting state of automaton.
    :param start_word: Starting pushdown word.
    :param states: String representation of automaton states
    :param input_alphabet: String representation of automatons input alphabet.
    :param pushdown_alphabet: String representation of automatons pushdown alphabet.
    :param final_states: String representation of final states.
    :return: Class representing parallel deep pushdown automaton.
    """
    automaton = Automaton(depth, states, input_alphabet, pushdown_alphabet, start, final_states)
    return TPPDA.from_input(automaton, start_word, rules)


def create_pda(depth: int,
               rules: str,
               start: str,
               start_symbol: str,
               states: str,
               input_alphabet: str,
               pushdown_alphabet: str,
               final_states: str) -> PDA:
    """
    Creates deep pushdown automaton.

    :param depth: Depth of automaton.
    :param rules: String representation of deep pushdown automatons rules.
    :param start: String representation of starting state of automaton.
    :param start_symbol: String representation of starting pushdown symbol.
    :param states: String representation of automaton states
    :param input_alphabet: String representation of automatons input alphabet.
    :param pushdown_alphabet: String representation of automatons pushdown alphabet.
    :param final_states: String representation of final states.
    :return: Class representing deep pushdown automaton.
    """
    automaton = Automaton(depth, states, input_alphabet, pushdown_alphabet, start, final_states)
    return PDA.from_input(automaton, start_symbol, rules)
