using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;
using System.Threading.Tasks;
using Tango.Core.ExtensionMethods;
using Tango.Integration.Operation;
using Tango.PMR;
using Tango.PMR.Common;
using Tango.PMR.Debugging;
using Tango.PMR.Diagnostics;
using Tango.PMR.Integration;
using Tango.PMR.MachineStatus;
using Tango.PMR.Power;
using Tango.Transport;
using Tango.Transport.Adapters;
using Tango.Transport.Transporters;
namespace Tango.Integration.ExternalBridge
{
public class ExternalBridgeReceiver : BasicTransporter
{
#region Message Handler
private class MessageHandler
{
public Action<MessageContainer> Method { get; set; }
public bool RequiresLogin { get; set; }
public ExternalBridgeLoginIntent LoginIntent { get; set; }
public MessageHandler(Action<MessageContainer> method)
{
Method = method;
}
public MessageHandler(Action<MessageContainer> method, ExternalBridgeLoginIntent intent) : this(method)
{
RequiresLogin = true;
LoginIntent = intent;
}
}
#endregion
private String _eventsToken;
private String _machineStatusToken;
private String _diagnosticsToken;
private String _debugLogsToken;
private String _applicationLogsToken;
private IMachineOperator _machineOperator;
private Dictionary<MessageType, MessageHandler> _messageHandlers;
#region Events
public event EventHandler<ExternalBridgeReceiverLoginRequestEventArgs> LoginRequest;
public event EventHandler<ColorProfileRequestEventArgs> ColorProfileRequest;
public event EventHandler Disconnected;
#endregion
#region Properties
public bool RequiresDiagnostics { get; private set; }
public bool RequiresDebugLogs { get; private set; }
public bool RequiresEventsNotification { get; private set; }
public bool RequiresMachineStatusUpdate { get; private set; }
public bool RequiresApplicationLogs { get; set; }
public bool IsLoggedIn { get; private set; }
public ExternalBridgeLoginIntent LoginIntent { get; private set; }
public bool IsLoggedInAndRequiresDiagnostics
{
get { return IsLoggedIn && (LoginIntent == ExternalBridgeLoginIntent.Diagnostics || LoginIntent == ExternalBridgeLoginIntent.FullControl); }
}
#endregion
#region Constructors
public ExternalBridgeReceiver(IMachineOperator machineOperator)
{
ComponentName = "External Bridge Receiver";
_machineOperator = machineOperator;
_messageHandlers = new Dictionary<MessageType, MessageHandler>();
UseKeepAlive = false;
KeepAliveTimeout = TimeSpan.FromSeconds(5);
KeepAliveRetries = 2;
_messageHandlers.Add(MessageType.ExternalBridgeLoginRequest, new MessageHandler(OnExternalBridgeLoginRequest));
_messageHandlers.Add(MessageType.ExternalBridgeLogoutRequest, new MessageHandler(OnExternalBridgeLogoutRequest));
_messageHandlers.Add(MessageType.ColorProfileRequest, new MessageHandler(OnColorProfileRequest));
_messageHandlers.Add(MessageType.StartDiagnosticsRequest, new MessageHandler(OnStartDiagnosticsRequest, ExternalBridgeLoginIntent.Diagnostics));
_messageHandlers.Add(MessageType.StopDiagnosticsRequest, new MessageHandler(OnStopDiagnosticsRequest, ExternalBridgeLoginIntent.Diagnostics));
_messageHandlers.Add(MessageType.StartDebugLogRequest, new MessageHandler(OnStartDebugLogRequest, ExternalBridgeLoginIntent.Diagnostics));
_messageHandlers.Add(MessageType.StopDebugLogRequest, new MessageHandler(OnStopDebugLogRequest, ExternalBridgeLoginIntent.Diagnostics));
_messageHandlers.Add(MessageType.StartEventsNotificationRequest, new MessageHandler(OnStartEventsNotificationRequest, ExternalBridgeLoginIntent.Diagnostics));
_messageHandlers.Add(MessageType.StopEventsNotificationRequest, new MessageHandler(OnStopEventsNotificationRequest, ExternalBridgeLoginIntent.Diagnostics));
_messageHandlers.Add(MessageType.StartMachineStatusUpdateRequest, new MessageHandler(OnStartMachineStatusUpdateRequest, ExternalBridgeLoginIntent.Diagnostics));
_messageHandlers.Add(MessageType.StopMachineStatusUpdateRequest, new MessageHandler(OnStopMachineStatusUpdateRequest, ExternalBridgeLoginIntent.Diagnostics));
_messageHandlers.Add(MessageType.StartApplicationLogsRequest, new MessageHandler(OnStartApplicationLogsRequest, ExternalBridgeLoginIntent.Diagnostics));
_messageHandlers.Add(MessageType.StopApplicationLogsRequest, new MessageHandler(OnStopApplicationLogsRequest, ExternalBridgeLoginIntent.Diagnostics));
_messageHandlers.Add(MessageType.JobRequest, new MessageHandler(OnJobRequest, ExternalBridgeLoginIntent.Diagnostics));
_messageHandlers.Add(MessageType.StartPowerDownRequest, new MessageHandler(OnStartPowerDownRequest, ExternalBridgeLoginIntent.Diagnostics));
}
public ExternalBridgeReceiver(TcpClient tcpClient, IMachineOperator machineOperator) : this(machineOperator)
{
Adapter = new TcpTransportAdapter(tcpClient);
}
public ExternalBridgeReceiver(SignalRTransportAdapter signalRAdapter, IMachineOperator machineOperator) : this(machineOperator)
{
Adapter = signalRAdapter;
}
#endregion
#region Override Methods
protected override void OnRequestReceived(MessageContainer container)
{
base.OnRequestReceived(container);
try
{
if (_messageHandlers.ContainsKey(container.Type))
{
var handler = _messageHandlers[container.Type];
if (handler.RequiresLogin && (!IsLoggedIn || (int)handler.LoginIntent > (int)LoginIntent))
{
SendErrorResponse(new AuthenticationException("The specified intent does not grant the specified action."), container.Token);
return;
}
try
{
try
{
handler.Method.Invoke(container);
}
catch (Exception ex)
{
SendErrorResponse(ex, container.Token);
}
}
catch (Exception ex)
{
if (ex is ResponseErrorException)
{
SendResponse((ex as ResponseErrorException).Container);
}
else
{
SendErrorResponse(ex, container.Token);
}
}
}
else
{
if (IsLoggedIn)
{
OnAnyRequest(container);
}
}
}
catch (Exception ex)
{
LogManager.Log(ex, "Error occurred in processing request message on external bridge receiver.");
}
}
protected override void OnFailed(Exception ex)
{
if (ex is KeepAliveException)
{
LogManager.Log("External bridge client has failed to provide a keep alive response. Disconnecting session...");
}
base.OnFailed(ex);
}
public async override Task Disconnect()
{
try
{
if (IsLoggedIn)
{
await SendRequest<ExternalBridgeLogoutRequest, ExternalBridgeLogoutResponse>(new ExternalBridgeLogoutRequest(), TimeSpan.FromSeconds(3));
}
}
catch (Exception ex)
{
LogManager.Log(ex, "Error sending an external bridge log out request.");
}
finally
{
ClearQueues();
}
try
{
IsLoggedIn = false;
await base.Disconnect();
}
catch { }
OnDisconnected();
}
#endregion
#region Virtual Methods
protected virtual void OnDisconnected()
{
Disconnected?.Invoke(this, new EventArgs());
}
#endregion
#region Message Handlers
protected virtual void OnExternalBridgeLoginRequest(MessageContainer container)
{
var request = MessageFactory.ParseTangoMessageFromContainer<ExternalBridgeLoginRequest>(container);
LogManager.Log($"External bridge login attempt:\nIntent: {request.Message.Intent}\nMessage:\n{request.Message.ToJsonString()}");
ExternalBridgeReceiverLoginRequestEventArgs args = new ExternalBridgeReceiverLoginRequestEventArgs((machine, deviceInfo) =>
{
//Confirm
LogManager.Log("External bridge client has logged-in successfully.");
IsLoggedIn = true;
LoginIntent = request.Message.Intent;
var response = new ExternalBridgeLoginResponse();
response.Authenticated = true;
response.SerialNumber = machine.SerialNumber;
response.DeviceInformation = deviceInfo;
SendResponse<ExternalBridgeLoginResponse>(response, container.Token);
UpdateMachineOperatorStatus((UpdateStatus)_machineOperator.Status);
UseKeepAlive = true;
},
(reason) =>
{
//Decline
SendResponse<ExternalBridgeLoginResponse>(new ExternalBridgeLoginResponse(), container.Token, false, ErrorCode.GeneralError, reason);
Disconnect().GetAwaiter().GetResult();
});
args.Address = Adapter.Address;
args.Request = request;
LoginRequest?.Invoke(this, args);
}
protected virtual void OnExternalBridgeLogoutRequest(MessageContainer container)
{
try
{
SendResponse<ExternalBridgeLogoutResponse>(new ExternalBridgeLogoutResponse(), container.Token);
}
catch (Exception ex)
{
LogManager.Log(ex);
}
finally
{
OnDisconnected();
ClearQueues();
}
}
protected async virtual void OnAnyRequest(MessageContainer container)
{
if (!container.Continuous)
{
try
{
var response = await _machineOperator.SendRequest(container);
await SendResponse(response);
}
catch (Exception ex)
{
await SendErrorResponse(ex, container.Token);
}
}
else
{
try
{
_machineOperator.SendContinuousRequest(container).Subscribe((response) =>
{
if (State == TransportComponentState.Connected)
{
SendResponse(response);
}
}, (ex) =>
{
if (State == TransportComponentState.Connected)
{
if (ex is ResponseErrorException)
{
SendResponse((ex as ResponseErrorException).Container);
}
}
});
}
catch (Exception ex)
{
await SendErrorResponse(ex, container.Token);
}
}
}
protected virtual void OnColorProfileRequest(MessageContainer container)
{
var request = MessageFactory.ParseTangoMessageFromContainer<ColorProfileRequest>(container);
ColorProfileRequestEventArgs e = new ColorProfileRequestEventArgs(request, async () =>
{
//Approved.
await SendResponse<ColorProfileResponse>(new ColorProfileResponse()
{
Approved = true
}, container.Token);
await Task.Delay(500);
await base.Disconnect();
},
async () =>
{
//Declined.
await SendResponse<ColorProfileResponse>(new ColorProfileResponse(), container.Token);
await Task.Delay(500);
await base.Disconnect();
});
ColorProfileRequest?.Invoke(this, e);
}
protected virtual void OnStartDiagnosticsRequest(MessageContainer container)
{
_diagnosticsToken = container.Token;
SendResponse<StartDiagnosticsResponse>(new StartDiagnosticsResponse(), _diagnosticsToken);
RequiresDiagnostics = true;
}
protected virtual void OnStopDiagnosticsRequest(MessageContainer container)
{
if (_diagnosticsToken != null)
{
RequiresDiagnostics = false;
SendResponse<StartDiagnosticsResponse>(new StartDiagnosticsResponse(), _diagnosticsToken, true);
_diagnosticsToken = null;
SendResponse<StopDiagnosticsResponse>(new StopDiagnosticsResponse(), container.Token);
}
}
protected virtual void OnStartDebugLogRequest(MessageContainer container)
{
_debugLogsToken = container.Token;
SendResponse<StartDebugLogResponse>(new StartDebugLogResponse(), _debugLogsToken);
RequiresDebugLogs = true;
}
protected virtual void OnStopDebugLogRequest(MessageContainer container)
{
if (_debugLogsToken != null)
{
RequiresDebugLogs = false;
SendResponse<StartDebugLogResponse>(new StartDebugLogResponse(), _debugLogsToken, true);
_debugLogsToken = null;
SendResponse<StopDebugLogResponse>(new StopDebugLogResponse(), container.Token);
}
}
protected virtual void OnStartEventsNotificationRequest(MessageContainer container)
{
_eventsToken = container.Token;
SendResponse<StartEventsNotificationResponse>(new StartEventsNotificationResponse(), _eventsToken);
RequiresEventsNotification = true;
}
protected virtual void OnStopEventsNotificationRequest(MessageContainer container)
{
if (_eventsToken != null)
{
RequiresEventsNotification = false;
SendResponse<StartEventsNotificationResponse>(new StartEventsNotificationResponse(), _eventsToken, true);
_eventsToken = null;
SendResponse<StopEventsNotificationResponse>(new StopEventsNotificationResponse(), container.Token);
}
}
protected virtual void OnStartMachineStatusUpdateRequest(MessageContainer container)
{
_machineStatusToken = container.Token;
SendResponse<StartMachineStatusUpdateResponse>(new StartMachineStatusUpdateResponse(), _machineStatusToken);
RequiresMachineStatusUpdate = true;
}
protected virtual void OnStopMachineStatusUpdateRequest(MessageContainer container)
{
if (_machineStatusToken != null)
{
RequiresMachineStatusUpdate = false;
SendResponse<StartMachineStatusUpdateResponse>(new StartMachineStatusUpdateResponse(), _machineStatusToken, true);
_machineStatusToken = null;
SendResponse<StopMachineStatusUpdateResponse>(new StopMachineStatusUpdateResponse(), container.Token);
}
}
protected virtual void OnStartApplicationLogsRequest(MessageContainer container)
{
_applicationLogsToken = container.Token;
SendResponse<StartApplicationLogsResponse>(new StartApplicationLogsResponse(), _applicationLogsToken);
RequiresApplicationLogs = true;
}
protected virtual void OnStopApplicationLogsRequest(MessageContainer container)
{
if (_applicationLogsToken != null)
{
RequiresApplicationLogs = false;
SendResponse<StartApplicationLogsResponse>(new StartApplicationLogsResponse(), _applicationLogsToken, true);
_applicationLogsToken = null;
SendResponse<StopApplicationLogsResponse>(new StopApplicationLogsResponse(), container.Token);
}
}
protected virtual void OnJobRequest(MessageContainer container)
{
if (LoginIntent != ExternalBridgeLoginIntent.FullControl)
{
throw new InvalidOperationException($"Job execution is disabled while session intent is '{LoginIntent}'.");
}
if (_machineOperator.IsPrinting)
{
throw new InvalidOperationException($"Could not execute job while machine operator status is '{_machineOperator.Status}'.");
}
else
{
OnAnyRequest(container);
}
}
protected virtual void OnStartPowerDownRequest(MessageContainer container)
{
SendResponse(new StartPowerDownResponse() { }, container.Token, false, ErrorCode.ContinuousResponseAborted, "Power down request is not supported via external bridge.");
}
#endregion
#region Continuous Updates
public void UpdateDiagnostics(MessageContainer container)
{
try
{
if (_diagnosticsToken != null)
{
var cloned = container.Clone();
cloned.Token = _diagnosticsToken;
SendResponse(cloned);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
public void UpdateDebugLogs(MessageContainer container)
{
try
{
if (_debugLogsToken != null)
{
var cloned = container.Clone();
cloned.Token = _debugLogsToken;
SendResponse(cloned);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
public void UpdateEvents(MessageContainer container)
{
try
{
if (_eventsToken != null)
{
var cloned = container.Clone();
cloned.Token = _eventsToken;
SendResponse(cloned);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
public void UpdateMachineStatus(MessageContainer container)
{
try
{
if (_machineStatusToken != null)
{
var cloned = container.Clone();
cloned.Token = _machineStatusToken;
SendResponse(cloned);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
public void UpdateApplicationLogs(MessageContainer container)
{
try
{
if (_applicationLogsToken != null)
{
var cloned = container.Clone();
cloned.Token = _applicationLogsToken;
SendResponse(cloned);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
public void UpdateMachineOperatorStatus(UpdateStatus status)
{
try
{
SendRequest<UpdateStatusRequest, UpdateStatusResponse>(new UpdateStatusRequest()
{
Status = status,
});
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
#endregion
}
}