"""
The sample demonstrates how to place and modify Short Sell orders.

Packages requirements:
    - win32com
    - tabulate
"""

import logging
import threading

import win32com.client
from tabulate import tabulate
from win32com.client import constants

import CELEnvironment
from CELEnvironment import AssertMessage

#region Settings

LOGIN = "CHANGE"  # Replace with real GW login
PASSWORD = "CHANGE"  # Replace with real password
SYMBOL = "S.US.GOOG"  # Replace it with enabled stock symbol
PRICE = 3000  # Replace with real (limit) price
TIMEOUT = 15  # Events waiting time (seconds)
LOCATE_ID = "CHANGE"  # Replace with your locate ID

# 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


#region Utility functions

def ExtendedSideToString(extended_side):
    """
    Maps extended side enum value with string representation.
    Returns passed value if cannot be mapped.
    """
    if extended_side == constants.esUndefined:
        return "Undefined"
    if extended_side == constants.esSellShort:
        return "Sell Short"
    return extended_side


def GWStatusToString(status):
    """
    Returns string representation of some GW order statuses.
    For non-covered statuses returns status code.
    """
    if status == constants.osInTransit:
        return "In Transit"
    if status == constants.osInOrderBook:
        return "Online"
    if status == constants.osInModify:
        return "In Modify"
    if status == constants.osInCancel:
        return "In Cancel"
    if status == constants.osCanceled:
        return "Canceled"
    return status


def OrderChangeTypeToString(change_type):
    """
    Returns string representation of some order change types.
    For non-covered change types returns passed value.
    """
    if change_type == constants.ctAdded:
        return "Added"
    if change_type == constants.ctChanged:
        return "Changed"
    return change_type

#endregion


class SellShortOrder(CELEnvironment.CELSinkBase):
    def __init__(self):
        self.account = None
        self.instrument = None
        self.eventDone = threading.Event()
        self.eventDisconnect = 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.eventDone.wait(TIMEOUT), "GW connection timeout!")
        self.eventDone.clear()

        self.celEnvironment.cqgCEL.AccountSubscriptionLevel = constants.aslAccountUpdatesAndOrders
        LOG.info("Waiting for accounts coming...")
        AssertMessage(self.eventDone.wait(TIMEOUT), "Accounts coming timeout!")
        self.eventDone.clear()

        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)
        request = win32com.client.Dispatch(self.celEnvironment.cqgCEL.CreateInstrumentRequest())
        request.Symbol = SYMBOL
        self.celEnvironment.cqgCEL.SubscribeNewInstrument(request)
        LOG.info("%s instrument waiting...", SYMBOL)
        AssertMessage(self.eventDone.wait(TIMEOUT), "Instrument resolution timeout!")
        self.eventDone.clear()

        LOG.info("Create sell order")
        quantity = -1
        order = win32com.client.Dispatch(self.celEnvironment.cqgCEL.CreateOrder(constants.otLimit, self.instrument,
                                                                                self.account, quantity,
                                                                                constants.osdUndefined, PRICE))

        LOG.info("Specify extended side as 'Sell Short' and locate ID: '%s'", LOCATE_ID)
        properties = win32com.client.Dispatch(order.Properties)
        properties(constants.opExtendedSide).Value = constants.esSellShort
        properties(constants.opLocateID).Value = LOCATE_ID

        LOG.info("Place order")
        order.Place()
        LOG.info("Waiting for order placing...")
        AssertMessage(self.eventDone.wait(TIMEOUT), "Order placing timeout!")
        self.eventDone.clear()

        LOG.info("Convert short sell order in common order")
        orderModify = order.PrepareModify()
        properties = win32com.client.Dispatch(orderModify.Properties)
        properties(constants.opExtendedSide).Value = constants.esUndefined
        order.Modify(orderModify)
        AssertMessage(self.eventDone.wait(TIMEOUT), "Order modifying timeout")
        self.eventDone.clear()

        LOG.info("Cancel order")
        order.Cancel()
        AssertMessage(self.eventDone.wait(TIMEOUT), "Order canceling timeout")

        LOG.info("Logoff from GW")
        self.eventDisconnect.clear()
        self.celEnvironment.cqgCEL.GWLogoff()
        AssertMessage(self.eventDisconnect.wait(TIMEOUT), "GW disconnection timeout!")

        LOG.info("Done!")

    def OnGWConnectionStatusChanged(self, newStatus):
        if newStatus == constants.csConnectionUp:
            LOG.info("GW connection is UP!")
            self.eventDone.set()
        if newStatus == constants.csConnectionDown:
            LOG.info("GW connection is DOWN!")
            self.eventDisconnect.set()

    def OnAccountChanged(self, changeType, cqgAccount, cqgPosition):
        if changeType != constants.actAccountsReloaded:
            return

        LOG.info("Accounts are ready!")
        self.eventDone.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("Instrument %s is resolved!", symbol)
        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).Value
        output = tabulate([
            ["GW status", "Quantity", "Price", "ExtendedSide", "LocateID"],
            [
                GWStatusToString(gwStatus), properties(constants.opQuantityFractional).Value,
                properties(constants.opLimitPrice).Value,
                ExtendedSideToString(properties(constants.opExtendedSide).Value),
                properties(constants.opLocateID).Value
            ]
        ])
        LOG.info("OnOrderChanged: change type: %s\n%s", OrderChangeTypeToString(changeType), output)

        if changeType != constants.ctChanged:
            return

        if gwStatus in (constants.osInOrderBook, constants.osCanceled):
            self.eventDone.set()


def CustomAPIConfiguration(APIConfiguration):
    APIConfiguration.NewInstrumentMode = True


if __name__ == "__main__":
    CELEnvironment.Start(SellShortOrder, CustomAPIConfiguration)
