"""
The sample requests study by name for specified expression and plots received data.

Packages requirements:
    - pandas
    - plotly
    - win32com
"""

import logging
import threading
from datetime import datetime, timedelta, timezone

import pandas as pd
import plotly.express as px
import win32com.client
from win32com.client import constants

import CELEnvironment

#region Settings

# Replace with your own study and/or expression
STUDY_NAME = "Moving Average Cross"
EXPRESSION = "Open(CLE)"
PARAMETERS = {
    "Period1" : 14,
    "MAType1" : "Exp",
    "Period2" : 21
}

# Events waiting time
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


def ToList(cqgCollection):
    """
    Converts CQGCollection of default types (e.g. string or float) to python list.
    """
    res = []
    for i in range(cqgCollection.Count):
        res.append(cqgCollection.Item(i))

    return res


class AdvancedStudy(CELEnvironment.CELSinkBase):
    def __init__(self):
        self.eventDone = threading.Event()
        self.study = None
        self.id = None

    def Init(self, celEnvironment):
        self.celEnvironment = celEnvironment

    def Start(self):
        LOG.info("Request study with name '%s'", STUDY_NAME)

        LOG.info("Create study request")
        request = win32com.client.Dispatch(self.celEnvironment.cqgCEL.CreateAdvancedStudyRequest(STUDY_NAME))

        LOG.info("Configuring request for study with abbreviation '%s' for expression '%s'",
                 request.Definition.Abbreviation, EXPRESSION)
        request.BaseExpression = EXPRESSION
        request.RangeStart = datetime.now(tz=timezone.utc) - timedelta(weeks=1)
        request.RangeEnd = datetime.now(tz=timezone.utc)
        request.SessionsFilter = "all"

        LOG.info("List of all study parameters:")
        for studyParam in request.Definition.ParameterDefinitions:
            # For more information on CQGParameterDefinition see
            # https://partners.cqg.com/sites/default/files/docs/CQGAPI_4.0R/CQG~CQGParameterDefinition.html
            LOG.info("   Name: '%s', default value: '%s'", studyParam.Name, studyParam.DefaultValue)

            if studyParam.Type != constants.ufptChoice:
                # AvailableValues property is valid only for ufptChoice parameter type
                continue

            LOG.info("    Available values:")
            for value in studyParam.AvailableValues:
                LOG.info("      - '%s'", value)

        LOG.info("Setting study parameters:")
        for parameter, value in PARAMETERS.items():
            LOG.info("   '%s': %s", parameter, value)
            request.SetParameter(parameter, value)

        LOG.info("Curves to be requested:")
        curveDefinitions = request.Definition.CurveDefinitions
        for i in range(curveDefinitions.Count):
            curve = win32com.client.Dispatch(curveDefinitions.Item(i))
            request.IncludeCurve(curve.Name)
            LOG.info("   +'%s'", curve.Name)

        LOG.info("Sending request...")
        self.study = win32com.client.Dispatch(self.celEnvironment.cqgCEL.RequestAdvancedStudy(request))
        self.id = self.study.Id

        LOG.info("Waiting for data...")
        CELEnvironment.AssertMessage(self.eventDone.wait(TIMEOUT), "Data request is timed out")

        LOG.info("Trying to plot received data...")
        df = self.GetDf()
        fig = px.line(df, x=df.index, y=df.columns)
        fig.show()
        LOG.info("Done! Plot is ready, check your browser.")

    def OnAdvancedStudyResolved(self, cqgAdvancedStudy, cqgError):
        """
        See https://partners.cqg.com/sites/default/files/docs/CQGAPI_4.0R/CQG~CQGCEL~AdvancedStudyResolved_EV.html

        This sample uses only this event to show data receiving, but AdvancedStudy request also supports other events.
        See AdvancedStudyAdded, AdvancedStudyInserted, AdvancedStudyRemoved and AdvancedStudyUpdated on Help.
        """
        if cqgError is not None:
            error = win32com.client.Dispatch(cqgError)
            LOG.error("OnDataError: Code: %d Description: %s", error.Code, error.Description)
        else:
            study = win32com.client.Dispatch(cqgAdvancedStudy)
            CELEnvironment.AssertMessage(study.Id == self.id, "Some unknown study resolved.")

        self.eventDone.set()

    def GetDf(self):
        """
        Converts advanced study to pandas.DataFrame
        """
        df = pd.DataFrame(columns=ToList(self.study.CurveHeaders))

        for i in range(self.study.Count):
            values = win32com.client.Dispatch(self.study.Item(i))
            df.loc[datetime.fromtimestamp(values.Timestamp.timestamp(), tz=timezone.utc)] = ToList(values)

        return df.applymap(lambda x: x if self.celEnvironment.cqgCEL.IsValid(x) else None)


if __name__ == "__main__":
    CELEnvironment.Start(AdvancedStudy)
