"""The sample demonstrates how to place a "manual" order and an order with MiFID algo id."""

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 = "DD"

# Events waiting time
TIMEOUT = 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 OrderPlacingSample(CELEnvironment.CELSinkBase):
    def __init__(self):
        self.account = None
        self.instrument = None
        self.eventGatewayIsUp = threading.Event()
        self.eventGatewayIsDown = threading.Event()
        self.eventAccountIsReady = threading.Event()
        self.eventInstrumentIsReady = threading.Event()
        self.eventOrderPlaced = threading.Event()
        self.eventOrderCancelled = threading.Event()

    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 coming...")
        AssertMessage(self.eventAccountIsReady.wait(TIMEOUT), "Accounts coming timeout!")

        LOG.info("Select the first account")
        accounts = win32com.client.Dispatch(self.celEnvironment.cqgCEL.Accounts)
        self.account = win32com.client.Dispatch(accounts.ItemByIndex(0))

        LOG.info("Account name: %s", self.account.GWAccountName)

        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!")

        instrument = win32com.client.Dispatch(self.instrument)

        bestBid = instrument.Bid
        AssertMessage(bestBid.IsValid, "Error! Can't set an order price due to invalid BBA")

        LOG.info("Best bid value is %f", bestBid.Price)
        LOG.info("Calculating a price that above the market by 10 tick sizes")
        buyOrderPrice = bestBid.Price - 10 * instrument.TickSize
        LOG.info("It is %f", buyOrderPrice)

        LOG.info("Create buy limit order")
        manualOrder = win32com.client.Dispatch(
            self.celEnvironment.cqgCEL.CreateOrder(
                constants.otLimit, self.instrument, self.account, 1, constants.osdUndefined, buyOrderPrice
            )
        )

        LOG.info("Mark the order as manually placed")
        manualOrder.Manual = True

        LOG.info("Place 'manual' order")
        manualOrder.Place()
        LOG.info("Waiting for order placing...")
        AssertMessage(self.eventOrderPlaced.wait(TIMEOUT), "Order placing timeout!")
        self.eventOrderPlaced.clear()

        LOG.info("Create another buy limit order")
        automatedOrder = win32com.client.Dispatch(
            self.celEnvironment.cqgCEL.CreateOrder(
                constants.otLimit, self.instrument, self.account, 1, constants.osdUndefined, buyOrderPrice
            )
        )

        automatedOrder.MiFIDAlgorithmID = "My-Own-MiFID-Algo-ID"
        LOG.info("Set MiFID algorithm ID to %s", automatedOrder.MiFIDAlgorithmID)
        LOG.info("Setting MiFID algo ID automatically makes it 'non-manual'. Is it manual? : %s", automatedOrder.Manual)

        LOG.info("Place the 'non-manual' aka 'algo' order")
        automatedOrder.Place()
        LOG.info("Waiting for order placing...")
        AssertMessage(self.eventOrderPlaced.wait(TIMEOUT), "Order placing timeout!")

        LOG.info("Cancel 'non-manual' aka 'algo' order")
        automatedOrder.Cancel()  # the operation is non-manual since the order was placed as non-manual
        LOG.info("Waiting for order cancellation...")
        AssertMessage(self.eventOrderCancelled.wait(TIMEOUT), "Order cancellation timeout!")
        self.eventOrderCancelled.clear()

        LOG.info("Cancel 'manual' order as 'non-manual' aka 'algo cancellation'")
        orderCancel = win32com.client.Dispatch(manualOrder.PrepareCancel())
        orderCancel.Manual = False
        orderCancel.MiFIDAlgorithmID = "some-algo-id"
        orderCancel.ForceClientExecutionDecision = True
        manualOrder.CancelEx(orderCancel)
        AssertMessage(self.eventOrderCancelled.wait(TIMEOUT), "Order cancellation 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):
        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: Code: %d Description: %s", error.Code, error.Description)
            return

        self.instrument = cqgInstrument
        LOG.info("Symbol %s is resolved!", symbol)
        self.eventInstrumentIsReady.set()

    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 = win32com.client.Dispatch(dispatchedOrder.Properties)
        gwStatus = properties(constants.opGWStatus)
        quantity = properties(constants.opQuantity)
        instrument = properties(constants.opInstrumentName)
        mifidId = properties(constants.opMiFIDAlgorithmID)
        isManual = properties(constants.opManual)

        LOG.info(
            "OnOrderChanged: change type: %d; GW status: %d; Quantity: %d; Instrument: %s; MiFID Algo ID: %s;"
            "Is manual: %s",
            changeType,
            gwStatus,
            quantity,
            instrument,
            mifidId,
            isManual,
        )

        if changeType != constants.ctChanged:
            return

        if gwStatus.Value == constants.osInOrderBook:
            LOG.info("Order is placed!")
            self.eventOrderPlaced.set()
        elif gwStatus.Value == constants.osCanceled:
            LOG.info("Order is cancelled!")
            self.eventOrderCancelled.set()


# If CQGCEL.APIConfiguration must be customized then do it in this function
def CustomAPIConfiguration(APIConfiguration):
    APIConfiguration.SkipAlgorithmIDCheckForManualOrders = True


if __name__ == "__main__":
    CELEnvironment.Start(OrderPlacingSample, CustomAPIConfiguration)
