"""
The sample requests orders for first available trading account account for specified instrument.

Packages requirements:
    - win32com
    - pandas
    - tabulate
"""


import logging
import threading
from datetime import datetime, timezone

import pandas as pd
import win32com.client
from win32com.client import constants

import CELEnvironment
from CELEnvironment import AssertMessage

#region Settings

# Replace with your own values
LOGIN = "change"        #< GW account login
PASSWORD = "change"     #< GW account password
INSTRUMENT = "F.US.ISP" #< symbol to query orders for

# Events waiting time in seconds
TIMEOUT = 20

# 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 QueryOrders(CELEnvironment.CELSinkBase):
    def __init__(self):
        """
        The constructor.
        """
        self.eventDone = threading.Event()  # used for most wait events
        self.eventGatewayIsDown = threading.Event()  # used for GW logoff event
        self.instrument = None  # CQGInstrument that we subscribe on

    def Init(self, celEnvironment):
        """
        Is called from CELEnvironment when CEL COM-object is ready.
        """
        self.celEnvironment = celEnvironment

    def Start(self):
        """
        Main sample logic.
        """
        LOG.info("Connecting to GW")
        self.celEnvironment.cqgCEL.GWLogon(LOGIN, PASSWORD)

        LOG.info("Waiting for GW connection...")
        AssertMessage(self.eventDone.wait(TIMEOUT), "GW connection timeout!")

        self.eventDone.clear()
        self.celEnvironment.cqgCEL.AccountSubscriptionLevel = constants.aslAccountUpdatesAndOrders
        LOG.info("Waiting for accounts coming...")
        AssertMessage(self.eventDone.wait(TIMEOUT), "Accounts coming timeout!")

        accounts = win32com.client.Dispatch(self.celEnvironment.cqgCEL.Accounts)
        account = win32com.client.Dispatch(accounts.ItemByIndex(0))
        AssertMessage(account, "Failed to load trading account.")

        LOG.info("Subscribing on instrument '%s'", INSTRUMENT)
        self.eventDone.clear()
        instrumentRequest = win32com.client.Dispatch(self.celEnvironment.cqgCEL.CreateInstrumentRequest())
        instrumentRequest.Symbol = INSTRUMENT
        self.celEnvironment.cqgCEL.SubscribeNewInstrument(instrumentRequest)
        AssertMessage(self.eventDone.wait(TIMEOUT), "Instrument subscription timeout!")
        AssertMessage(self.instrument, f"Failed to subscribe on '{INSTRUMENT}'")

        LOG.info("Starting Orders Query for '%s'", self.instrument.FullName)
        self.eventDone.clear()
        self.celEnvironment.cqgCEL.QueryOrders(account, self.instrument, constants.osfAll, datetime.now(timezone.utc))
        AssertMessage(self.eventDone.wait(TIMEOUT), "Orders Query timeout!")

        LOG.info("Logoff from GW")
        self.eventGatewayIsDown.clear()
        self.celEnvironment.cqgCEL.GWLogoff()
        AssertMessage(self.eventGatewayIsDown.wait(TIMEOUT), "GW disconnection timeout!")

        LOG.info("Done!")

    def OnGWConnectionStatusChanged(self, newStatus):
        """
        See https://partners.cqg.com/sites/default/files/docs/CQGAPI_4.0R/CQG~CQGCEL~GWConnectionStatusChanged_EV.html
        """
        if newStatus == constants.csConnectionUp:
            LOG.info("GW connection is UP!")
            self.eventDone.set()
        if newStatus == constants.csConnectionDown:
            LOG.info("GW connection is DOWN!")
            self.eventGatewayIsDown.set()

    def OnAccountChanged(self, changeType, cqgAccount, cqgPosition):
        """
        See https://partners.cqg.com/sites/default/files/docs/CQGAPI_4.0R/CQG~CQGCEL~AccountChanged_EV.html
        """
        if changeType != constants.actAccountsReloaded:
            return

        LOG.info("Accounts are ready!")
        self.eventDone.set()

    def OnInstrumentResolved(self, symbol, cqgInstrument, cqgError):
        """
        See https://partners.cqg.com/sites/default/files/docs/CQGAPI_4.0R/CQG~CQGCEL~InstrumentResolved_EV.html
        """
        if cqgError is not None:
            error = win32com.client.Dispatch(cqgError)
            LOG.error("OnInstrumentResolved error: Error code: %d Description: %s",
                      error.Code, error.Description)
            return

        self.instrument = win32com.client.Dispatch(cqgInstrument)
        LOG.info("Symbol '%s' is resolved!", self.instrument.FullName)
        self.eventDone.set()

    def OnQueryProgress(self, cqgOrdersQuery, cqgError):
        """
        See https://partners.cqg.com/sites/default/files/docs/CQGAPI_4.0R/CQG~CQGCEL~OnQueryProgress_EV.html
        """
        if cqgError is not None:
            error = win32com.client.Dispatch(cqgError)
            LOG.error("OnQueryProgress error: Error code: %d Description: %s",
                      error.Code, error.Description)
            return

        LOG.info("Orders query updated.")
        ordersQuery = win32com.client.Dispatch(cqgOrdersQuery)
        newChunk = []
        for order in ordersQuery.LastChunk:
            orderProps = win32com.client.Dispatch(order.Properties)
            newChunk.append([order.GWOrderID, order.Quantity, orderProps(constants.opAverageFillPrice),
                             orderProps(constants.opFilledQuantity), orderProps(constants.opRemainingQuantity)])

        if newChunk:
            # use pandas DataFrame for pretty output
            df = pd.DataFrame(newChunk, columns=["Order ID", "Qty", "Avg Fill Price", "Filled Qty", "Remaining Qty"])
            LOG.info("New chunk for '%s' arrived:\n\n%s\n", self.instrument.FullName, df.to_markdown())

        if ordersQuery.Status == constants.rsInProgress:
            LOG.info("Waiting for next chunk...")
        elif ordersQuery.Status == constants.rsSuccess:
            LOG.info("Final chunk processed for this query.")
            self.eventDone.set()


def CustomAPIConfiguration(APIConfiguration):
    """
    Helper function to set any APIConfiguration properties.
    See https://partners.cqg.com/sites/default/files/docs/CQGAPI_4.0R/CQG~CQGAPIConfig.html
    """
    APIConfiguration.NewInstrumentMode = True


if __name__ == "__main__":
    CELEnvironment.Start(QueryOrders, CustomAPIConfiguration)
