import logging
import platform
import sys
import time

from asts_client import Native, Entity


def aggregate_orderbook(entity: Entity, records: dict):
    """Специальная обработка для запросов котировок"""
    board_name = entity.output.sec_board_name
    paper_name = entity.output.sec_code_name
    current_board = None
    current_paper = None
    current_lines = []
    for record in records:
        b = None
        p = None
        board_changed = False
        paper_changed = False
        if board_name is not None:
            b = record.pop(board_name, None)
            if b is not None:
                board_changed = current_board is None or b != current_board
        if paper_name is not None:
            p = record.pop(paper_name, None)
            if p is not None:
                paper_changed = current_paper is None or p != current_paper
        if board_changed or paper_changed:
            if current_board is not None or current_paper is not None:
                book = {}
                if board_name is not None:
                    book[board_name] = current_board
                if paper_name is not None:
                    book[paper_name] = current_paper
                if current_lines:
                    book["ENTRIES"] = current_lines
                yield book
            current_board = b
            current_paper = p
            current_lines = []
            if record:
                current_lines.append(record)
        else:
            current_lines.append(record)
    # Last orderbook after having scanned all records
    if current_board is not None or current_paper is not None:
        book = {}
        if board_name is not None:
            book[board_name] = current_board
        if paper_name is not None:
            book[paper_name] = current_paper
        if current_lines:
            book["ENTRIES"] = current_lines
        yield book


if __name__ == '__main__':
    logging.basicConfig(stream=sys.stdout, level=logging.DEBUG,
                        format='%(asctime)s %(levelname)-8s %(name)-16s %(message)s')

    is_windows = platform.system().lower() == "windows"
    is_64bits = sys.maxsize >= 2 ** 32  # platform.architecture() == 'AMD64'

    # Здесь указак путь к размещению native-библиотек.
    # В примере используется прямое подключение, для шлюзового
    # подключения 'embedded' следует заменить на 'mtesrl'.
    # В demo-примере путь задан относительно расположения
    # кода примера в дистрибутиве шлюза ASTS - откорректируйте
    # в соответствии с собственными реалиями.
    path = "../../embedded/"
    if is_windows:
        path += "win64" if is_64bits else "win32"
    else:
        # Note: предполагается, что libmtesrl...tar.gz
        # уже была распакована (tar -xzf libmtesrl....tar.gx)
        path += "linux"
    native = Native.load(path)
    logging.info(f"MTESRL version: %s", native.version())

    # Указаны параметры подключения к тестовой среде MOEX
    params = {
        "SERVER": "INET_GATEWAY",
        "SERVICE": "inet_gateway",
        "BROADCAST": "91.208.232.101",
        "INTERFACE": "IFCBroker45",
        "USERID": "MU0000900002",
        "LANGUAGE": "English",
        "LOGGING": "2,0",
        "LOGFOLDER": "."
    }

    # Подключиемся к MOEX с указанными параметрами
    client = native.connect(params)
    try:
        # При успехе - информация о подключении
        logging.info("Status: %d", client.status())
        logging.info("Language: %s", client.language)
        logging.info("Market: %s", client.market)

        # В демо-целях - ограничиваемся только одним режимом торгов
        client.select({'TQBR'})

        # Хранилища данных (это только один из возможных вариантов хранения данных,
        # далеко не самый оптимальный, используется в демонстрационных целях.

        # Инструменты: словарь. Ключ: пара (tuple) из кода режима торгов и бумаги.
        # Значение: словарь из наименование поля и его значения.
        securities = {}

        # Котировки: словарь. Ключ: пара (tuple) из кода режима торгов и бумаги.
        # Значение: список котировок, где элемент списка - так же словарь из
        # наименования поля и его значения.
        orderbooks = {}

        logging.debug("Loading SECURITIES")
        # Открываем запрос "Инструменты" (с полным получением данных).
        # На выходе - пара (tuple) из объекта-дескриптора открытого запроса
        # и парсера - объекта для итерации для полученных данных.
        s_stream, parser = client.open("SECURITIES", {}, True)
        # Получаем итератор полученных записей
        records = parser.parse()
        for record in records:
            # Составляем первичный ключ хранилища
            ticket = (record.get('SECBOARD'), record.get('SECCODE'))
            # Ищем запись в хранилище, если не нашли - используем полученное
            security = securities.setdefault(ticket, record)
            if security is not record:
                # Если запись в хранилище уже была - значит
                # в обработку поступило инкрементальное обновление
                for k, v in record.items():
                    security[k] = v

        logging.debug("Loading EXT_ORDERBOOK")
        o_stream, parser = client.open("EXT_ORDERBOOK", {}, False)
        records = parser.parse()
        for record in aggregate_orderbook(o_stream.entity, records):
            ticket = (record.get('SECBOARD'), record.get('SECCODE'))
            orderbooks[ticket] = record.get("ENTRIES")
            logging.debug(record)

        for i in range(3):
            time.sleep(1)
            logging.debug("Refresh cycle #%d", i)
            for stream, parser in client.refresh(s_stream, o_stream):
                if stream is s_stream:
                    for record in parser.parse():
                        ticket = (record.pop('SECBOARD'), record.pop('SECCODE'))
                        security = securities.get(ticket)
                        if security is not None:
                            # Инкрементальное обновление
                            for k, v in record.items():
                                security[k] = v
                        logging.debug("Updated %s", str(security))
                if stream is o_stream:
                    for record in aggregate_orderbook(o_stream.entity, parser.parse()):
                        ticket = (record.pop('SECBOARD'), record.pop('SECCODE'))
                        entries = record.pop("ENTRIES", None)
                        if entries:
                            orderbooks.setdefault(ticket, entries)
                        else:
                            orderbooks.setdefault(ticket, []).clear()
                        logging.debug("Updated %s: %s", str(ticket), str(orderbooks.get(ticket)))

        # Just for demo, will fail
        params = {
            "ACCOUNT": "ABCDE",
            "SECBOARD": "SOME",
            "SECCODE": "UNKNOWN",
            "BUYSELL": "B",
            "MKTLIMIT": "L",
            "SPLITFLAG": "S",
            "IMMCANCEL": " ",
            "PRICE": 5.65,
            "QUANTITY": 1000
        }
        success, message = client.execute("ORDER", params)
        logging.debug("Executed %s: %s", success, message)

    finally:
        client.close()
