using System;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using TradeIdeas.XML;
using TradeIdeas.MiscSupport;
using System.Collections.Generic;
using TradeIdeas.ServerConnection;
using TradeIdeas.TIProData;
using TradeIdeas.TIProData.Interfaces;
namespace TradeIdeas.TIProGUI.LimitAlerts
{
public delegate void LimitAlertMessageReceivedHandler(string type, XmlNode message);
public delegate void LimitAlertStatusProcessedHandler();
///
/// Responsible for interpreting messages from the limit alert server and sending them to the appropriate forms in TIPro.
///
public class LimitAlertsManager
{
private IConnectionMaster _connectionMaster;
public event LimitAlertMessageReceivedHandler MessageReceived;
public event LimitAlertStatusProcessedHandler StatusProcessed;
///
/// Responsible for interpreting messages from the limit alert server and sending them to the appropriate forms in TIPro.
///
public LimitAlertsManager(IConnectionMaster connectionMaster)
{
_connectionMaster = connectionMaster;
}
///
/// Handles message from limit alert server as received from the limit alert server.
///
/// Type of message. Can be: alert, update, status, or removed.
/// Xml message from server.
public void HandleMessage(string type, XmlNode message)
{
//GuiEnvironment.LogMessage("[LimitAlert-HandleMessage] type=" + type + ", message=" + message.OuterXml);
if (type == "alert" || type == "update")
{
LimitAlert limitAlert = GetLimitAlert(message);
if (null != limitAlert)
{
int index = GuiEnvironment.LimitAlerts.IndexOf(limitAlert);
if (index == -1)
{
if ((GuiEnvironment.LimitAlerts.Count < GuiEnvironment.MaxLimitAlerts))
GuiEnvironment.LimitAlerts.Add(limitAlert);
else
return;
}
else
GuiEnvironment.LimitAlerts[index] = limitAlert;
List limitAlertsForms = Application.OpenForms.OfType().Where(x => !x.IsDisposed).ToList();
if (limitAlertsForms.Count > 0)
{
foreach (LimitAlertsForm limitAlertsForm in limitAlertsForms)
{
limitAlertsForm.AddLimitAlert(limitAlert, type == "alert");
}
}
List chartForms = Application.OpenForms.OfType().Where(x => !x.IsDisposed && x.Symbol == limitAlert.Symbol).ToList();
if (chartForms.Count > 0)
{
foreach (Charts chart in chartForms)
{
chart.AddOrUpdateLimitAlertAnnotation(limitAlert);
}
}
}
}
else if (type == "status")
{
GuiEnvironment.LoadingLimitAlerts = true;
GuiEnvironment.LimitAlerts.Clear();
// We need to clear the oldestLimitAlertForDeletion limit alert by setting Id to an empty string.
GuiEnvironment.oldestLimitAlertForDeletion.Id = "";
XmlNode source = message;
int alertCount = source.Property("COUNT", 0);
if (null != source)
GuiEnvironment.LogMessage("[Limit Alert Status Msg] Cleared alerts and alert count from status is " + alertCount + ", outerxml=" + source.OuterXml);
else
GuiEnvironment.LogMessage("[Limit Alert Status Msg] Cleared alerts and alert count from status is " + alertCount + ", outerxml= null");
for (int i = 1; i <= alertCount; i++)
{
LimitAlert limitAlert = GetLimitAlert(source, i);
if (null != limitAlert && GuiEnvironment.LimitAlerts.Count < GuiEnvironment.MaxLimitAlerts)
GuiEnvironment.LimitAlerts.Add(limitAlert);
}
GuiEnvironment.LoadingLimitAlerts = false;
/*
List limitAlertsForms = Application.OpenForms.OfType().ToList();
GuiEnvironment.LogMessage("[Limit Alert Status Msg] Found " + limitAlertsForms.Count + " price alert forms in OpenForms");
if (limitAlertsForms.Count > 0)
{
foreach (LimitAlertsForm limitAlertsForm in limitAlertsForms)
{
limitAlertsForm.MergeAlerts();
limitAlertsForm.UpdatePandLCharts();
}
}
*/
if (null != StatusProcessed)
StatusProcessed();
UpdateLimitAlertAnnotations();
}
else if (type == "removed")
{
LimitAlert limitAlert = GetLimitAlert(message);
if (null != limitAlert)
{
GuiEnvironment.LimitAlerts.Remove(limitAlert);
List limitAlertsForms = Application.OpenForms.OfType().Where(x => !x.IsDisposed).ToList();
if (limitAlertsForms.Count > 0)
{
foreach (LimitAlertsForm limitAlertsForm in limitAlertsForms)
{
limitAlertsForm.RemoveLimitAlert(limitAlert);
}
}
List chartForms = Application.OpenForms.OfType().Where(x => !x.IsDisposed && x.Symbol == limitAlert.Symbol).ToList();
if (chartForms.Count > 0)
{
foreach (Charts chart in chartForms)
{
chart.RemovedLimitAlertAnnotation(limitAlert.Id);
}
}
// We need to clear the oldestLimitAlertForDeletion limit alert if it was removed by setting Id to an empty string.
if (GuiEnvironment.oldestLimitAlertForDeletion.Id == limitAlert.Id)
GuiEnvironment.oldestLimitAlertForDeletion.Id = "";
}
}
else if (type == "curve")
{
// A user was getting an "Object reference not set to an instance of an object." exception.
// We need to check if message is not null.
string debugView = type;
if (null != message)
debugView += message.OuterXml;
XmlNode source = message;
int alertCount = source.Property("COUNT", 0);
if (null != source)
GuiEnvironment.LogMessage("[Limit Alert Status Msg] Cleared alerts and alert count from status is " + alertCount + ", outerxml=" + source.OuterXml);
else
GuiEnvironment.LogMessage("[Limit Alert Status Msg] Cleared alerts and alert count from status is " + alertCount + ", outerxml= null");
for (int i = 1; i <= alertCount; i++)
{
string intradayNumbers = source.Property("INTRADAY_CURVE" + i, "");
string dailyNumbers = source.Property("DAILY_CURVE" + i, "");
string id = source.Property("ID" + i, "");
try
{
var matches = GuiEnvironment.LimitAlerts.Where(x => x.Id == id);
if (matches.Count() == 1)
{
LimitAlert limitAlert = matches.First();
if (null != limitAlert)
{
if (intradayNumbers != "")
{
Thermograph intradayThermograph = RowDataHelper.GetThermograph(intradayNumbers, 1);
limitAlert.IntradayPerformance = intradayThermograph;
}
if (dailyNumbers != "")
{
Thermograph dailyThermograph = RowDataHelper.GetThermograph(dailyNumbers, 1);
limitAlert.DailyPerformance = dailyThermograph;
}
}
}
}
catch (Exception e)
{
string debugView1 = e.StackTrace;
}
}
List limitAlertsForms = Application.OpenForms.OfType().Where(x => !x.IsDisposed).ToList();
if (limitAlertsForms.Count > 0)
{
foreach (LimitAlertsForm limitAlertsForm in limitAlertsForms)
{
limitAlertsForm.UpdatePandLCharts();
}
}
}
else
{
// This is for debugging purposing only.
// A user was getting an "Object reference not set to an instance of an object." exception.
// We need to check if message is not null.
string debugView = type;
if (null != message)
debugView += message.OuterXml;
}
}
///
/// Loops through open charts and looks for limit alerts for each chart symbol
///
private void UpdateLimitAlertAnnotations()
{
List chartForms = Application.OpenForms.OfType().Where(x => !x.IsDisposed).ToList();
if (chartForms.Count > 0)
{
foreach (Charts chart in chartForms)
{
try
{
List limitAlertsForSymbol = GuiEnvironment.LimitAlerts.Where(x => x.Symbol == chart.Symbol).ToList();
foreach (LimitAlert limitAlert in limitAlertsForSymbol)
{
chart.AddOrUpdateLimitAlertAnnotation(limitAlert);
}
}
catch (Exception e)
{
string debugView = e.StackTrace;
}
}
}
}
///
/// Converts XmlNode message from server into a LimitAlert object.
///
/// XmlNode message from server
/// Optional alert number. To be used when message type = 'status' which includes multiple limit alerts in the XmlNode message.
/// A LimitAlert object.
private LimitAlert GetLimitAlert(XmlNode source, int? alertNumber = null)
{
if (null == source)
return null;
string propertySuffix = "";
if (alertNumber.HasValue)
propertySuffix = alertNumber.Value.ToString();
LimitAlert limitAlert = new LimitAlert();
limitAlert.Symbol = source.Property("SYMBOL" + propertySuffix, "");
limitAlert.Id = source.Property("ID" + propertySuffix, "");
limitAlert.ClientId = source.Property("CLIENT_ID" + propertySuffix, "");
limitAlert.Price = source.Property("PRICE" + propertySuffix, 0.00);
limitAlert.IsLong = source.Property("IS_LONG" + propertySuffix, 1) == 1;
limitAlert.LongAfter = source.Property("LONG_AFTER" + propertySuffix, 1) == 1;
limitAlert.AfterHours = source.Property("AFTER_HOURS" + propertySuffix, 1) == 1;
int expires = source.Property("EXPIRES" + propertySuffix, -1);
if (expires != -1)
limitAlert.Expires = ServerFormats.FromTimeT(expires);
double invalidPrice = source.Property("INVALID_PRICE" + propertySuffix, -1.0);
if (invalidPrice != -1.0)
limitAlert.InvalidPrice = invalidPrice;
limitAlert.Notes = source.Property("NOTES" + propertySuffix, "");
int triggered = source.Property("TRIGGERED" + propertySuffix, -1);
if (triggered != -1)
limitAlert.Triggered = ServerFormats.FromTimeT(triggered);
limitAlert.Status = source.Property("STATUS" + propertySuffix, "WORKING");
int created = source.Property("CREATED" + propertySuffix, 0);
DateTime? createdDateTime = null;
if (created > 0)
createdDateTime = ServerFormats.FromTimeT(created);
if (createdDateTime.HasValue)
limitAlert.Created = createdDateTime.Value;
int updated = source.Property("UPDATED" + propertySuffix, 0);
DateTime? updatedDateTime = null;
if (updated > 0)
updatedDateTime = ServerFormats.FromTimeT(updated);
if (updatedDateTime.HasValue)
limitAlert.Updated = updatedDateTime.Value;
return limitAlert;
}
///
/// Converts a LimitAlert object into a TCL prototype appropriate for sending to the limit alert server.
///
/// Limit Alert object.
/// If this is a create, set this to true to add a client id to avoid duplication.
/// TCL prototype string
public static string GetPrototype(LimitAlert limitAlert, bool addClientId)
{
string toReturn = "symbol " + limitAlert.Symbol + " price " + ServerFormats.ToString(limitAlert.Price) + " is_long " + (limitAlert.IsLong ? "1" : "0");
toReturn += " long_after " + (limitAlert.LongAfter ? "1" : "0");
toReturn += " after_hours " + (limitAlert.AfterHours ? "1" : "0");
if (limitAlert.InvalidPrice.HasValue)
toReturn += " invalid_price " + ServerFormats.ToString(limitAlert.InvalidPrice.Value);
if (limitAlert.Expires.HasValue)
toReturn += " expires " + ServerFormats.ToTimeT(limitAlert.Expires.Value);
if (limitAlert.Triggered.HasValue)
toReturn += " triggered " + ServerFormats.ToTimeT(limitAlert.Triggered.Value);
toReturn += " created " + ServerFormats.ToTimeT(limitAlert.Created);
toReturn += " updated " + ServerFormats.ToTimeT(limitAlert.Updated);
// setting the Id makes the server try to update an existing limit alert if it exists
if (null != limitAlert.Id && limitAlert.Id != "")
toReturn += " id " + limitAlert.Id;
if (limitAlert.Status != "")
toReturn += " status " + limitAlert.Status;
if (addClientId)
toReturn += " client_id " + Guid.NewGuid().ToString();
string escapedNotes = TclStringEscape(limitAlert.Notes);
toReturn += " notes {" + escapedNotes + "}";
return toReturn;
}
///
/// Escapes special characters in a string to be sent to the server in a safe way.
///
/// Text to be escaped.
/// Escaped text.
private static string TclStringEscape(string text)
{
string escaped = text.Replace("{", "\\{");
escaped = escaped.Replace("}", "\\}");
return escaped;
}
///
/// Takes LimitAlert object and sends it to the limit alert server to be created.
///
/// LimitAlert to be created
public void CreateNewLimitAlert(LimitAlert limitAlert)
{
object[] message = new object[]
{
"command", GetFlexCommand(),
"subcommand", "la_add_alert",
"options", LimitAlertsManager.GetPrototype(limitAlert, true)
};
SendLimitAlert(message);
}
private void SendLimitAlert(object[] message)
{
Dictionary messageToSend = TalkWithServer.CreateMessage(message);
_connectionMaster.SendManager.SendMessage(messageToSend, GetLimitAlertCreationResponse, false, message);
}
private void GetLimitAlertCreationResponse(byte[] body, object message)
{
if (null == body)
{
if (message as object[] != null)
SendLimitAlert(message as object[]);
}
else
{
string debugView = System.Text.Encoding.UTF8.GetString(body);
// response to this command is ignored. If successful, the server sends a message of type "update"
}
}
///
/// Send deletion request to the limit alert server. If successful, the server will return a message of type "removed".
///
/// ID of limit alert to be removed.
public void DeleteAlert(string id)
{
object[] message = new object[] {
"command", GetFlexCommand(),
"subcommand", "la_remove_alert",
"id", id
};
Dictionary messageToSend = TalkWithServer.CreateMessage(message);
_connectionMaster.SendManager.SendMessage(messageToSend);
}
///
/// Tell the limit alert server to start sending limit alert messages to this client.
///
public void StartListening()
{
object[] message = new object[] {
"command", GetFlexCommand(),
"subcommand", "la_add_listener"
};
GuiEnvironment.LogMessage("[Limit Alerts LimitAlertResponse] Start Listening command sent to server");
Dictionary messageToSend = TalkWithServer.CreateMessage(message);
_connectionMaster.SendManager.SendMessage(messageToSend, LimitAlertResponse, true);
}
private void LimitAlertResponse(byte[] body, object clientId)
{
if (null == body)
StartListening();
else
{
string debugView = System.Text.Encoding.UTF8.GetString(body);
//GuiEnvironment.LogMessage("[Limit Alerts LimitAlertResponse] Got limitalertresponse from server: " + debugView);
XmlNode message = XmlHelper.Get(body).Node(0);
String type = message.Property("type");
if (null != MessageReceived)
MessageReceived(type, message);
}
}
///
/// Tell the limit alert server to stop sending limit alert messages to this client.
///
public void StopListening()
{
object[] message = new object[] {
"command", GetFlexCommand(),
"subcommand", "la_remove_listener"
};
Dictionary messageToSend = TalkWithServer.CreateMessage(message);
_connectionMaster.SendManager.SendMessage(messageToSend);
}
public static string GetFlexCommand()
{
return GuiEnvironment.AppConfig.Node("GUI_LIB").Node("LIMIT_ALERTS").Node("FLEX_COMMAND").Property("VALUE", "limit_alert_command");
}
}
}