# Path: aisikl/app.py
# def assert_ops(operations, *expected_methods):
#     '''Ensures the list of collected operations contains the methods we expect.
# 
#     This is a helper method you can use after calling
#     :meth:`~Application.collect_operations`. It just looks at each operation's
#     :attr:`~Operation.method` and checks if they match.
# 
#     :param operations: The collected operations.
#     :param \*expected_methods: A list of method names.
#     Raises:
#         ~aisikl.exceptions.AISBehaviorError: Raised if the methods don't match.
#     '''
#     real_methods = [op.method for op in operations]
#     expected_methods = list(expected_methods)
#     if real_methods != expected_methods:
#         raise AISBehaviorError(
#             "We expected %r but AIS did something different: %r" %
#                 (expected_methods, operations),
#             expected_methods=expected_methods,
#             operations=operations)
# 
# Path: fladgejt/helpers.py
# class CantOpenApplication(Exception):
#     '''AIS didn't open the application we wanted.
# 
#     Thrown from ``_open_...`` methods if AIS refuses to open the application
#     (e.g. the menu item is disabled for this user). The caller should catch
#     this and show a message to the user. This is only used when we know it can
#     happen -- unknown AIS behavior will cause an AISBehaviorError.
#     '''
# 
# def find_row(objects, **conditions):
#     '''Searches the list of objects for one that matches all given conditions.
# 
#     ``obj['foo'] == bar`` must be true for all ``foo=bar`` keyword arguments
#     for ``obj`` to match. The index of the first matching object is returned.
#     This is useful for table rows and similar mapping-like objects.
# 
#     :param objects: List of objects.
#     :param \*\*conditions: The conditions that the object must match.
#     Returns:
#         The index of the first object that had all the given keys and values.
#     Raises:
#         KeyError: Raised if no such object is found.
#     '''
#     for index, obj in enumerate(objects):
#         if all(obj[key] == value for key, value in conditions.items()):
#             return index
#     raise KeyError("Object not found: {!r}".format(conditions))
# 
# def find_option(objects, **conditions):
#     '''Searches the list of objects for one that matches all given conditions.
# 
#     ``obj.foo == bar`` must be true for all ``foo=bar`` keyword arguments for
#     ``obj`` to match. The index of the first matching object is returned. This
#     is useful for combo box options and similar objects.
# 
#     :param objects: List of objects.
#     :param \*\*conditions: The conditions that the object must match.
#     Returns:
#         The index of the first object that had all the given keys and values.
#     Raises:
#         KeyError: Raised if no such object is found.
#     '''
#     for index, obj in enumerate(objects):
#         if all(getattr(obj, key) == value for key, value in conditions.items()):
#             return index
#     raise KeyError("Object not found: {!r}".format(conditions))
# 
# def encode_key(tuple):
#     '''Reversibly encode a tuple of strings into a single string.
# 
#     See :func:`keyed_namedtuple` for the rationale.
# 
#     Empty tuples may be decoded incorrectly. But :func:`keyed_namedtuple`
#     doesn't support empty keys anyway.
# 
#     The current encoding additionally always outputs URL-safe strings. But its
#     reversibility is the more important property.
# 
#     Args:
#         tuple: A tuple of strings that is to be encoded.
#     Returns:
#         A string ``s`` such that ``decode_key(s)`` produces the original tuple.
#     '''
#     result = []
#     for part in tuple:
#         part = part.encode('utf8')
#         part = urlsafe_b64encode(part)
#         part = part.rstrip(b'=')
#         part = part.decode('ascii')
#         result.append(part)
#     return '.'.join(result)
# 
# def decode_key(string):
#     '''Decode the output of :func:`encode_key` back into a tuple of strings.
# 
#     See :func:`keyed_namedtuple` for the rationale.
# 
#     Args:
#         string: A string created with :func:`encode_key`.
#     Returns:
#         The tuple of strings that was originally given to :func:`encode_key`.
#     '''
#     result = []
#     for part in string.split('.'):
#         part = part.encode('ascii')
#         part += b'=' * (-len(part) % 4)
#         part = urlsafe_b64decode(part)
#         part = part.decode('utf8')
#         result.append(part)
#     return tuple(result)
# 
# Path: fladgejt/structures.py



from aisikl.app import assert_ops
from fladgejt.helpers import (
    CantOpenApplication, find_row, find_option, encode_key, decode_key)
from fladgejt.structures import Predmet, Termin, PrihlasenyStudent
        return result

    def __open_vyber_terminu_dialog(self, app):
        # Caller musi dialog potom zavriet, aby otvorena aplikacia bola zase
        # v konzistentnom stave pre dalsieho co ju pouzije.

        # Stlacime button "Prihlasit sa na termin" dole.
        with app.collect_operations() as ops:
            app.d.pridatButton.click()

        # Otvori sa novy dialog.
        app.awaited_open_dialog(ops)

    def __select_predmet_row(self, app, predmet_key):
        self.__vyber_oba_semestre(app)

        (skratka,) = decode_key(predmet_key)
        index = find_row(app.d.predmetyTable.all_rows(), skratka=skratka)
        app.d.predmetyTable.select(index)

        return app.d.predmetyTable.all_rows()[index]

    def get_vypisane_terminy_predmetu(self, zapisny_list_key, predmet_key):
        app = self._open_terminy_hodnotenia_app(zapisny_list_key)

        predmet_row = self.__select_predmet_row(app, predmet_key)
        self.__open_vyber_terminu_dialog(app)

        studium_key, akademicky_rok = decode_key(zapisny_list_key)


# Path: aisikl/app.py
# def assert_ops(operations, *expected_methods):
#     '''Ensures the list of collected operations contains the methods we expect.
# 
#     This is a helper method you can use after calling
#     :meth:`~Application.collect_operations`. It just looks at each operation's
#     :attr:`~Operation.method` and checks if they match.
# 
#     :param operations: The collected operations.
#     :param \*expected_methods: A list of method names.
#     Raises:
#         ~aisikl.exceptions.AISBehaviorError: Raised if the methods don't match.
#     '''
#     real_methods = [op.method for op in operations]
#     expected_methods = list(expected_methods)
#     if real_methods != expected_methods:
#         raise AISBehaviorError(
#             "We expected %r but AIS did something different: %r" %
#                 (expected_methods, operations),
#             expected_methods=expected_methods,
#             operations=operations)
# 
# Path: fladgejt/helpers.py
# class CantOpenApplication(Exception):
#     '''AIS didn't open the application we wanted.
# 
#     Thrown from ``_open_...`` methods if AIS refuses to open the application
#     (e.g. the menu item is disabled for this user). The caller should catch
#     this and show a message to the user. This is only used when we know it can
#     happen -- unknown AIS behavior will cause an AISBehaviorError.
#     '''
# 
# def find_row(objects, **conditions):
#     '''Searches the list of objects for one that matches all given conditions.
# 
#     ``obj['foo'] == bar`` must be true for all ``foo=bar`` keyword arguments
#     for ``obj`` to match. The index of the first matching object is returned.
#     This is useful for table rows and similar mapping-like objects.
# 
#     :param objects: List of objects.
#     :param \*\*conditions: The conditions that the object must match.
#     Returns:
#         The index of the first object that had all the given keys and values.
#     Raises:
#         KeyError: Raised if no such object is found.
#     '''
#     for index, obj in enumerate(objects):
#         if all(obj[key] == value for key, value in conditions.items()):
#             return index
#     raise KeyError("Object not found: {!r}".format(conditions))
# 
# def find_option(objects, **conditions):
#     '''Searches the list of objects for one that matches all given conditions.
# 
#     ``obj.foo == bar`` must be true for all ``foo=bar`` keyword arguments for
#     ``obj`` to match. The index of the first matching object is returned. This
#     is useful for combo box options and similar objects.
# 
#     :param objects: List of objects.
#     :param \*\*conditions: The conditions that the object must match.
#     Returns:
#         The index of the first object that had all the given keys and values.
#     Raises:
#         KeyError: Raised if no such object is found.
#     '''
#     for index, obj in enumerate(objects):
#         if all(getattr(obj, key) == value for key, value in conditions.items()):
#             return index
#     raise KeyError("Object not found: {!r}".format(conditions))
# 
# def encode_key(tuple):
#     '''Reversibly encode a tuple of strings into a single string.
# 
#     See :func:`keyed_namedtuple` for the rationale.
# 
#     Empty tuples may be decoded incorrectly. But :func:`keyed_namedtuple`
#     doesn't support empty keys anyway.
# 
#     The current encoding additionally always outputs URL-safe strings. But its
#     reversibility is the more important property.
# 
#     Args:
#         tuple: A tuple of strings that is to be encoded.
#     Returns:
#         A string ``s`` such that ``decode_key(s)`` produces the original tuple.
#     '''
#     result = []
#     for part in tuple:
#         part = part.encode('utf8')
#         part = urlsafe_b64encode(part)
#         part = part.rstrip(b'=')
#         part = part.decode('ascii')
#         result.append(part)
#     return '.'.join(result)
# 
# def decode_key(string):
#     '''Decode the output of :func:`encode_key` back into a tuple of strings.
# 
#     See :func:`keyed_namedtuple` for the rationale.
# 
#     Args:
#         string: A string created with :func:`encode_key`.
#     Returns:
#         The tuple of strings that was originally given to :func:`encode_key`.
#     '''
#     result = []
#     for part in string.split('.'):
#         part = part.encode('ascii')
#         part += b'=' * (-len(part) % 4)
#         part = urlsafe_b64decode(part)
#         part = part.decode('utf8')
#         result.append(part)
#     return tuple(result)
# 
# Path: fladgejt/structures.py



# Path: fladgejt/webui/terminy.py
from aisikl.app import assert_ops
from fladgejt.helpers import (
    CantOpenApplication, find_row, find_option, encode_key, decode_key)
from fladgejt.structures import Predmet, Termin, PrihlasenyStudent

        return result

    def __open_vyber_terminu_dialog(self, app):
        # Caller musi dialog potom zavriet, aby otvorena aplikacia bola zase
        # v konzistentnom stave pre dalsieho co ju pouzije.

        # Stlacime button "Prihlasit sa na termin" dole.
        with app.collect_operations() as ops:
            app.d.pridatButton.click()

        # Otvori sa novy dialog.
        app.awaited_open_dialog(ops)

    def __select_predmet_row(self, app, predmet_key):
        self.__vyber_oba_semestre(app)

        (skratka,) = decode_key(predmet_key)
        index = find_row(app.d.predmetyTable.all_rows(), skratka=skratka)
        app.d.predmetyTable.select(index)

        return app.d.predmetyTable.all_rows()[index]

    def get_vypisane_terminy_predmetu(self, zapisny_list_key, predmet_key):
        app = self._open_terminy_hodnotenia_app(zapisny_list_key)

        predmet_row = self.__select_predmet_row(app, predmet_key)
        self.__open_vyber_terminu_dialog(app)

        studium_key, akademicky_rok = decode_key(zapisny_list_key)


        result = [Termin(datum=row['dat'],