"""The sample demonstrates how to place an order with subscription to meta data only."""

import logging
import threading
import time

import win32com.client
from win32com.client import constants

import CELEnvironment
from CELEnvironment import AssertMessage

#region Settings

# Replace it with enabled symbol
SYMBOL = "EP"

# Replace with real prices:
#   - Set LMT_PRICE to 0 and some valid STP_PRICE to place trailing stop order;
#   - Set STP_PRICE to 0 and some valid LMT_PRICE to place trailing limit order;
#   - Set valid STP_PRICE and LMT_PRICE prices to place trailing stop-limit order;
LMT_PRICE = 5000
STP_PRICE = 4000

# Replace with real trailing offset
TRAILING_OFFSET = 50.

# Replace with real preferred trailing peg, value is used only for LMT order.
PREFERRED_TRAILING_PEG = constants.otpLastTrade

# Replace with real order quantity (negative values for sell)
ORDER_QUANTITY = -1

# Events waiting time (seconds)
TIMEOUT = 15

# 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


def GetOrderType() -> int:
    """Returns order type depending on LMT_PRICE and STP_PRICE values.

    Returns:
        int: Order type.
    """
    orderType = constants.otUndefined
    if LMT_PRICE != 0 and STP_PRICE != 0:
        orderType = constants.otStopLimit
    elif LMT_PRICE != 0:
        orderType = constants.otLimit
    elif STP_PRICE != 0:
        orderType = constants.otStop

    AssertMessage(orderType != constants.otUndefined, "Price values are invalid!")
    return orderType


class TrailingOrderPlacing(CELEnvironment.CELSinkBase):
    """Sets trailing STP/LMT/STL orders.
    """
    def __init__(self):
        self.account = None
        self.instrument = None
        self.eventGwUp = threading.Event()
        self.eventAccountsReady = threading.Event()
        self.eventInstrumentReady = threading.Event()
        self.orderGuid = ""

    def Init(self, celEnvironment):
        self.celEnvironment = celEnvironment

    def Start(self):
        """Main sample script.
        """
        LOG.info("Waiting for GW connection...")
        AssertMessage(self.eventGwUp.wait(TIMEOUT), "GW connection timeout!")

        self.celEnvironment.cqgCEL.AccountSubscriptionLevel = constants.aslAccountUpdatesAndOrders
        LOG.info("Waiting for accounts coming...")
        AssertMessage(self.eventAccountsReady.wait(TIMEOUT), "Accounts coming timeout!")

        # to simplify sample - take first account
        accounts = win32com.client.Dispatch(self.celEnvironment.cqgCEL.Accounts)
        self.account = win32com.client.Dispatch(accounts.ItemByIndex(0))

        request = win32com.client.Dispatch(self.celEnvironment.cqgCEL.CreateInstrumentRequest())
        request.Symbol = SYMBOL
        self.celEnvironment.cqgCEL.SubscribeNewInstrument(request)
        LOG.info("%s instrument waiting for subscription...", SYMBOL)
        AssertMessage(self.eventInstrumentReady.wait(TIMEOUT), "Instrument resolution timeout!")

        orderType = GetOrderType()
        order = win32com.client.Dispatch(self.celEnvironment.cqgCEL.CreateOrder(orderType, self.instrument,
                                                                                self.account, ORDER_QUANTITY,
                                                                                constants.osdUndefined, LMT_PRICE,
                                                                                STP_PRICE))
        properties = win32com.client.Dispatch(order.Properties)
        # save guid to watch only after our order
        self.orderGuid = properties(constants.opGUID).Value

        # Configure trailing properties
        properties(constants.opExecutionInstructions).Value = constants.oeiTrailing
        properties(constants.opTrailingOffset).Value = TRAILING_OFFSET
        if orderType == constants.otLimit:
            # property cannot be set for stop-orders
            properties(constants.opPreferredTrailingPeg).Value = PREFERRED_TRAILING_PEG

        LOG.info("Placing order...")
        order.Place()

        # just wait some comparably long time to see order updates
        time.sleep(TIMEOUT * 10)

        LOG.info("Finished.")

    def OnGWConnectionStatusChanged(self, newStatus):
        """GWConnectionStatusChanged CEL event handler. GW authentication is required for this sample.
        See documentation for detailed description.
        """
        if newStatus == constants.csConnectionUp:
            LOG.info("GW connection is UP!")
            self.eventGwUp.set()

    def OnAccountChanged(self, changeType, cqgAccount, cqgPosition):
        """AccountChanged CEL event handler. Account is required to place order.
        See documentation for detailed description.
        """
        if changeType != constants.actAccountsReloaded:
            return

        LOG.info("Accounts are ready!")
        self.eventAccountsReady.set()

    def OnInstrumentResolved(self, symbol, cqgInstrument, cqgError):
        """InstrumentResolved CEL event handler. Instrument is required to place order.
        See documentation for detailed description.
        """
        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("Instrument %s is resolved!", symbol)
        self.eventInstrumentReady.set()

    def OnOrderChanged(self, changeType, cqgOrder, oldProperties, cqgFill, cqgError):
        """OrderChanged CEL event handler. Allows to track order status.
        See documentation for detailed description.
        """
        if cqgError is not None:
            error = win32com.client.Dispatch(cqgError)
            LOG.error("OnInstrumentResolved error: Error code: %d Description: %s", error.Code, error.Description)
            return

        dispatchedOrder = win32com.client.Dispatch(cqgOrder)
        properties = win32com.client.Dispatch(dispatchedOrder.Properties)

        if self.orderGuid != properties(constants.opGUID).Value:
            # this is not our order, skip event
            return

        gwStatus = properties(constants.opGWStatus).Value
        quantity = properties(constants.opQuantityFractional).Value
        instrument = properties(constants.opInstrumentName).Value
        limitPrice = properties(constants.opLimitPrice).Value if properties(constants.opLimitPrice) else None
        stopPrice = properties(constants.opStopPrice).Value if properties(constants.opStopPrice) else None
        trailingLimitPrice =\
            properties(constants.opTrailingLimitPrice).Value if properties(constants.opTrailingLimitPrice) else None
        trailingStopPrice =\
            properties(constants.opTrailingStopPrice).Value if properties(constants.opTrailingStopPrice) else None

        LOG.info(
            "%s order changed: change type: %d; GW status: %d; Qty: %s; LMT Price: %f; STP Price: %f; "
            "Trailing offset: %f; Trailing LMT Price: %f; Trailing STP Price: %f",
            instrument,
            changeType,
            gwStatus,
            quantity,
            limitPrice,
            stopPrice,
            TRAILING_OFFSET,
            trailingLimitPrice,
            trailingStopPrice
        )


def CustomAPIConfiguration(APIConfiguration):
    """Utility function to set API configuration

    Args:
        APIConfiguration (CQGAPIConfiguration): COM object to set API-wide settings.
    """
    APIConfiguration.NewInstrumentMode = True


if __name__ == "__main__":
    CELEnvironment.Start(TrailingOrderPlacing, CustomAPIConfiguration)
