using System;
using System.Collections.Generic;
using System.Text;
using IndianHealthService.BMXNet.Model;
using IndianHealthService.BMXNet.WinForm.Model;
using IndianHealthService.BMXNet.WinForm.Services;
using IndianHealthService.BMXNet.WinForm.Configuration;
using IndianHealthService.BMXNet.Net;
using System.IO;
using System.IO.IsolatedStorage;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;
using System.Reflection;
using System.Security.Policy;
using System.Xml;
using System.Data;
using System.Globalization;
using System.Windows.Forms;
using IndianHealthService.BMXNet.Forms;
using IndianHealthService.BMXNet.Ado;
using System.Threading;
using IndianHealthService.BMXNet.Services;

namespace IndianHealthService.BMXNet.WinForm
{
    /// <summary>
    /// Instance manages the context/access to RPMS 
    /// - Configuration information
    /// - Login entry points
    /// - External changes to patient context
    /// </summary>
    /// 
    public class WinFramework : Log
    {
        private Log _log = new NullLog();

        /// <summary>
        /// Logger to be used by all BMX objects.         
        /// </summary>
        public Log Log
        {
            get { return _log; }
            set { _log = value; }
        }

        public static WinFramework EasyLogin(IWin32Window aUiOwnerForPositioningDialog, params string[] configKeys)
        {
            WinFramework easyFramework = CreateWithNetworkBroker(true, new NullLog());
            easyFramework.LoadConnectionSpecs(LocalPersistentStore.CreateDefaultStorage(true), configKeys);

            LoginProcess login = easyFramework.CreateLoginProcess();

            if (login.HasDefaultConnectionWithUseWindowsAuth && login.AttemptWindowsAuthLogin())
            {
                login.AttemptWindowsAuthLogin();
            }

            if (!login.WasLoginAttempted || !login.WasSuccessful)
            {
                login.AttemptUserInputLogin("RPMS Login", 3, true, aUiOwnerForPositioningDialog);
            }
            easyFramework.Login = login;

            return easyFramework;
        }


        public RemoteSessionPool RemoteSessionPool
        {
            get { return this.Broker.RemoteSessionPool; }
        }
        

        public static WinFramework CreateWithNetworkBroker()
        {
            return WinFramework.CreateWithNetworkBroker(false, new NullLog());
        }

        public static WinFramework CreateWithNetworkBroker(bool parseCommandLineArguments)
        {
            return WinFramework.CreateWithNetworkBroker(parseCommandLineArguments, new NullLog());
        }

        public static WinFramework CreateWithNetworkBroker(bool parseCommandLineArguments, Log aLogger)
        {
            WinFramework framework = new WinFramework();
            framework.Log = aLogger;

            framework.HardCodedSettings = new Settings();

            if (parseCommandLineArguments)
            {
                Settings commandLineSettings = new CommandLineArguments().ParseSlashKeyValue(Environment.GetCommandLineArgs(), true);
                commandLineSettings.BackupSettings = framework.HardCodedSettings;
                framework.BootStrapSettings = commandLineSettings;
            }
            else
            {
                framework.BootStrapSettings = framework.HardCodedSettings;
            }

            return framework;
        }

        /// <summary>
        /// Ping the server, will reset read-timeout
        /// </summary>
        /// <returns></returns>
        public double ImHereServer()
        {
            return this.SocketBroker.ImHereServer();
        }

        private Settings _hardCodedSettings = null;

        public Settings HardCodedSettings
        {
            get { return _hardCodedSettings; }
            set { _hardCodedSettings = value; }
        }

        private Settings _bootStrapSettings = new Settings();

        public Settings BootStrapSettings
        {
            get { return _bootStrapSettings; }
            set { _bootStrapSettings = value; }
        }
        
        public Settings Settengs
        {
            get { return this.BootStrapSettings; }
        }

        private RpmsConnectionSettings _connectionSettings = null;

        public RpmsConnectionSettings ConnectionSettings
        {
            get { return _connectionSettings; }
            set { _connectionSettings = value; }
        }


        private BMXNetBroker _socketBroker = null;

        internal BMXNetBroker SocketBroker
        {
            get
            {
                if (_socketBroker == null)
                {
                    if (this.BootStrapSettings.Get("UseBMXNetSocketBroker", true))
                    {
                        this.SocketBroker = BMXNetBroker.CreateSocketBroker();
                        this.SocketBroker.Log = (Log)this;
                    }
                }
                return _socketBroker;
            }
            set { _socketBroker = value; }
        }


        private BMXNetBroker _broker = null;

        internal BMXNetBroker Broker
        {
            get
            {
                if (_broker == null)
                {
                    return this.SocketBroker;
                }

                return _broker;
            }
            set { _broker = value; }
        }


        private LoginProcess _login = null;

        public LoginProcess Login
        {
            get { return _login; }
            set
            {
                if (this.Login != null)
                {
                    this.Close();
                }

                _login = value;

                if (this.Login != null)
                {
                    if (this.Login.WasSuccessful)
                    {
                        this.Start(this.Broker.PrimaryRemoteSession);
                    }

                }
            }
        }


        private RemoteSession _primaryRemoteSession = null;

        public RemoteSession PrimaryRemoteSession
        {
            get { return _primaryRemoteSession; }
            set { _primaryRemoteSession = value; }
        }

  

   
        public LocalSession LocalSession
        {
            get { return (LocalSession)this.FrameworkSession; }
        }

        private WinSession _brokerSession = null;

        internal WinSession FrameworkSession
        {
            get { return _brokerSession; }
            set { _brokerSession = value; }
        }

        private List<WinSession> _sessions = new List<WinSession>();

        internal List<WinSession> Sessions
        {
            get { return _sessions; }
            set { _sessions = value; }
        }

        public static WinFramework OpenOn(RemoteSession aSession)
        {
            WinFramework framework = new WinFramework();

            framework.Start(aSession);

            return framework;
        }

        public void TriggerLocalEvent(String anEvent, String eventInfo)
        {
            this.FrameworkSession.TriggerEvent(anEvent, eventInfo);
        }

        internal void Start(RemoteSession aSession)
        {
            this.PrimaryRemoteSession = aSession;

            WinUser user = new WinUser();
            user.RemoteSession= this.PrimaryRemoteSession;
            user.Name = ((BMXNetRemoteSession)aSession).AuthenicatedUserName;
            user.Ien = ((BMXNetRemoteSession)aSession).AuthenicatedDuz;

            this._user = user;
            this.Broker.User = user;

            WinSession session = new WinSession();
            session.Framework = this;

            WinContext context = new WinContext();
            context.User = user;

            session.Open(this, context);

            this.Sessions.Add(session);
            this.FrameworkSession = session;

            context.ChangePatient(this.FrameworkSession.Context.Patient);
            context.ChangeVisit(this.FrameworkSession.Context.Visit);
        }

        private EventRegistry _eventRegistry = new EventRegistry();

        internal EventRegistry EventRegistry
        {
            get { return _eventRegistry; }
            set { _eventRegistry = value; }
        }

        public bool ChangePatient(Patient aPatient, bool force)
        {
            bool canChange = true;

            foreach (WinSession each in this.Sessions)
            {
                canChange = canChange && each.DesktopContext.RequestCanChangePatient(aPatient,force);
            }

            if (canChange)
            {
                foreach (WinSession each in this.Sessions)
                {
                    each.DesktopContext.ChangePatient(aPatient);
                }
            }

            return canChange;
        }

        public bool ChangeVisit(Visit aVisit, bool force)
        {
            bool canChange = true;

            foreach (WinSession each in this.Sessions)
            {
                canChange = canChange && each.DesktopContext.RequestCanChangeVisit(aVisit, force);
            }

            if (canChange)
            {
                foreach (WinSession each in this.Sessions)
                {
                    each.DesktopContext.ChangeVisit(aVisit);
                }
            }
            return canChange;
        }

        public bool ChangeVisit(Visit aVisit)
        {
            return this.ChangeVisit(aVisit, false);
        }

        public bool ChangePatient(Patient aPatient)
        {
            return this.ChangePatient(aPatient, false);
        }

        public Context Context
        {
            get
            {
                return this.FrameworkSession.Context;
            }
        }

        internal ChangableContext ChangableContext
        {
            get
            {
                return (ChangableContext)this.FrameworkSession.Context;
            }
        }
        private User _user = null;

        public User User
        {
            get { return _user; }
        }

        public DataTable DivisionTable(User aUser)
        {

            return this.DivisionTable(aUser.Duz);
        }

        public DataTable DivisionTable(String anIen)
        {
            return this.PrimaryRemoteSession.TableFromCommand("BMXGetFacRS^" + anIen);
        }

        void future_Returned(object sender, DataTableFutureEventArgs e)
        {
            DataTable peek = e.Future.Result;
        }



        internal List<SelectableDivision> Divisions(User aUser)
        {
            return this.Divisions(aUser.Duz);
        }

        internal List<SelectableDivision> Divisions(String anIen)
        {
            DataTable data = this.DivisionTable(anIen);
            List<SelectableDivision> answer = new List<SelectableDivision>();

            foreach (DataRow each in data.Rows)
            {
                WinDivision division = new WinDivision();
                division.Ien = each["FACILITY_IEN"].ToString();
                division.Name = each["FACILITY_NAME"].ToString();
                division.MostRecentLookup = "1".Equals(each["MOST_RECENT_LOOKUP"].ToString());
                answer.Add((SelectableDivision)division);
            }

            return answer;
        }

        public bool SetDivision(String aDivisionIen)
        {

            DataTable data = this.PrimaryRemoteSession.TableFromCommand("BMXSetFac^" + aDivisionIen);

            if (data.Rows.Count == 1)
            {
                String duz2 = data.Rows[0]["FACILITY_IEN"].ToString();
                if ("0".Equals(duz2))
                {
                    return false;
                }
                else
                {
                    WinDivision division = new WinDivision();
                    division.Ien = duz2;
                    division.Name = data.Rows[0]["FACILITY_NAME"].ToString();
                    (this.User as WinUser).Division = division;

                    this.ChangePatient(null);

                    return true;
                }
            }
            else
            {
                return false;
            }
        }


        public bool SetDivision(Division aDivision)
        {
            return this.SetDivision(aDivision.Ien);
        }

        /// <summary>
        /// Answer true if changed, false if the same
        /// </summary>
        /// <param name="aDialogTitle"></param>
        /// <param name="aUiOwnerForPositioningDialog"></param>
        /// <returns></returns>
        public bool AttemptUserInputSetDivision(string aDialogTitle, IWin32Window aUiOwnerForPositioningDialog)
        {
            ChangeDivisionDialog dialog = new ChangeDivisionDialog();
            dialog.Divisions = this.Divisions(this.User);

            if (aDialogTitle != null)
            {
                dialog.Text = aDialogTitle;
            }

            if (aUiOwnerForPositioningDialog == null)
            {
                dialog.StartPosition = FormStartPosition.CenterScreen;
            }

            if (dialog.ShowDialog(aUiOwnerForPositioningDialog) == DialogResult.OK)
            {
                //This isn't really an optimization but rather 
                if ((this.User.Division != null) && dialog.SelectedDivision.Equals(this.User.Division))
                {
                    return true;
                }

                if (this.SetDivision(dialog.SelectedDivision))
                {
                    return true;
                }
                else
                {
                    MessageBox.Show(aUiOwnerForPositioningDialog, "Unable to set division.  Please try again or contact your helpdesk.", "RPMS RPC Error");
                    return this.AttemptUserInputSetDivision(aDialogTitle, aUiOwnerForPositioningDialog);
                }
            }
            else
            {
                return false;
            }

        }
        public void Close()
        {
            if (this.Broker != null)
            {
                this.Broker.Close();
            }
            if (this.FrameworkSession != null)
            {
                this.FrameworkSession.Close();
            }
        }


        protected object GetTypeFromEvidence(Evidence evidence, Type aType)
        {
            foreach (object each in evidence)
            {
                if (each.GetType() == aType)
                {
                    return each;
                }
            }
            return null;
        }

        protected object GetAssemblyIdentityFromEvidence(Evidence evidence)
        {
            return
                 GetTypeFromEvidence(evidence, typeof(Publisher))
                 ??
                 GetTypeFromEvidence(evidence, typeof(StrongName))
                 ??
                 GetTypeFromEvidence(evidence, typeof(Url));
        }

        protected RpmsConnectionSpec RetrievePriorBMXConfiguration(Assembly priorAssemblyReadFromFile)
        {
            try
            {
                object identity = this.GetAssemblyIdentityFromEvidence(priorAssemblyReadFromFile.Evidence);

                using (IsolatedStorageFile isStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly, null, identity))
                {
                    string sFileName = "mserver0200.dat";
                    String[] peeks = isStore.GetFileNames("*.*");
                    using (Stream stStorage = new IsolatedStorageFileStream(sFileName, FileMode.Open, isStore))
                    {
                        XmlDocument doc = new XmlDocument();
                        doc.Load(stStorage);

                        //Deserializing is yucky because SOAP XML is assembly version specific
                        //Peek into doc and grab values
                        RpmsConnectionSpec spec = new RpmsConnectionSpec();
                        spec.Name = "Current Connection";
                        spec.IsDefault = true;
                        spec.Server = doc.GetElementsByTagName("m_sAddress").Item(0).InnerText;
                        int port = 0;
                        int.TryParse(doc.GetElementsByTagName("m_nPort").Item(0).InnerText, out port);
                        spec.Port = port;
                        spec.NameSpace = doc.GetElementsByTagName("m_sNamespace").Item(0).InnerText;
                        return spec;
                    }
                }
            }
            catch 
            {
                return null;
            }
        }

        public void LoadConnectionSpecsAndMergeWithExistingBMXConfiguration(Assembly priorAssemblyReadFromFile, PersistentStore aPersistentStore, params string[] keys)
        {
            this.LoadConnectionSpecs(aPersistentStore, keys);

            if (this.ConnectionSettings.ConnectionSpecs.Count == 0)
            {
                RpmsConnectionSpec spec = this.RetrievePriorBMXConfiguration(priorAssemblyReadFromFile);
                if (spec != null)
                {
                    this.ConnectionSettings.AddConnectionSpec(spec);
                    this.ConnectionSettings.Save();
                }
            }
        }

        public void LoadSettings(PersistentStore aPersistentStore, params string[] keys)
        {
            Settings settings = new Settings();
            settings.Store = aPersistentStore;
            settings.StorageKeys = keys;
            settings.WillPersistFutureSets = true;
            settings.Load();
            
            settings.BackupSettings = this.HardCodedSettings;
            this.BootStrapSettings.BackupSettings = settings;
        }


        public void LoadConnectionSpecs(PersistentStore aPersistentStore, params string[] keys)
        {
            RpmsConnectionSettings settings = new RpmsConnectionSettings();
            settings.Store = aPersistentStore;
            settings.StorageKeys = keys;
            settings.Load();

            //Look for:  /rpms:Server,Port,[Connection Name],[Namespace]
            String[] commandLineSpecParts = this.BootStrapSettings.Get("rpms", "").Split(new char[] { ',' });
            if (commandLineSpecParts.Length >= 2)
            {
                RpmsConnectionSpec spec = new RpmsConnectionSpec();
                spec.Name = "[Command-Line Connection]";
                spec.Server = commandLineSpecParts[0];
                spec.UseDefaultNamespace = true;
                spec.UseWindowsAuthentication = false;

                int port = 0;
                if (int.TryParse(commandLineSpecParts[1], out port))
                {
                    spec.Port = port;
                    settings.CommandLineConnectionSpec = spec;
                }

                if (commandLineSpecParts.Length >= 3)
                {
                    spec.Name = commandLineSpecParts[2];

                    if (commandLineSpecParts.Length >= 4)
                    {
                        spec.NameSpace = commandLineSpecParts[3];
                        spec.UseDefaultNamespace = spec.NameSpace.Length == 0;
                    }
                    else
                    {
                        spec.UseDefaultNamespace = true;
                    }

                    if (commandLineSpecParts.Length >= 5)
                    {
                        spec.UseWindowsAuthentication= commandLineSpecParts[4].Equals("Win",StringComparison.InvariantCultureIgnoreCase);

                        if (commandLineSpecParts.Length >= 6)
                        {
                            int timeOut = 0;
                            if (int.TryParse(commandLineSpecParts[5], out timeOut))
                            {
                                spec.ReceiveTimeout= timeOut;
                            }

                            if (commandLineSpecParts.Length >= 7)
                            {
                                if (int.TryParse(commandLineSpecParts[6], out timeOut))
                                {
                                    spec.SendTimeout= timeOut;
                                }
                            }
                        }
                    }
                }

                
            }
            String connectionName = this.BootStrapSettings.Get("connection", "").Trim();
            if (connectionName.Length > 0)
            {
                foreach (RpmsConnectionSpec each in settings.ConnectionSpecs)
                {
                    if (each.Name.Equals(connectionName, StringComparison.InvariantCultureIgnoreCase))
                    {
                        settings.DefaultConnectionSpec = each;
                        break;
                    }
                }
            }

            this.ConnectionSettings = settings;
        }

        public LoginProcess CreateLoginProcess()
        {
            return new LoginProcess(this);

        }

        public Patient FindPatient(String aSearchString)
        {
            this.Log.Log("BMX WIN", "Info", "FindPatient > " + aSearchString);
            List<Patient> patients = this.FindPatients(aSearchString, 2);

            return patients.Count == 1 ? patients[0] : null;
        }


        public List<Patient> FindPatients(String aSearchString, int max)
        {
            this.Log.Log("BMX WIN", "Info", "FindPatients > " + aSearchString + ", " + max.ToString());
            DataTable data = this.FindPatientsAsTable(aSearchString, max);

            //this.Bmx.TableFromCommand("BMX FIND PATIENT^" + aSearchString.ToUpper() + "|" + max.ToString()); ;

            List<Patient> answer = new List<Patient>();

            foreach (DataRow each in data.Rows)
            {
                WinPatient patient = new WinPatient();
                patient.Ien = each["IEN"].ToString();
                patient.PatientName = each["PATIENTNAME"].ToString();
                patient.Sex = each["SEX"].ToString();
                patient.HealthRecordNumber = each["CHART"].ToString();
                patient.Age = int.Parse(each["AGE"].ToString());
                patient.Ssn = each["SSN"].ToString();
                //patient.????= each["CLASSBEN"].ToString();
                //patient.????= each["REG"].ToString();
                //patient.????= each["LASTUPDATE"].ToString();


                DateTime dob;
                if (DateTime.TryParse(each["DOB"].ToString(), out dob))
                {
                    patient.Dob = dob;
                };

                answer.Add(patient);
            }

            return answer;
        }

        public DataTable FindPatientsAsTable(String aSearchString, int max)
        {
            this.Log.Log("BMX WIN", "Info", "FindPatientsAsTable > " + aSearchString + ", " + max.ToString());
            return this.PrimaryRemoteSession.TableFromCommand("BMX FIND PATIENT^" + aSearchString + "|" + max.ToString());
        }

        public DataTable VisitsAsTable(Patient aPatient, int aMax)
        {
            return this.VisitsAsTable(aPatient.Ien, aMax);
        }

        public DataTable VisitsAsTable(String aDFN, int aMax)
        {
            return this.PrimaryRemoteSession.TableFromCommand("BMX FIND VISIT^" + aDFN + "|" + aMax.ToString()); ;
        }

        public List<Visit> Visits(Patient aPatient, int aMax)
        {
            return this.Visits(aPatient.Ien, aMax);
        }


        public List<Visit> Visits(String aDFN, int aMax)
        {

            DataTable data = this.VisitsAsTable(aDFN, aMax);

            List<Visit> answer = new List<Visit>();

            foreach (DataRow each in data.Rows)
            {
                WinVisit visit = new WinVisit();
                visit.Ien = each["VISIT_IEN"].ToString();
                visit.ProviderName = each["PRIMARY_PROVIDER"].ToString();
                visit.Clinic = each["CLINIC"].ToString();
                visit.LocationName = each["LOCATION"].ToString();
                visit.ServiceCategory = each["SERVICE CATEGORY"].ToString();
                visit.VisitType = each["VISIT_TYPE"].ToString();

                DateTime date;

                String[] formats = new string[] { "MMM dd, yyyy@HH:mm", "MMM d, yyyy@HH:mm", "MMM d,yyyy@HH:mm", "MMM d, yyyy@H:mm", "MMM d,yyyy@H:mm", "MMM dd,yyyy@HH:mm" };
                String dateText = each["TIMESTAMP"].ToString();
                if (DateTime.TryParseExact(dateText, formats, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out date))
                {
                    visit.DateTime = date;
                }
                else if (DateTime.TryParse(dateText, out date))
                {
                    visit.DateTime = date;
                }


                answer.Add(visit);
            }

            return answer;
        }

        internal void Close(WinSession winSession)
        {
            if (this.Broker != null)
            {
                this.Broker.Close();
            }
        }

        #region ILog Members

        public bool IsLogging
        {
            get
            {
                return this.Log.IsLogging;
            }
            set
            {
                this.Log.IsLogging = value;
            }
        }

        void Log.Log(string aClass, string aCategory, params string[] lines)
        {
            this.Log.Log(aClass, aCategory, lines);
        }

        void Log.Log(string aClass, string aCategory, Exception anException, params string[] lines)
        {
            this.Log.Log(aClass, aCategory, anException, lines);
        }

        #endregion
    }
}