"""The sample demonstrates how to place an order and observes it in a DOM orders queue"""

import logging
import threading

import win32com.client
from win32com.client import constants

import CELEnvironment
from CELEnvironment import AssertMessage

#region Settings

# Replace it with real login before run
LOGIN = "your_gw_login"
PASSWORD = "your_gw_password"

# Replace it with enabled symbol
SYMBOL = "EP"

# Events waiting time
TIMEOUT = 30
DOM_BOOK_LIMIT = 10
# Observing the order in detailed DOM just in case it is in the limit boundary
DETAILED_DOM_LIMIT = 10

# 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 OrderInDetailedDOM(CELEnvironment.CELSinkBase):
    def __init__(self):
        self.instrument = None
        self.eventGatewayIsUp = threading.Event()
        self.eventGatewayIsDown = threading.Event()
        self.eventAccountIsReady = threading.Event()
        self.eventInstrumentIsReady = threading.Event()
        self.eventOrderInFinalState = threading.Event()
        self.execSystemOrderID = "-1"
        self.orderPrice = 0
        self.orderNumberInLine = -1

    def Init(self, celEnvironment):
        self.celEnvironment = celEnvironment

    def Start(self):
        LOG.info("Connecting to GW")
        self.celEnvironment.cqgCEL.GWLogon(LOGIN, PASSWORD)

        LOG.info("Waiting for GW connection...")
        AssertMessage(self.eventGatewayIsUp.wait(TIMEOUT), "GW connection timeout!")

        self.celEnvironment.cqgCEL.AccountSubscriptionLevel = constants.aslAccountUpdatesAndOrders
        LOG.info("Waiting for accounts...")
        AssertMessage(self.eventAccountIsReady.wait(TIMEOUT), "Accounts coming timeout!")

        LOG.info("Select the first account")
        accounts = self.celEnvironment.cqgCEL.Accounts
        account = accounts.ItemByIndex(0)

        LOG.info("%s instrument requesting...", SYMBOL)
        self.celEnvironment.cqgCEL.NewInstrument(SYMBOL)
        LOG.info("%s instrument waiting...", SYMBOL)
        AssertMessage(self.eventInstrumentIsReady.wait(TIMEOUT), "Instrument resolution timeout!")

        dispatchedInstrument = win32com.client.Dispatch(self.instrument)

        bestAsk = dispatchedInstrument.Ask
        AssertMessage(bestAsk.IsValid, "Error! Can't set an order price due to invalid BBA")

        LOG.info("Calculating a price that above the market by 3 tick sizes...")
        self.orderPrice = bestAsk.Price + 3 * dispatchedInstrument.TickSize

        LOG.info("Create a sell limit order")
        sellOrder = self.celEnvironment.cqgCEL.CreateOrder(constants.otLimit, self.instrument, account, -1,
                                                           constants.osdUndefined,
                                                           self.orderPrice)

        LOG.info("Place order")
        sellOrder.Place()

        LOG.info("Waiting for the order final state (fill or cancel) and observing the number in line changes...")
        self.eventOrderInFinalState.wait(100)

        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):
        if newStatus == constants.csConnectionUp:
            LOG.info("GW connection is UP!")
            self.eventGatewayIsUp.set()
        if newStatus == constants.csConnectionDown:
            LOG.info("GW connection is DOWN!")
            self.eventGatewayIsDown.set()

    def OnAccountChanged(self, changeType, cqgAccount, cqgPosition):
        if changeType != constants.actAccountsReloaded:
            return

        LOG.info("Accounts are ready!")
        self.eventAccountIsReady.set()

    def OnInstrumentResolved(self, symbol, cqgInstrument, cqgError):
        if cqgError is not None:
            error = win32com.client.Dispatch(cqgError)
            LOG.error("OnInstrumentResolved error: Error code: %d Description: %s", error.Code, error.Description)
            return

        LOG.info("Instrument %s is resolved!", symbol)

        self.instrument = cqgInstrument
        instrument = win32com.client.Dispatch(cqgInstrument)
        LOG.info("OnInstrumentResolved: Symbol: %s Instrument full name: %s", symbol, instrument.FullName)
        LOG.info("Set data subscription level for DOM data receiving")
        instrument.DataSubscriptionLevel = win32com.client.constants.dsQuotesAndDOM
        LOG.info("Set limit to DOM depth")
        instrument.DOMBookLimit = DOM_BOOK_LIMIT
        LOG.info("Set limit to Detailed DOM depth")
        instrument.IncludeDetailedDOM = True
        instrument.DetailedDOMLimit = DETAILED_DOM_LIMIT

        self.eventInstrumentIsReady.set()

    def OnInstrumentDOMChanged(self, cqgInstrument, prevAsks, prevBids):
        instrument = win32com.client.Dispatch(cqgInstrument)
        # looking for placed order in a DOM orders queue
        for ask in instrument.DOMAsks:
            if ask.IsValid and ask.Price == self.orderPrice:
                idx = 0
                for domOrder in ask.OrdersQueue:
                    if domOrder.ID == self.execSystemOrderID and idx != self.orderNumberInLine:
                        LOG.info("     order %s for price %f has number %d in line", domOrder.ID, ask.Price, idx)
                        self.orderNumberInLine = idx
                        return
                    idx += 1

    def OnOrderChanged(self, changeType, cqgOrder, oldProperties, cqgFill, cqgError):
        if cqgError is not None:
            error = win32com.client.Dispatch(cqgError)
            LOG.error("OnOrderChanged error: Code: %d Description: %s", error.Code, error.Description)
            return

        dispatchedOrder = win32com.client.Dispatch(cqgOrder)
        properties = dispatchedOrder.Properties
        gwStatus = properties(constants.opGWStatus).Value
        instrument = properties(constants.opInstrumentName).Value
        limitPrice = properties(constants.opLimitPrice).Value
        execSystemOrderID = properties(constants.opExecSystemOrderID)
        if execSystemOrderID:
            # property may be not filled yet
            self.execSystemOrderID = properties(constants.opExecSystemOrderID).Value

        LOG.info(
            "OnOrderChanged: GW status: %d; Instrument: %s; Price: %f; ExecSystemOrderID: %s;",
            gwStatus,
            instrument,
            limitPrice,
            self.execSystemOrderID
        )

        if gwStatus in (constants.osFilled, constants.osCanceled):
            self.eventOrderInFinalState.set()


def CustomAPIConfiguration(APIConfiguration):
    """
    If CQGCEL.APIConfiguration should be customized then do it in this function
    """
    APIConfiguration.NewInstrumentMode = False
    APIConfiguration.DOMUpdatesMode = win32com.client.constants.domUMSnapshot
    APIConfiguration.DOMUpdatesPeriod = 1000


if __name__ == "__main__":
    CELEnvironment.Start(OrderInDetailedDOM, CustomAPIConfiguration)
