"""Runnable sample script. Demonstrates intraday timed bars request and responses processing.
"""

import logging
import threading

from tabulate import tabulate
from win32com.client import Dispatch

import CELEnvironment

# region Settings

# Replace it with enabled symbol
SYMBOL = "CLE"

# Logging settings: specify log level and log format here.
# Set log level to DEBUG to see more internal messages.
# See more information about logging lib usage: https://docs.python.org/3/howto/logging-cookbook.html
CELEnvironment.LOG_LEVEL = logging.INFO
# CELEnvironment.LOG_FORMAT = ...

# CELEnvironment.SinkInternal uses own LOG_LEVEL (which is inherited from the global one) and own LOG_FORMAT.
# You can redefine them as follows:
# CELEnvironment.SinkInternal.LOG_LEVEL = logging.DEBUG
# CELEnvironment.SinkInternal.LOG_FORMAT = ...

LOG = CELEnvironment.GetLogger()

# endregion


class IntradayBarsRequestSample(CELEnvironment.CELSinkBase):
    def __init__(self):
        self.eventDone = threading.Event()
        self.celEnvironment = None

    def Init(self, celEnvironment):
        self.celEnvironment = celEnvironment

    def Start(self):
        """Script actions."""
        LOG.info("Create timed bars request")
        request = Dispatch(self.celEnvironment.cqgCEL.CreateTimedBarsRequest())
        request.Symbol = SYMBOL
        request.RangeStart = 0
        request.RangeEnd = -10
        request.IntradayPeriod = 1  # minute
        request.UpdatesEnabled = True
        request.SessionsFilter = "all"

        LOG.info("Starting the request...")
        self.celEnvironment.cqgCEL.RequestTimedBars(request)
        self.eventDone.wait(10)

        LOG.info("Done!")

    # region Event handlers

    def OnDataError(self, cqgError, errorDescription):
        if cqgError is not None:
            error = Dispatch(cqgError)
            LOG.error("OnDataError: Code: %d. Description: '%s'.", error.Code, error.Description)

        self.eventDone.set()

    def OnTimedBarsResolved(self, cqgTimedBars, cqgError):
        if cqgError is not None:
            error = Dispatch(cqgError)
            LOG.error("OnTimedBarsResolved: Code: %d. Description: '%s'.", error.Code, error.Description)
            self.eventDone.set()
            return

        bars = Dispatch(cqgTimedBars)
        LOG.info("OnTimedBarsResolved: Bars count: %d. Bars:\n%s", bars.Count, self.formatBars(bars))

    def OnTimedBarsAdded(self, cqgTimedBars):
        bars = Dispatch(cqgTimedBars)
        LOG.info("OnTimedBarsAdded: Bars count: %d. Bars:\n%s", bars.Count, self.formatBars(bars))

    def OnTimedBarsUpdated(self, cqgTimedBars, index):
        bars = Dispatch(cqgTimedBars)
        LOG.info("OnTimedBarsUpdated: Updated bar index: %d. Bars:\n%s", index, self.formatBars(bars))

    def OnTimedBarsRemoved(self, cqgTimedBars, index):
        bars = Dispatch(cqgTimedBars)
        LOG.info(
            "OnTimedBarsRemoved: Removed bar index: %d. Bars count: %d. Bars:\n%s",
            index,
            bars.Count,
            self.formatBars(bars),
        )

    # endregion

    # region Utility methods

    def validateValue(self, value):
        """Returns value if it is valid, otherwise "N/A" string.
        WARN: should be called only from callbacks.

        Args:
            value (datetime | float | int): Value to check.
        """
        return value if self.celEnvironment.cqgCELForCallback.IsValid(value) else "N/A"

    def getBarRow(self, bar, index: int) -> list:
        """Returns bar values as list.
        WARN: should be called only from callbacks.

        Args:
            bar (CQGTimedBar): Dispatched CQGTimedBar object.
            index (int): Bar index.
        """
        res = [
            index,
            bar.Timestamp,
            bar.Open,
            bar.High,
            bar.Low,
            bar.Close,
            bar.AskVolumeFractional,
            bar.AskFlagVolume,
            bar.BidVolumeFractional,
            bar.BidFlagVolume,
        ]

        return [self.validateValue(value) for value in res]

    def formatBars(self, bars) -> str:
        """Formats bars into string as table.
        WARN: should be called only from callbacks.

        Args:
            bars (CQGTimedBars): Dispatched CQG timed bars collection.

        Returns:
            str: Formatted string.
        """
        return tabulate(
            [self.getBarRow(bar, index) for bar, index in zip(bars, range(bars.Count))],
            headers=[
                "Index",
                "Timestamp",
                "Open",
                "High",
                "Low",
                "Close",
                "AskVol",
                "AskFlagVol",
                "BidVol",
                "BidFlagVol",
            ],
        )

    # endregion


if __name__ == "__main__":
    CELEnvironment.Start(IntradayBarsRequestSample)
