using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Data;
using System.Threading.Tasks;
using System.Threading;
using System.Diagnostics;
using System.Xml;

using System.Configuration;
using System.IO;

using DataAccess;

namespace RevampPhilipsService
{
    class CPhilipsXfer
    {
        /// <summary>
        /// used to signal the start of a thread to process a group of patients
        /// ie.: every n number of patients read from file 
        /// </summary>
        int m_nPatientGroup = 30;

        /// <summary>
        /// Local data dir
        /// </summary>
        string m_strLocalDataDir = String.Empty;

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public bool ImportPhilipsData()
        {
            #region local variables
            CLogEvent evt = new CLogEvent();

            CDataConnection conn = null;
            string strConnectionString = String.Empty;
            bool bAudit = true;

            //long lThreadGroups = 0;
            int nThreadGroupCount = 0;

            DateTime dtStart = System.DateTime.Now;
            DateTime dtEnd = System.DateTime.Now;

            string strMsg = String.Empty;

            #endregion

            //config settings
            try
            {
                string strUseCnnConfig = "DBConnString_" + ConfigurationManager.AppSettings["APP_TARGET_ENV"];
                strConnectionString = ConfigurationManager.ConnectionStrings[strUseCnnConfig].ConnectionString;
                bAudit = (ConfigurationManager.AppSettings["AUDIT"] == "1") ? true : false;
            }
            catch (ConfigurationErrorsException e)
            {
                if (e != null) { }
                evt.LogEvent(CLogEvent.EventMessage.nERROR_THREAD_CONFIG_FILE, EventLogEntryType.Error);
                return false;
            }

            //connect to data source
            conn = new CDataConnection();
            if (!conn.Connect(strConnectionString, (int)DataConnectionType.Oracle, bAudit))
            {
                evt.LogEvent(CLogEvent.EventMessage.nERROR_CONNECTING_DB, EventLogEntryType.Error);
                return false;
            }

            if (!LoadSystemSettings(ref conn, ref evt))
            {
                conn.Close();
                return false;
            }
            //close connection... it is not needed for now.
            conn.Close();

            //read all .xml files from the directory
            //get all the files in the directory
            string[] files = null;
            files = System.IO.Directory.GetFiles(m_strLocalDataDir);

            int nPatCount = 0;
            CPhilipsXferList xferList = new CPhilipsXferList();

            List<ManualResetEvent> handles = new List<ManualResetEvent>();
            nThreadGroupCount = 0;

            //loop over each file
            foreach (string file in files)
            {
                //get info for the file
                System.IO.FileInfo fi = new System.IO.FileInfo(file);

                //make sure it's a .xml file before processing
                if (fi.Extension.ToLower() == ".xml")
                {
                    //security - XML External Entities attacks benefit from an XML feature to build documents dynamically at the time of processing. 
                    XmlReaderSettings settings = new XmlReaderSettings();
                    settings.DtdProcessing = DtdProcessing.Prohibit;
                    settings.XmlResolver = null;

                    XmlTextReader reader = new XmlTextReader(fi.FullName);
                    while (!reader.EOF)
                    {
                        if (reader.NodeType == XmlNodeType.Element && reader.Name.ToLower() == "patient")
                        {

                            //thread off a database update for n patients
                            if (xferList.Count >= m_nPatientGroup)
                            {
                                handles.Add(new ManualResetEvent(false));
                                
                                //queue the list for thread transfer
                                xferList.QueueTransfer(handles[nThreadGroupCount]);

                                //make a new list for the next group
                                xferList = new CPhilipsXferList();

                                nThreadGroupCount++;
                            }

                            //new transfer item
                            CPhilipsXferItem xfer = new CPhilipsXferItem();

                            //load patient properties
                            xfer.PatID = reader.GetAttribute("ID");
                            xfer.PatIsActive = reader.GetAttribute("IsActive");
                            xfer.PatFacilityID = reader.GetAttribute("FacilityID");
                            xfer.PatFirstName = reader.GetAttribute("FirstName");
                            xfer.PatLastName = reader.GetAttribute("LastName");
                            xfer.PatMiddleName = reader.GetAttribute("MiddleName");
                            xfer.FileName = fi.Name;

                            //get one patient block of days and sessions
                            string strPatientXML = "<patient>" + reader.ReadInnerXml() + "</patient>";

                            XmlReader readerPatientInnerXML = XmlTextReader.Create(new System.IO.StringReader(strPatientXML), settings);
                            while (!readerPatientInnerXML.EOF)
                            {
                                if (readerPatientInnerXML.NodeType == XmlNodeType.Element)
                                {
                                    if (readerPatientInnerXML.Name.ToLower() == "prescriptions")
                                    {
                                        //pass the prescription for processing later
                                        xfer.PrescriptionsXML = readerPatientInnerXML.ReadOuterXml();
                                    }
                                    else if (readerPatientInnerXML.Name.ToLower() == "days")
                                    {
                                        //pass the days for processing later
                                        xfer.DaysXML = readerPatientInnerXML.ReadOuterXml();
                                    }
                                    else if (readerPatientInnerXML.Name.ToLower() == "sessions")
                                    {
                                        //pass the session xml for processing later
                                        xfer.SessionsXML = readerPatientInnerXML.ReadOuterXml();
                                    }
                                    else
                                    {
                                        readerPatientInnerXML.Read();
                                    }
                                }
                                else
                                {
                                    readerPatientInnerXML.Read();
                                }
                            }
                            readerPatientInnerXML.Close();


                            //add the item to the list of items to transfer
                            xferList.Add(xfer);
                            nPatCount++;

                        }
                        else
                        {
                            reader.Read();
                        }

                    }//reader.read
                    reader.Close();

                    //queue up any remaining patients
                    if (xferList != null)
                    {
                        if (xferList.Count > 0)
                        {
                            handles.Add(new ManualResetEvent(false));

                            //queue the list for thread transfer
                            xferList.QueueTransfer(handles[nThreadGroupCount]);

                            //make a new list for the next group
                            xferList = new CPhilipsXferList();

                            nThreadGroupCount++;
                        }
                    }

                    //save binary copy
                    SaveFileData(fi.Name, fi.FullName);

                    //Remove the file
                    try
                    {
                        System.IO.File.Delete(fi.FullName);
                    }
                    catch (Exception e)
                    {
                        if (e != null)
                        {
                            evt.LogEvent(CLogEvent.EventMessage.nERROR_CANNOT_DELETE_FILE, EventLogEntryType.Error);
                        }
                    }

                    
                }//its an .xml file

            }//loop over all files

            //Wait for all thread to finish and then display a final message...
            if (handles.Count > 0)
            {
                try
                {
                    WaitHandle.WaitAll(handles.ToArray());
                }
                catch (Exception e)
                {
                    if (e != null)
                    {
                        evt.LogEvent(CLogEvent.EventMessage.nERROR_WAITALL, EventLogEntryType.Information);
                    }
                }
            }

            ////at this point we are done processing all the files, but threads
            ////are still running in the background processing the data.
            //evt.EventMessages[(int)CLogEvent.EventMessage.nDETAIL_MESSAGE] = String.Format("File(s) proccessing completed. Threads are still running in background. Processing {0} records using {1} thread(s).", nPatCount, Math.Ceiling((decimal)nPatCount / m_nPatientGroup));
            //evt.LogEvent(CLogEvent.EventMessage.nDETAIL_MESSAGE, EventLogEntryType.Information);

            dtEnd = System.DateTime.Now;
            strMsg = "Data Import process completed at {0}. Total processing time was {1}. A total of {2} record(s) was/were processed using {3} thread(s).";
            strMsg = String.Format(strMsg, dtEnd.ToString(), (dtEnd - dtStart).ToString(@"dd\.hh\:mm\:ss\.ffffff"), nPatCount, nThreadGroupCount);
            evt.EventMessages[(int)CLogEvent.EventMessage.nDETAIL_MESSAGE] = strMsg;
            evt.LogEvent(CLogEvent.EventMessage.nDETAIL_MESSAGE, EventLogEntryType.Information);

            return true;

        }

        /// <summary>
        /// Load system settings
        /// </summary>
        /// <param name="conn"></param>
        /// <param name="evt"></param>
        /// <returns></returns>
        private bool LoadSystemSettings(ref CDataConnection conn,
                                        ref CLogEvent evt)
        {
            bool bSuccess = false;
            string strMsg = String.Empty;
            string strSVCListCapacity = String.Empty;

            //status info
            long lStatusCode = 0;
            string strStatusComment = String.Empty;

            //parameter list (BaseMstr.ASPSessionID, BaseMstr.ClientIP, BaseMstr.FXUserID);
            CDataParameterList plist = new CDataParameterList(String.Empty, String.Empty, 0);

            //
            CDataSet cds = new CDataSet();
            DataSet ds = cds.GetOracleDataSet(conn,
                                              "PCK_UTL_PARAMETERS.GetSystemParametersRS",
                                              plist,
                                              out lStatusCode,
                                              out strStatusComment);

            //check response
            if (lStatusCode == 0)
            {
                if (ds != null)
                {
                    foreach (DataTable table in ds.Tables)
                    {
                        //get the parameters
                        foreach (DataRow row in table.Rows)
                        {
                            string strParamName = String.Empty;
                            string strParamValue = String.Empty;

                            strParamName = CDataUtils2.GetDSStringValue(row, "PARAMETER_NAME");
                            strParamValue = CDataUtils2.GetDSStringValue(row, "PARAMETER_VALUE");

                            if (!String.IsNullOrEmpty(strParamName))
                            {
                                switch (strParamName)
                                {
                                    case "PHILIPS_SVC_LIST_CAPACITY":
                                        strSVCListCapacity = strParamValue;
                                        break;
                                }
                            }
                        }

                        //work with the parameters...
                        if (String.IsNullOrEmpty(strSVCListCapacity))
                        {
                            strMsg = "Unable to read list capacity setting from database. Switching to config file...";

                            //try loading the system settings from the config file
                            try
                            {
                                m_nPatientGroup = Convert.ToInt32(ConfigurationManager.AppSettings["ListCapacity"].ToString());
                                strMsg += Environment.NewLine + "[CONFIG] nListCapacity " + Convert.ToString(m_nPatientGroup);
                            }
                            catch (ConfigurationErrorsException e)
                            {
                                if (e != null)
                                {
                                    m_nPatientGroup = 500;
                                    strMsg += Environment.NewLine + "Unable to read list capacity from config file. Switching to default values...";
                                    strMsg += Environment.NewLine + "[DEFAULT] nListCapacity " + Convert.ToString(m_nPatientGroup);
                                }
                            }
                            catch (NullReferenceException e)
                            {
                                if (e != null)
                                {
                                    m_nPatientGroup = 500;
                                    strMsg += Environment.NewLine + "Unable to read list capacity from config file. Switching to default values...";
                                    strMsg += Environment.NewLine + "[DEFAULT] nListCapacity " + Convert.ToString(m_nPatientGroup);
                                }
                            }

                            evt.EventMessages[(int)CLogEvent.EventMessage.nDETAIL_MESSAGE] = strMsg;
                            evt.LogEvent(CLogEvent.EventMessage.nDETAIL_MESSAGE, EventLogEntryType.Warning);

                        }
                        else
                        {
                            m_nPatientGroup = Convert.ToInt32(strSVCListCapacity);
                        }

                        //try loading the system settings from the config file
                        try
                        {
                            m_strLocalDataDir = ConfigurationManager.ConnectionStrings["PhilipsDataDir"].ConnectionString;
                        }
                        catch (ConfigurationErrorsException e)
                        {
                            if (e != null)
                            {
                                lStatusCode = 1;
                                strMsg = "Local data dir setting not found.";
                            }
                        }
                        catch (NullReferenceException e)
                        {
                            if (e != null)
                            {
                                lStatusCode = 1;
                                strMsg = "Local data dir setting not found.";
                            }
                        }

                    }
                }
                else
                {
                    lStatusCode = 1;
                    strMsg = "System settings not found for Resmed AVX API.";
                }
            }
            else
            {
                lStatusCode = 1;
                strMsg = "An error occurred while loading system settings.";
            }

            if (lStatusCode != 0)
            {
                evt.EventMessages[(int)CLogEvent.EventMessage.nDETAIL_MESSAGE] = strMsg;
                evt.LogEvent(CLogEvent.EventMessage.nDETAIL_MESSAGE, EventLogEntryType.Error);
                bSuccess = false;
            }
            else
            {
                bSuccess = true;
            }

            return bSuccess;
        }

        /// <summary>
        /// Save a copy of the file in the DB.
        /// </summary>
        /// <param name="strFileName"></param>
        /// <param name="strFullName"></param>
        private void SaveFileData(string strFileName,
                                  string strFullName)
        {
            CLogEvent evt = new CLogEvent();

            //status info
            long lStatusCode = 0;
            string strStatusComment = String.Empty;

            CDataConnection conn = null;
            string strConnectionString = String.Empty;
            bool bAudit = true;

            //config settings
            try
            {
                string strUseCnnConfig = "DBConnString_" + ConfigurationManager.AppSettings["APP_TARGET_ENV"];
                strConnectionString = ConfigurationManager.ConnectionStrings[strUseCnnConfig].ConnectionString;
                bAudit = (ConfigurationManager.AppSettings["AUDIT"] == "1") ? true : false;
            }
            catch (ConfigurationErrorsException e)
            {
                if (e != null) { }
                evt.LogEvent(CLogEvent.EventMessage.nERROR_THREAD_CONFIG_FILE, EventLogEntryType.Error);
                return;
            }

            //connect to data source
            conn = new CDataConnection();
            if (!conn.Connect(strConnectionString, (int)DataConnectionType.Oracle, bAudit))
            {
                evt.LogEvent(CLogEvent.EventMessage.nERROR_CONNECTING_DB, EventLogEntryType.Error);
                return;
            }
            
            string strData = File.ReadAllText(strFullName);

            CDataParameterList pList = new CDataParameterList();
            pList.AddInputParameter("pi_nVendorID", 1); //Vendor ID = 1 = Philips
            pList.AddInputParameter("pi_vDataName", strFileName);
            pList.AddInputParameterCLOB("pi_clData", strData);

            conn.ExecuteOracleSP("PCK_XFER.PutDataFile",
                                  pList,
                                  out lStatusCode,
                                  out strStatusComment);

            
            if (lStatusCode > 0)
            {
                evt.EventMessages[(int)CLogEvent.EventMessage.nDETAIL_MESSAGE] = strStatusComment;
                evt.LogEvent(CLogEvent.EventMessage.nDETAIL_MESSAGE, EventLogEntryType.Error);

                evt.LogEvent(CLogEvent.EventMessage.nERROR_SAVE_DATAFILE, EventLogEntryType.Error);
                return;            
            }

            //close connection.
            conn.Close();
        }
    }
}