"""The sample demonstrates how to place an order with Good Till Date property set."""

import logging
import threading
from datetime import datetime, timedelta, timezone

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 GTDOrderPlacing(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()

    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("%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)

        bestBid = dispatchedInstrument.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 * dispatchedInstrument.TickSize
        LOG.info("It is %f", buyOrderPrice)

        LOG.info("Create Buy limit order")
        buyOrder = win32com.client.Dispatch(
            self.celEnvironment.cqgCEL.CreateOrder(constants.otLimit, self.instrument, self.account, 1,
                                                   constants.osdUndefined, buyOrderPrice))

        LOG.info("Set Good-Till-Date property of the order")
        buyOrder.DurationType = constants.odGoodTillDate
        properties = win32com.client.Dispatch(buyOrder.Properties)
        gtdTime = properties(constants.opGTDTime)

        dateGTD = datetime.now(timezone.utc) + timedelta(days=7)
        gtdTime.Value = dateGTD
        LOG.info("Good-Till-Date property is set to %s", dateGTD)

        LOG.info("Place order")
        buyOrder.Place()
        LOG.info("Waiting for order placing...")
        AssertMessage(self.eventOrderPlaced.wait(TIMEOUT), "Order placing 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: 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)
        gtd = properties(constants.opGTDTime)

        LOG.info("OnOrderChanged: change type: %d; GW status: %d; Quantity: %d; Instrument: %s GTD: %s",
                 changeType, gwStatus, quantity, instrument, gtd)

        if changeType != constants.ctChanged:
            return

        if gwStatus.Value == constants.osInOrderBook:
            LOG.info("Order is placed!")
            self.eventOrderPlaced.set()


# If CQGCEL.APIConfiguration must be customized then do it in this function
def CustomAPIConfiguration(APIConfiguration):
    LOG.info("Set UTC time zone. All time values passed to (received from) CQG API should correspond to this setting.")
    APIConfiguration.TimeZoneCode = constants.tzUTC


if __name__ == "__main__":
    CELEnvironment.Start(GTDOrderPlacing, CustomAPIConfiguration)
