using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using IndianHealthService.BMXNet.Services;
using System.Windows.Forms;

namespace IndianHealthService.BMXNet.Ado
{
    /// <summary>
    /// DataTableFutures are created synchrounously when Async calls are made on a RemoteSession.
    /// A DataTabelFuture is used to manage each async call and has a variety of methods to support
    /// the relativelty long and dynamic lifecycle and an Async call.
    /// </summary>
    /// <remarks>
    /// Beware that all return communications are not on the UI thread unless an InvokedControl
    /// is specify.
    /// </remarks>
    public class DataTableFuture
    {

        /// <summary>
        /// Triggered with the async returns, see DataTableFutureEventArgs to know if there
        /// is a result waiting.  It's very common to hook the Returned event after each Async call and
        /// then unhook it when the Returned or Aborted calls return.
        /// </summary>
        public event EventHandler<DataTableFutureEventArgs> Returned;
        
        /// <summary>
        /// The async call was aborted by either the server or the client.  It's very common to hook the Aborted event after each Async call and
        /// then unhook it when the Returned or Aborted calls return.
        /// </summary>
        /// </summary>
        public event EventHandler<DataTableFutureEventArgs> Aborted;

        private Control _invokedControl = null;

        
        /// If the InvokedControl is set to a valid Form or Control object (non-displosed), the
        /// DataTabletFuture will trigger events on the UI thread using Invoke()
        public Control InvokedControl
        {
            get { return _invokedControl; }
            set { _invokedControl = value; }
        }

        private bool _isAutoFetchEnabled = true;

       /// <summary>
       /// If set to true the result will be fetched before the Returned event is called, otherwise
       /// the Returned event will be triggered and the application code needs to call PostFetch() to retrieve
       /// the result.  True by default.
       /// </summary>
        public bool IsAutoFetchEnabled
        {
            get { return _isAutoFetchEnabled; }
            set { _isAutoFetchEnabled = value; }
        }

        private Exception _errorException = null;


        /// <summary>
        /// Access to any exceptions that occur during Async call.
        /// </summary>
        public Exception ErrorException
        {
            get { return _errorException; }
            set { _errorException = value; }
        }

        private int _resultId = 0;

        internal int ResultId
        {
            get { return _resultId; }
            set { _resultId = value; }
        }

        private String _resultKey = null;

        protected String ResultKey
        {
            get { return _resultKey; }
            set { _resultKey = value; }
        }


        private DataTable _result = null;

        /// <summary>
        /// The result table if any, or null.
        /// </summary>
        public DataTable Result
        {
            get { return _result; }
            set { _result = value; }
        }

        private bool _wasCancelled = false;

        /// <summary>
        /// Answer true if canceled.  It's common that a dialog would allow the user to cancel the
        /// Async call and the Dialog that is holding on the the Future will cancel the Future.  Cancelling
        /// the Future does not stop the server-side processing but when the server returns nothing will happen.
        /// </summary>
        public bool WasCancelled
        {
            get { return _wasCancelled; }
            set { _wasCancelled = value; }
        }

        private bool _hasTimedOut = false;

        /// <summary>
        /// Answer true if the Async call has timeed-out (a time-based version of Cancel)
        /// </summary>
        public bool HasTimedOut
        {
            get { return _hasTimedOut; }
            set { _hasTimedOut = value; }
        }

        private bool _hasReturned = false;


        /// <summary>
        /// Answer true if the Async call has completed on the server and that a result is ready.
        /// </summary>
        public bool HasReturned
        {
            get { return _hasReturned; }
            set
            {
                _hasReturned = value;
                this.FutureTime = DateTime.Now;
            }
        }


        private bool _hasData = false;

        /// <summary>
        /// Answer true if the data has been fetched from the server and is ready to be used by the application.
        /// </summary>
        public bool HasData
        {
            get { return _hasData; }
            set { _hasData = value; }
        }

        private DateTime _presentTime = DateTime.Now;

        /// <summary>
        /// The time the Future was created.
        /// </summary>
        public DateTime PresentTime
        {
            get { return _presentTime; }
            set { _presentTime = value; }
        }

        private DateTime? _futureTime = null;

        /// <summary>
        /// The time the async call returned (the future)
        /// </summary>
        public DateTime? FutureTime
        {
            get { return _futureTime; }
            set { _futureTime = value; }
        }

        /// <summary>
        /// How long in milliseconds the Async call took to complete.
        /// </summary>
        public double WaitTime
        {
            get
            {
                return ((this.FutureTime.HasValue ? this.FutureTime.Value : DateTime.Now) - this.PresentTime).TotalMilliseconds;
            }
        }

        private int _maxWaitTime = 60 * 60 * 24;

        /// <summary>
        /// Number of seconds to wait for an async response before returning without a response.
        /// </summary>
        public int MaxWaitTime
        {
            get { return _maxWaitTime; }
            set { _maxWaitTime = value; }
        }

        private BMXNetRemoteSession _session = null;

        internal BMXNetRemoteSession Session
        {
            get { return _session; }
            set { _session = value; }
        }

        /// <summary>
        /// Method to cancel the Async call.  This does not stop the server-side processing.  The Aborted event
        /// will be called immediately.
        /// </summary>
        public void Cancel()
        {         
            this.WasCancelled = true;            
            this.Session.RemoveFromFutures(this);
            this.TriggerAbortedEvent();
        }

        delegate void UiCall();

        protected void TriggerAbortedEvent()
        {
            if (this.Aborted != null)
            {
                if (this.InvokedControl != null && this.InvokedControl.InvokeRequired)
                {
                    this.InvokedControl.Invoke(new UiCall(UiTriggerAbortedEvent));
                }
                else
                {
                    this.UiTriggerAbortedEvent();
                }
            }
        }

        private void UiTriggerAbortedEvent()
        {
            DataTableFutureEventArgs args = new DataTableFutureEventArgs();
            args.Future = this;
            this.Aborted(this, args);
        }

        protected void TriggerReturnedEvent()
        {
            if (this.Returned != null)
            {
                if (this.InvokedControl != null && this.InvokedControl.InvokeRequired)
                {
                    this.InvokedControl.Invoke(new UiCall(UiTriggerReturnedEvent));
                }
                else
                {
                    this.UiTriggerReturnedEvent();
                }
            }
        }

        private void UiTriggerReturnedEvent()
        {
            DataTableFutureEventArgs args = new DataTableFutureEventArgs();
            args.Future = this;
            this.Returned(this, args);
        }


        internal void FetchResults(BMXNetRemoteSession aSession)
        {
            if (!this.HasReturned || this.HasTimedOut || this.WasCancelled)
                return;

            try
            {
                this.Result = aSession.TableFromCommand("BMX ASYNC GET^" + this.ResultKey, this.ResultDataSet, this.ResultTableName, this.ResultAppContext);
                this.HasData = true;
            }
            catch (Exception problem)
            {
                this.ErrorException = problem;
                this.Cancel();
            }
        }

        private BMXNetRemoteSession _fetchingSession = null;

        internal BMXNetRemoteSession FetchingSession
        {
            get { return _fetchingSession; }
            set { _fetchingSession = value; }
        }

        /// <summary>
        /// If IsAutoFetchEnabled is false, then the results must be fetched synchrounously useding PostFetch().
        /// Call PostFetch() once and check if HasData is true.  If not true, there is was error related to the
        /// async call.
        /// </summary>
        public void PostFetch()
        {
            
            this.FetchResults(this.FetchingSession);
        }

        internal void FutureHasReturned(BMXNetRemoteSession aSession, String resultsKey)
        {            
            //TODO:If cancled, BMX ASYNC GET to clean cancelle ditem
            if (this.HasReturned) 
                return;

            this.ResultKey = resultsKey;
            this.HasReturned = true;

            if (!this.HasTimedOut && !this.WasCancelled)
            {
                if (this.IsAutoFetchEnabled)
                {
                    this.FetchResults(aSession);
                }
                else
                {
                    this.FetchingSession = aSession;
                }
            }

            this.TriggerReturnedEvent();         
        }

        private String _resultTableName = null;

        public String ResultTableName
        {
            get { return _resultTableName; }
            set { _resultTableName = value; }
        }

        private DataSet _resultDataSet = null;

        /// <summary>
        /// The data set the result data table is in.
        /// </summary>
        public DataSet ResultDataSet
        {
            get { return _resultDataSet; }
            set { _resultDataSet = value; }
        }

        private String _resultAppContext = null;

        /// <summary>
        /// The AppContext that the async call was made with
        /// </summary>
        public String ResultAppContext
        {
            get { return _resultAppContext; }
            set { _resultAppContext = value; }
        }


    }
}
