using System;
using System.Data;
using System.Text;
using System.Windows.Forms;
using IndianHealthService.BMXNet.Model;
using IndianHealthService.BMXNet.Ado;
using System.Collections.Generic;
using System.Threading;

namespace IndianHealthService.BMXNet.Services
{
    /// <summary>
    /// Extends BMX functionality for easier development and debugging.
    /// Copied from Component Framework project IndianHealthService.Xo.Framework.Rpms
    /// </summary>
    internal class BMXNetRemoteSession : RemoteSession, RemoteEventService
    {
        public int ReceiveTimeout
        {
            get { return this.SessionConnection.ReceiveTimeout; }
            set { this.SessionConnection.ReceiveTimeout = value; }
        }

        public int SendTimeout
        {
            get { return this.SessionConnection.SendTimeout; }
            set { this.SessionConnection.SendTimeout = value; }
        }

        private Log _log = new NullLog();

        public Log Log
        {
            get { return _log; }
            set { _log = value; }
        }

        private object _rpcLock = new object();

        public object RpcLock
        {
            get { return _rpcLock; }
            set { _rpcLock = value; }
        }

        private Guid _id = Guid.NewGuid();

        public Guid Id
        {
            get { return _id; }
            set { _id = value; }
        }

        private System.Timers.Timer _pollingTimer = null;

        protected System.Timers.Timer PollingTimer
        {
            get { return _pollingTimer; }
            set { _pollingTimer = value; }
        }

        private int _eventPollingInterval = 5000;

        public int EventPollingInterval
        {
            get {             
                return _eventPollingInterval;
            }
            set
            {
               _eventPollingInterval = value;
                if (this.IsEventPollingEnabled)
                {
                    this.PollingTimer.Interval = value;
                }
            }
        }

        public Encoding ConnectionEncoding
        {
            get
            {
                return SessionConnection.ConnectionEncoding;
            }
            set
            {
                SessionConnection.ConnectionEncoding = value;
            }
        }

        public bool IsEventPollingEnabled
        {
            get
            {
                return this.PollingTimer != null;
            }
            set
            {
                if (value)
                {
                    this.StartPollingTimer();
                }
                else
                {
                    this.StopPollingTimer();
                }
            }
        }

        private void AssertPrimarySession()
        {
            if (!this.IsPrimary)
            {
                throw new NotSupportedException("This behavior is not supported on non-primary sessions.  See documentation.");
            }
        }


        private void StartPollingTimer()
        {
            if (this.PollingTimer == null)
            {
                this.PollingTimer = new System.Timers.Timer(this.EventPollingInterval);
                this.PollingTimer.Enabled = true;
                this.PollingTimer.Elapsed += new System.Timers.ElapsedEventHandler(PollingTimer_Elapsed);
                this.PollingTimer.Start();
            }
        }

        private bool _inPollForEvent = false;

        protected bool InPollForEvent
        {
            get { return _inPollForEvent; }
            set { _inPollForEvent = value; }
        }


        void PollingTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (!this.InPollForEvent)
            {
                try
                {
                    this.InPollForEvent = true;          
                    this.PollForEvent();
                }
                finally
                {
                    this.InPollForEvent = false;
                }
            }
        }

        private void StopPollingTimer()
        {
            if (this.PollingTimer != null)
            {
                this.PollingTimer.Enabled = false;
                this.PollingTimer.Stop();
                this.PollingTimer.Elapsed -= new System.Timers.ElapsedEventHandler(PollingTimer_Elapsed);           
                this.PollingTimer = null;
            }
        }

        public String Job
        {
            get { return this.SessionConnection == null ? "" : this.SessionConnection.Job; }
        }

        public override string ToString()
        {
            return "RemoteSession [Process " + this.Job + (this.IsPrimary ? ", Primary Session]" : "]");
        }
        private bool _isPrimary = false;

        public bool IsPrimary
        {
            get { return _isPrimary; }
            set { _isPrimary = value; }
        }

        private bool _isConnected = false;

        public bool IsConnected
        {
            get
            {
                return (_isConnected || this.IsPrimary) && this.SessionConnection.IsConnected;
            }
            set { _isConnected = value; }
        }

        public User User
        {
            get { return this.Broker.User; }
        }

        public BMXNetBroker Broker
        {
            get { return this.SessionConnection.Broker; }
     
        }
        private BMXNetSessionConnection _sessionConnection;

        internal BMXNetSessionConnection SessionConnection
        {
            get { return _sessionConnection; }
            set { _sessionConnection = value; }
        }

        private BMXNetDataAdapter m_cachedDataAdapter;

        private String _debugLastRpcSignature;

        public String DebugLastRpcSignature
        {
            get { return _debugLastRpcSignature; }
            set { _debugLastRpcSignature = value; }
        }

        private String _debugLastRpcResult;

        public String DebugLastRpcResult
        {
            get { return _debugLastRpcResult; }
            set { _debugLastRpcResult = value; }
        }

        private DataTable _debugLastTableResult;

        public DataTable DebugLastTableResult
        {
            get { return _debugLastTableResult; }
            set { _debugLastTableResult = value; }
        }

        private String _rpcResult;

        public BMXNetRemoteSession()
        {
            this.m_cachedDataAdapter = new BMXNetDataAdapter();
        }

        internal void Begin(BMXNetSessionConnection aConnection, BMXRemoteSessionPool aPool)
        {
            this.SessionConnection = aConnection;
            this.SessionPool = aPool;
            this.IsConnected = true;
        }

        private void PollForTimerEvent()
        {
            if (this.TimerEvent != null)
            {
                try
                {
                    this.TimerEvent(this, new EventArgs());
                }
                catch (Exception problemToIgnore)
                {
                    this.Log.Log("BMXNetRemoteSession", "Exception", problemToIgnore.Message);
                }
            }            
        }

        public RemoteEventService EventServices
        {
            get { return this; }
        }

        private Control _invokedControl = null;

        public Control InvokedControl
        {
            get { return _invokedControl; }
            set { _invokedControl = value; }
        }


        private DataTable _debugLastTableChanges = null;

        public DataTable DebugLastTableChanges
        {
            get { return _debugLastTableChanges; }
            set { _debugLastTableChanges = value; }
        }

        public event EventHandler<RemoteEventArgs> RpmsEvent;
        public event EventHandler TimerEvent;

        internal void AuthenticatedConnectionInfo(BMXNetSessionConnection aSessionConnection)
        {
            this.SessionConnection = aSessionConnection;
        }

        public virtual String TransmitRPC(String rpcCommand, String rpcParameter, String aContext)
        {
            this.DebugLastRpcSignature = rpcCommand + "^" + rpcParameter + "^" + aContext;
            this.DebugLastRpcResult = null;        

            lock (this.RpcLock)
            {
                this.AppContext = aContext;
                this.RpcResult = this.SessionConnection.TransmitRPC(rpcCommand, rpcParameter);
                this.DebugLastRpcResult = this.RpcResult;
            }

            return this.RpcResult;
        }

        private void AssertConnected()
        {
            if (!this.IsConnected)
            {
                throw new BMXNetException("Session is not connected or has been closed.");
            }
        }

        public String SafelyTransmitRPC(String rpcCommand, String rpcParameter, String context)
        {
            this.AssertConnected();

            try
            {
                return this.TransmitRPC(rpcCommand, rpcParameter, context);
            }
            catch (Exception exception)
            {
                return "Exception: " + exception.Message;
            }
        }



        public bool SaveChanges(DataTable aDataTable)
        {
            this.AssertConnected();

            DataTable changes = aDataTable.GetChanges();

            this.DebugLastTableChanges = changes;

            if (changes == null)
                return false;

            BMXNetConnection bmxConnection = null;

            lock (this.RpcLock)
            {
                try
                {
                    bmxConnection = new BMXNetConnection(this.SessionConnection);
                    bmxConnection.Open();

                    if (bmxConnection.State == ConnectionState.Open)
                    {
                        BMXNetDataAdapter bmxDataAdaptor = this.m_cachedDataAdapter;

                        BMXNetCommand bmxSelectCommand = bmxConnection.CreateCommand() as BMXNetCommand;
                        bmxSelectCommand.CommandText = aDataTable.ExtendedProperties["BMXNetSelectStatementForUpdate"] as String;
                        bmxDataAdaptor.SelectCommand = bmxSelectCommand;

                        DataTable schema = bmxDataAdaptor.FillSchema(aDataTable, SchemaType.Source);
                        BMXNetCommand bmxUpdateCommand = bmxConnection.CreateCommand() as BMXNetCommand;
                        bmxUpdateCommand.BMXBuildUpdateCommand(schema);
                        bmxDataAdaptor.UpdateCommand = bmxUpdateCommand;

                        bmxDataAdaptor.Update(changes);
                        aDataTable.AcceptChanges();

                        return true;
                    }
                    else
                    {
                        throw new BMXNetException("Unable to connect to RPMS.");
                    }
                }

                catch (Exception exception)
                {
                    throw new BMXNetException("Unable to save data to RPMS.", exception);
                }
                finally
                {
                    if (bmxConnection != null)
                    {
                        bmxConnection.Close();
                    }
                }
            }
        }


        public virtual DataTable TableFromCommand(String aCommand)
        {
            return this.TableFromCommand(aCommand, this.AppContext);
        }

        public DataTable TableFromCommand(string aCommand, string aContext)
        {
            return this.TableFromCommand(aCommand, null, null,aContext);
        }


        public DataTable TableFromCommand(String aCommand, DataSet aDataSet, string aTableName)
        {
            return this.TableFromCommand(aCommand, aDataSet, aTableName, this.AppContext);
        }

        public virtual DataTable TableFromCommand(String aCommand, DataSet aDataSet, string aTableName, string aContext)
        {
            this.Log.Log("BMX NET", "Debug", "TableFromCommand >> " + aCommand, aTableName == null ? "Null Table" : aTableName, aContext == null ? "Null Context" : aContext);
            this.DebugLastTableResult = null;

            this.AssertConnected();

            DataSet dataSet = aDataSet == null ? new DataSet() : aDataSet;

            BMXNetConnection bmxConnection = new BMXNetConnection(this.SessionConnection);
            bmxConnection.Open();

            if (bmxConnection.State != ConnectionState.Open)
                throw new BMXNetException("Unable to connect to RPMS.");

            lock (this.RpcLock)
            {
                try
                {
                    this.AppContext = aContext;
                    BMXNetCommand bmxCommand = bmxConnection.CreateCommand() as BMXNetCommand;
                    bmxCommand.CommandText = aCommand;
                    BMXNetDataAdapter bmxDataAdaptor = new BMXNetDataAdapter();
                    bmxDataAdaptor.SelectCommand = bmxCommand;


                    DataTable answer = null;
                    String validTableName = aTableName == null ? this.DefaultTableName : aTableName;
                    DateTime stopWatch = DateTime.Now;

                    if (aDataSet == null)
                    {
                        answer = new DataTable(validTableName);
                        bmxDataAdaptor.Fill(answer);
                    }
                    else
                    {
                        DataTable dataTable = aDataSet.Tables.Contains(validTableName) ? aDataSet.Tables[validTableName] : aDataSet.Tables.Add(validTableName);
                        bmxDataAdaptor.Fill(aDataSet, validTableName);
                        answer = aDataSet.Tables[validTableName];
                    }

                    answer.ExtendedProperties["BMXNetSelectStatementForUpdate"] = bmxCommand.CommandText;
                    answer.ExtendedProperties["BMXQueryStart"] = stopWatch;
                    answer.ExtendedProperties["BMXQueryTime"] = (DateTime.Now - stopWatch).TotalMilliseconds;
                    answer.ExtendedProperties["RpmsQuery"] = aCommand;
                    answer.ExtendedProperties["RpmsSchema"] = this.RpmsSchema(aCommand);

                    this.DebugLastTableResult = answer;

                    return answer;
                }

                catch (Exception exception)
                {
                    throw new BMXNetException("Unable to retrive data from RPMS.", exception);
                }
                finally
                {
                    if (bmxConnection != null)
                    {
                        bmxConnection.Close();
                    }
                }
            }
        }

        private String RpmsSchema(String aString)
        {
            char[] carrotDelimiter = "^".ToCharArray();
            String[] parts = aString.Split(carrotDelimiter);
            return (parts.Length > 2) ? parts[1] : "";
        }

        public DataTable TableFromRPC(String rpcCommand, String rpcParameter)
        {
            return this.TableFromRPC(rpcCommand, rpcParameter, this.AppContext);
        }
        
        public DataTable TableFromRPC(string rpcCommand, string rpcParameter, string aContext)
        {
            return this.TableFromRPC(rpcCommand, rpcParameter, new DataSet(), this.DefaultTableName, aContext);
       
        }

        public DataTable TableFromRPC(string rpcCommand, string rpcParameter, DataSet aDataSet, string aTableName)
        {
            return this.TableFromRPC(rpcCommand, rpcParameter, aDataSet, aTableName, this.AppContext);
        }
            
        public DataTable TableFromRPC(String rpcCommand, String rpcParameter, DataSet aDataSet, String aTableName, String aContext)
        {
            String generatedCommand = this.TransmitRPC(rpcCommand, rpcParameter, aContext);

            return this.IsBmxAdoCommand(generatedCommand) ? this.TableFromCommand(generatedCommand, aDataSet, aTableName, aContext) : null;
        }
    
        public String RpcResult
        {
            get { return this._rpcResult; }
            set { this._rpcResult = value; }
        }

        public virtual DataTable TableFromSQL(String sql)
        {
            return this.TableFromSQL(sql, this.AppContext);
        }

        public DataTable TableFromSQL(string sql, string aContext)
        {
            return this.TableFromSQL(sql, new DataSet(), this.DefaultTableName,aContext);
        }

        //smh: THIS IS WRONG. If you pass the data set, why make a new dataset...
        public DataTable TableFromSQL(string sql, DataSet aDataSet, string aTableName)
        {
            return this.TableFromSQL(sql, aDataSet, aTableName, this.AppContext);
        }

        public DataTable TableFromSQL(string sql, DataSet aDataSet, string aTableName, String aContext)
        {
            this.Log.Log("BMX NET", "Debug", "TableFromSQL>> " + sql, aTableName == null ? "Null Table" : aTableName, aContext == null ? "Null Context" : aContext);
            this.DebugLastTableResult = null;

            this.AssertConnected();

            BMXNetConnection bmxConnection;
            BMXNetCommand bmxCommand;
            BMXNetDataAdapter bmxDataAdaptor;

            bmxConnection = new BMXNetConnection(this.SessionConnection);
            bmxConnection.Open();
            if (bmxConnection.State != ConnectionState.Open)
            {
                throw new BMXNetException("Unable to connect to RPMS.");
            }

            lock (this.RpcLock)
            {
                try
                {
                   this.AppContext = aContext;

                    bmxCommand = bmxConnection.CreateCommand() as BMXNetCommand;
                    bmxCommand.CommandText = sql;
                    bmxDataAdaptor = new BMXNetDataAdapter();
                    bmxDataAdaptor = this.m_cachedDataAdapter;
                    bmxDataAdaptor.SelectCommand = bmxCommand;

                    DataTable answer = null;
                    String validTableName = aTableName == null ? this.DefaultTableName : aTableName;
                    DateTime stopWatch = DateTime.Now;

                    if (aDataSet == null)
                    {
                        answer = new DataTable(validTableName);
                        bmxDataAdaptor.Fill(answer);
                    }
                    else
                    {
                        DataTable dataTable = aDataSet.Tables.Contains(validTableName) ? aDataSet.Tables[validTableName] : aDataSet.Tables.Add(validTableName);
                        bmxDataAdaptor.Fill(aDataSet, validTableName);
                        answer = aDataSet.Tables[validTableName];
                    }


                    answer.ExtendedProperties["BMXNetSelectStatementForUpdate"] = bmxCommand.CommandText;
                    answer.ExtendedProperties["SqlQuery"] = sql;
                    answer.ExtendedProperties["BMXQueryStart"] = stopWatch;
                    answer.ExtendedProperties["BMXQueryTime"] = (DateTime.Now - stopWatch).TotalMilliseconds;

                    this.DebugLastTableResult = answer;

                    return answer;
                }

                catch (Exception exception)
                {
                    throw new Exception("Unable to retrive sql data from RPMS.", exception);
                }
                finally
                {
                    if (bmxConnection != null)
                        bmxConnection.ToString();
                }
            }
        }

        public bool IsBmxAdoCommand(String aString)
        {
            String validFetchTransactionPrefix = "BMX ADO SS";

            return aString.StartsWith(validFetchTransactionPrefix);
        }

        private BMXRemoteSessionPool _sessionPool = null;

        internal BMXRemoteSessionPool SessionPool
        {
            get { return _sessionPool; }
            set { _sessionPool = value; }
        }

        private bool _hasSubscribedForAsyncEvents = false;

        internal bool HasSubscribedForAsyncEvents
        {
            get { return _hasSubscribedForAsyncEvents; }
            set { _hasSubscribedForAsyncEvents = value; }
        }

        public virtual void Close()
        {
            if (this.HasSubscribedForAsyncEvents)
            {
                this.EventServices.Unsubscribe(this.AsyncFinishedEventName);
            }
            this.StopPollingTimer();
            this.SessionPool.CloseSession(this);
        }

        public string HostAddress
        {
            get { throw new NotImplementedException(); }
        }

        public string AuthenicatedDuz
        {
            get { return this.SessionConnection.DUZ; }
        }


        public string AuthenicatedUserName
        {
            get { return this.SessionConnection.UserName; }
        }

        public string AppContext
        {
            get
            {
                return this.SessionConnection.AppContext;
            }
            set
            {
                this.SessionConnection.AppContext = value;
            }
        }

        public string TransmitRPC(string rpcCommand, string rpcParameter)
        {
            return this.TransmitRPC(rpcCommand, rpcParameter, this.AppContext);
        }

        public string SafelyTransmitRPC(string rpcCommand, string rpcParameter)
        {
            return this.SafelyTransmitRPC(rpcCommand, rpcParameter, this.AppContext);
        }
      
        public System.Data.DataTable TableFromRPC(string rpcCommand, string rpcParameter, System.Data.DataSet aDataSet)
        {
            return this.TableFromRPC(rpcCommand, rpcParameter, aDataSet);
        } 
 
        public int Subscribe(string anEventName)
        {
            int result=this.EventManagmentCall("BMX EVENT REGISTER^" + anEventName);
            if (result == 0)
            {
                this.Log.Log("BMX NET", "Events", "Subscribed to " + anEventName);
            }
            else
            {
                this.Log.Log("BMX NET", "Events", "Unable to subscribe to " + anEventName);           
            }
            return result;
        }

        public int Unsubscribe(string anEventName)
        {
            int result=this.EventManagmentCall("BMX EVENT UNREGISTER^" + anEventName);
            if (result == 0)
            {
                this.Log.Log("BMX NET", "Events", "Unsubscribed from " + anEventName);
            }
            else
            {
                this.Log.Log("BMX NET", "Events", "Unable to unsubscribe from " + anEventName);
            }
            return result;
        }

        public int TriggerEvent(string anEventName, string aParameter, bool doRaiseBack)
        {
            this.Log.Log("BMX NET", "Remote Events", "Triggered  " + anEventName);
            return this.EventManagmentCall("BMX EVENT RAISE^" + anEventName + "^" + aParameter + "^" + doRaiseBack.ToString().ToUpper() + "^");
        }

        protected int EventManagmentCall(String aCommand)
        {
            try
            {
                DataTable table = this.TableFromCommand(aCommand);
                return (int)table.Rows[0]["ERRORID"];
            }
            catch
            {
                return 99;
            }
        }
        
        delegate void UiCallPollForEvent();

        protected void PollForEvent()
        {
            if (this.InvokedControl != null && this.InvokedControl.InvokeRequired)
            {
                this.InvokedControl.Invoke(new UiCallPollForEvent(SafePollForEvent));
            }
            else
            {
                this.SafePollForEvent();
            }
        }

        protected void SafePollForEvent()
        {
            this.Log.Log("BMX NET", "Events", "Polling for event (" + this.PollingTimer.Interval.ToString() + ")");

            this.PollForTimerEvent();

            if (this.RpmsEvent != null || this.AsyncFutureLookup.Count > 0)
            {
                if (Monitor.TryEnter(this.RpcLock))
                {
                    Monitor.Exit(this.RpcLock);
                }
                else
                {
                    return;
                }
                DataTable events = this.TableFromCommand("BMX EVENT POLL");

                foreach (DataRow row in events.Rows)
                {
                    try
                    {
                        RemoteEventArgs args = new RemoteEventArgs();
                        args.EventType = row["EVENT"].ToString();
                        args.Details = row["PARAM"].ToString();
                        this.Log.Log("BMX NET", "Remote Events", "Raised: " + args.EventType + "(" + args.Details + ")");
                        if (args.EventType.Equals(this.AsyncFinishedEventName))
                        {
                            this.AsyncTableRequestReturned(args);
                        }
                        else 
                        {
                            this.TriggerRpmsEvent(args);
                        }
                    }
                    catch
                    {
                    }
                }
            }
        }

        
        delegate void UiCallTriggerRpmsEvent(RemoteEventArgs args);

        protected void TriggerRpmsEvent(RemoteEventArgs args)
        {
            if (this.RpmsEvent != null)
            {
                if (this.InvokedControl != null && this.InvokedControl.InvokeRequired)
                {
                    this.InvokedControl.Invoke(new UiCallTriggerRpmsEvent(SafeTriggerRpmsEvent), args);
                }
                else
                {
                    this.SafeTriggerRpmsEvent(args);
                }
            }
        }

        private void SafeTriggerRpmsEvent(RemoteEventArgs args)
        {
            this.RpmsEvent.Invoke(this, args);
        }

        private void AsyncTableRequestReturned(RemoteEventArgs args)
        {
            int key = 0;
            if (int.TryParse(M.Piece(args.Details,"~",1), out key) && this.AsyncFutureLookup.ContainsKey(key))
            {
                DataTableFuture future = this.AsyncFutureLookup[key];
                this.RemoveFromFutures(future);
                future.FutureHasReturned(this, M.Piece(args.Details, "~", 2));                
            }
        }

        private double _timerEventPollingMultiple = 1;

        public double TimerEventPollingMultiple
        {
            get { return _timerEventPollingMultiple; }
            set { _timerEventPollingMultiple = value; }
        }
    
        public DataTable VersionInfo
        {
            get { return this.TableFromCommand("BMX VERSION INFO^EBCI"); }//"+this.Broker.NameSpace); }
        }

        internal void Resume(BMXRemoteSessionPool bMXRemoteSessionPool)
        {
            this._isConnected = true;
        }
    
        internal void Suspend(BMXRemoteSessionPool bMXRemoteSessionPool)
        {
            this._isConnected = false;
            if (this.EventSourcePrimarySession != null)
            {
                this.EventSourcePrimarySession.Unsubscribe(this.AsyncFinishedEventName);  
                this.EventSourcePrimarySession.RpmsEvent -= new EventHandler<RemoteEventArgs>(aSession_RpmsEvent);
            }
            this.IsEventPollingEnabled = false;
        }

  

        #region RemoteSession Members

        private String _defaultTableName = "BmxResultTable";

        public String DefaultTableName
        {
            get { return _defaultTableName; }
            set { _defaultTableName = value; }
        }


        public DataTableFuture AsyncTableFromCommand(string aCommand)
        {
            return this.AsyncTableFromCommand(aCommand, this.AppContext);
        }

        public DataTableFuture AsyncTableFromCommand(string aCommand, string aContext)
        {
            return this.AsyncTableFromCommand(aCommand, new DataSet(), this.DefaultTableName, aContext);
        }

        public DataTableFuture AsyncTableFromCommand(string aCommand, DataSet aDataSet, string aTableName)
        {
            return this.AsyncTableFromCommand(aCommand, aDataSet, aTableName, this.AppContext);
        }

        private String _asyncFinishedEventName=null;

        public String AsyncFinishedEventName
        {
            get
            {
                if (_asyncFinishedEventName == null)
                {
                    _asyncFinishedEventName = "BMX ASYNC FINISHED " + this.Id.ToString();
                }
                return _asyncFinishedEventName;
            }

            set { _asyncFinishedEventName = value; }
        }

        private Dictionary<int, DataTableFuture> _asyncFutureLookup = new Dictionary<int, DataTableFuture>();

        protected Dictionary<int, DataTableFuture> AsyncFutureLookup
        {
            get { return _asyncFutureLookup; }
            set { _asyncFutureLookup = value; }
        }

        public DataTableFuture AsyncTableFromCommand(string aCommand, DataSet aDataSet, string aTableName, string aContext)
        {
            int errorCode = -1;

            try
            {
                if (!this.HasSubscribedForAsyncEvents)
                {
                    this.HasSubscribedForAsyncEvents = 0 == this.EventServices.Subscribe(this.AsyncFinishedEventName);
                }

                //replace ^'s in CommandString with $c(30)'s
                DataTable infoTable = this.TableFromCommand("BMX ASYNC QUEUE^"+aCommand.Replace('^', (char)30) + "^" + this.AsyncFinishedEventName,aContext);

                if (infoTable.Rows.Count == 1)
                {
                    DataRow queueingInfo = infoTable.Rows[0];
                    errorCode = int.Parse(queueingInfo["ERRORID"].ToString());
                    if (errorCode == 1)
                    {
                        int resultId = int.Parse(queueingInfo["PARAM"].ToString());

                        DataTableFuture answer = new DataTableFuture();
                        answer.InvokedControl = this.InvokedControl;
                        answer.ResultDataSet = aDataSet;
                        answer.ResultTableName = aTableName;
                        answer.ResultAppContext = aContext;
                        answer.ResultId = resultId;
                        this.AddToFutures(answer);
                        return answer;
                    }
                    else
                    {
                        throw new BMXNetException("Async call failed: " + queueingInfo["PARAM"].ToString());
                    }

                }
                else
                {
                    throw new BMXNetException("Error code not available.");
                }

            }
            catch (Exception problem)
            {
                String codeString = (errorCode == -1) ? "" : "Error code " + errorCode.ToString();
                throw new BMXNetException("BMX Unable to places async request on queue. " + codeString,problem);
            }
        }

        private void AddToFutures(DataTableFuture aFuture)
        {
            if (this.AsyncFutureLookup.ContainsKey(aFuture.ResultId))
            {
                DataTableFuture toCancel = this.AsyncFutureLookup[aFuture.ResultId];
                toCancel.Cancel();
            }
         
            lock (this.AsyncFutureLookup)
            {
                aFuture.Session = this;
                this.AsyncFutureLookup[aFuture.ResultId] = aFuture;
            }
     
        }

        internal void RemoveFromFutures(DataTableFuture aFuture)
        {
            if (this.AsyncFutureLookup.ContainsKey(aFuture.ResultId))
            {
                lock (this.AsyncFutureLookup)
                {
                    this.AsyncFutureLookup.Remove(aFuture.ResultId);
                }
            }
        }


        public DataTableFuture AsyncTableFromSQL(string sql)
        {
            return this.AsyncTableFromSQL(sql, this.AppContext);
        }

        public DataTableFuture AsyncTableFromSQL(string sql, string aContext)
        {
            return this.AsyncTableFromSQL(sql, new DataSet(),this.DefaultTableName, this.AppContext);
        }

        public DataTableFuture AsyncTableFromSQL(string sql, DataSet aDataSet, string aTableName)
        {
            return this.AsyncTableFromSQL(sql, aDataSet, aTableName, this.AppContext);
        }


        public DataTableFuture AsyncTableFromSQL(string sql, DataSet aDataSet, string aTableName, string aContext)
        {
            int errorCode = -1;

            try
            {
                if (!this.HasSubscribedForAsyncEvents)
                {
                    this.HasSubscribedForAsyncEvents = 0== this.EventServices.Subscribe(this.AsyncFinishedEventName);
                }
         
                //replace ^'s in CommandString with $c(30)'s
                DataTable infoTable = this.TableFromCommand("BMX ASYNC QUEUE^" + sql.Replace('^', (char)30) + "^" + this.AsyncFinishedEventName, aContext);

                DataRow queueingInfo = infoTable.Rows[0];
                errorCode = int.Parse(queueingInfo["ERRORID"].ToString());
                if (errorCode == 1)
                {
                    int resultId = int.Parse(queueingInfo["PARAM"].ToString());

                    DataTableFuture answer = new DataTableFuture();
                    answer.ResultDataSet = aDataSet;
                    answer.ResultTableName = aTableName;
                    answer.ResultAppContext = aContext;
                    answer.ResultId = resultId;
                    this.AddToFutures(answer);
                    return answer;
                }
                else
                {
                    throw new BMXNetException("Unexcepted error code");
                }


            }
            catch (Exception problem)
            {
                String codeString = (errorCode == -1) ? "" : "Error code " + errorCode.ToString();
                throw new BMXNetException("BMX Unable to places async request on queue. " + codeString, problem);
            }

               }


        public DataTableFuture AsyncTableFromRPC(string rpcCommand, string rpcParameter)
        {
            return this.AsyncTableFromRPC(rpcCommand, rpcParameter, this.AppContext);
        }


        public DataTableFuture AsyncTableFromRPC(string rpcCommand, string rpcParameter, string aContext)
        {
            return this.AsyncTableFromRPC(rpcParameter, rpcParameter, new DataSet(), this.DefaultTableName, aContext);
        }


        public DataTableFuture AsyncTableFromRPC(string rpcCommand, string rpcParameter, DataSet aDataSet, string aTableName)
        {
            return this.AsyncTableFromRPC(rpcCommand, rpcParameter, aDataSet, aTableName, this.AppContext);
        }

        public DataTableFuture AsyncTableFromRPC(string rpcCommand, string rpcParameter, DataSet aDataSet, string aTableName, string aContext)
        {
            String generatedCommand = this.TransmitRPC(rpcCommand, rpcParameter, aContext);

            return this.IsBmxAdoCommand(generatedCommand) ? this.AsyncTableFromCommand(generatedCommand, aDataSet, aTableName, aContext) : null;
     
        }

        #endregion

        private BMXNetRemoteSession _eventSourcePrimarySession = null;

        public BMXNetRemoteSession EventSourcePrimarySession
        {
            get { return _eventSourcePrimarySession; }
            set { _eventSourcePrimarySession = value; }
        }


        internal void PrimarySession(BMXNetRemoteSession aSession)
        {
            this.EventSourcePrimarySession = aSession;
        //    this.EventSourcePrimarySession.RpmsEvent += new EventHandler<RemoteEventArgs>(aSession_RpmsEvent);
         //   this.EventSourcePrimarySession.Subscribe(this.AsyncFinishedEventName);
        }

        void aSession_RpmsEvent(object sender, RemoteEventArgs e)
        {
            this.Log.Log("BMX NET", "Remote Events", "Raised: " + e.EventType + "(" + e.Details + ")");
            if (e.EventType.Equals(this.AsyncFinishedEventName))
            {
                this.AsyncTableRequestReturned(e);
            }
            else if (this.RpmsEvent != null)
            {
                this.RpmsEvent(this, e);
            }                    
        }

        public bool Lock(string lvnToLock, string addOrSubstract)
        {
            return SessionConnection.Lock(lvnToLock, addOrSubstract);
        }
    }
}