"""Sample demonstrates how to create new QFormula, place strategy order with it and then delete it"""

import logging
import threading

import win32com.client
from win32com.client import constants

import CELEnvironment
from CELEnvironment import AssertMessage

#region Settings

# Replace with any valid values
QFORMULA_NAME = "AwesomeStrategy"  # < QFormula name w/o prefix "Q."
QFORMULA_EXPRESSION = "UDS(-UEP?1+UEP?2, CalC)"
QFORMULA_NUMBER = 11  # < values <=0 are considered as no-number
LOGIN = "your_gw_login"
PASSWORD = "your_gw_password"
ACCOUNT_NAME = "your_gw_account_name"  # < GW account with enabled UDS

# 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 UDSviaQFormula(CELEnvironment.CELSinkBase):
    def __init__(self):
        self.celEnvironment = None
        self.eventDone = threading.Event()
        self.eventGWUp = threading.Event()
        self.eventGWDown = threading.Event()

    def Init(self, celEnvironment):
        self.celEnvironment = celEnvironment

    def Start(self):
        """Main sample script.
        """
        LOG.info("Create new QFormula '%s'", QFORMULA_NAME)
        self.celEnvironment.cqgCEL.CreateQFormula(QFORMULA_NAME, QFORMULA_EXPRESSION, QFORMULA_NUMBER)
        AssertMessage(self.eventDone.wait(TIMEOUT), "QFormula creation timeout")
        self.eventDone.clear()

        LOG.info("Connecting to GW")
        self.celEnvironment.cqgCEL.GWLogon(LOGIN, PASSWORD)
        AssertMessage(self.eventGWUp.wait(TIMEOUT), "GW login timeout")

        LOG.info("Request accounts")
        self.celEnvironment.cqgCEL.AccountSubscriptionLevel = constants.aslAccountUpdatesAndOrders
        AssertMessage(self.eventDone.wait(TIMEOUT), "Accounts coming timeout!")
        self.eventDone.clear()
        account = self.__find_account(ACCOUNT_NAME)

        LOG.info("Define UDS via new QFormula")
        strategy = win32com.client.Dispatch(self.celEnvironment.cqgCEL.DefineStrategyEx(f"Q.{QFORMULA_NAME}", account))
        AssertMessage(self.eventDone.wait(TIMEOUT), "Strategy resolution timeout")
        self.eventDone.clear()

        LOG.info("Place strategy order")
        order = win32com.client.Dispatch(self.celEnvironment.cqgCEL.CreateStrategyOrder(constants.otMarket, strategy,
                                                                                        account, None, 1))
        order.Place()
        AssertMessage(self.eventDone.wait(TIMEOUT), "Strategy order placing timeout")
        self.eventDone.clear()

        LOG.info("Logoff from GW")
        self.eventGWDown.clear()
        self.celEnvironment.cqgCEL.GWLogoff()
        AssertMessage(self.eventGWDown.wait(TIMEOUT), "GW disconnection timeout!")

        LOG.info("Delete created QFormula")
        self.celEnvironment.cqgCEL.DeleteQFormula(QFORMULA_NAME)
        AssertMessage(self.eventDone.wait(TIMEOUT), "QFormula deletion timeout")
        self.eventDone.clear()

        LOG.info("Verify that QFormula is deleted")
        self.celEnvironment.cqgCEL.RequestQFormulaDefinition(QFORMULA_NAME)
        AssertMessage(self.eventDone.wait(TIMEOUT), "Request QFormula definition timeout")

        LOG.info("Done!")

#region CQGCEL events handlers
    def OnQFormulaCreated(self, name, cqgQFormulaDefinition, cqgError):
        if cqgError is not None:
            error = win32com.client.Dispatch(cqgError)
            LOG.error("Failed to create QFormula with specified parameters. Error: %d '%s'",
                      error.Code, error.Description)
            return

        definition = win32com.client.Dispatch(cqgQFormulaDefinition)
        LOG.info("QFormula '%s' is created.", definition.Name)
        self.eventDone.set()

    def OnGWConnectionStatusChanged(self, newStatus):
        if newStatus == constants.csConnectionUp:
            LOG.info("GW connection is UP!")
            self.eventGWUp.set()
        if newStatus == constants.csConnectionDown:
            LOG.info("GW connection is DOWN!")
            self.eventGWDown.set()

    def OnAccountChanged(self, changeType, cqgAccount, cqgPosition):
        if changeType != constants.actAccountsReloaded:
            return

        LOG.info("Accounts are ready!")
        self.eventDone.set()

    def OnStrategyDefinitionProgress(self, cqgDefinition, cqgError):
        if cqgError is not None:
            error = win32com.client.Dispatch(cqgError)
            LOG.error("OnStrategyDefinitionProgress error: Code: %d Description: %s",
                      error.Code, error.Description)
            return

        dispatchedDefinition = win32com.client.Dispatch(cqgDefinition)
        LOG.info("OnStrategyDefinitionProgress: requested symbol: '%s' GW symbol: '%s' status: %d",
                 dispatchedDefinition.RequestString, dispatchedDefinition.Symbol, dispatchedDefinition.Status)
        if dispatchedDefinition.Status == constants.srsSuccess:
            self.eventDone.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)
        limitPrice = properties(constants.opLimitPrice)

        LOG.info("OnOrderChanged: change type: %d; GW status: %d; Quantity: %d; Instrument: '%s'; Price: %f;",
                 changeType, gwStatus, quantity, instrument, limitPrice)

        if changeType == constants.ctChanged and gwStatus.Value == constants.osInOrderBook:
            LOG.info("Strategy order is placed!")
            self.eventDone.set()

    def OnQFormulaDeleted(self, name, cqgError):
        if cqgError is not None:
            error = win32com.client.Dispatch(cqgError)
            LOG.error("Failed to delete created QFormula. Error: %d '%s'", error.Code, error.Description)
            return

        LOG.info("QFormula '%s' has been deleted.", name)
        self.eventDone.set()

    def OnQFormulaDefinitionResolvedEx(self, requestStr, cqgQFormulaDefinition, cqgError):
        if cqgError is not None:
            LOG.info("QFormulaDefinition[%s]: %s", requestStr, win32com.client.Dispatch(cqgError).Description)
            self.eventDone.set()
#endregion

#region Helper methods
    def __find_account(self, name):
        for account in self.celEnvironment.cqgCEL.Accounts:
            if account.GWAccountName == name:
                return account

        raise RuntimeError(f"Account with GWAccountName == '{name}' is not found.")
#endregion

if __name__ == "__main__":
    CELEnvironment.Start(UDSviaQFormula)
