using System;
using System.Collections.Generic;
using System.Linq;
using XdsObjects;
using XdsObjects.Enums;
using System.Data.Entity;
using System.Text.RegularExpressions;

namespace XdsRegistry
{
    partial class Registry
    {
        public delegate void LogMessageHandler(string msg);
        public event LogMessageHandler LogMessageEvent;

        #region "Varibles and Constants"
        // the Server object which handles the incoming Register and StoredQuery requests
        XdsSoapServer server;

        // The registry address - where we register the received documents
        const string registryURI = "http://localhost:8090/MyRegistry";

        // Home Community ID
        const string HomeCommunityID = "1.2.3.4.5.6.7";

        static string DummyContext = "";        
        static int MaxLeafClassResults = 25;
        static int MaxObjectRefResults = 200;
        #endregion

        internal void StartListen()
        {
            XdsGlobal.LogToFile("C:\\Logs", LogLevel.All, 60);
            server = new XdsSoapServer();
            server.RegisterReceived += server_RegisterReceived;
            server.RegistryStoredQueryReceived += server_RegistryStoredQueryReceived;
            server.Listen(registryURI);
            server.StructureIncomingSubmissionSet = false;
        }        

        internal void StopListen()
        {
            if (server != null)
                server.UnListenAll();
        }

        #region "Server Events"
        XdsQueryResponse server_RegistryStoredQueryReceived(XdsQueryRequest Request, XdsRequestInfo RequestInfo)
        {
            LogMessageEvent("Registry stored query received");

            try
            {
                switch (Request.QueryType)
                {
                    case QueryType.FindDocuments:
                        return HandleFindDocumentQuery(Request);

                    case QueryType.FindSubmissionSets:
                        return HandleFindSubmissionSetQuery(Request);

                    case QueryType.GetAll:
                        return HandleGetAll(Request);

                    case QueryType.GetAssociations:
                        return HandleGetAssociations(Request);

                    case QueryType.GetSubmissionSets:
                        return HandleGetSubmissionSets(Request);

                    case QueryType.GetDocuments:
                        return HandleGetDocuments(Request);

                    case QueryType.GetRelatedDocuments:
                        return HandleGetRelatedDocuments(Request);

                    case QueryType.FindFolders:
                        return HandleFindFolders(Request);

                    case QueryType.GetFolders:
                        return HandleGetFolders(Request);

                    case QueryType.GetFoldersForDocument:
                        return HandleGetFoldersForDocument(Request);

                    case QueryType.GetFolderAndContents:
                        return HandleGetFolderAndContents(Request);

                    case QueryType.GetSubmissionSetAndContents:
                        return HandleGetSubmissionSetAndContents(Request);

                    case QueryType.GetDocumentsAndAssociations:
                        return HandleGetDocumentsAndAssociations(Request);

                    default:
                        return new XdsQueryResponse(XdsErrorCode.XDSUnknownStoredQuery, DummyContext);
                }
            }
            catch (Exception e)
            {
                return new XdsQueryResponse(e, XdsErrorCode.GeneralException);
            }
        }

        private XdsQueryResponse HandleGetDocumentsAndAssociations(XdsQueryRequest Request)
        {
            LogMessageEvent("Handling Get Documents and Associations");

            IQueryable<XdsDocument> document_matches;
            IQueryable<XdsAssociation> association_matches;

            XdsDataBase db = new XdsDataBase();

            if (string.IsNullOrEmpty(Request.DocumentEntryEntryUUID.ToString()) && string.IsNullOrEmpty(Request.DocumentEntryUniqueId.ToString()))
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "Missing EntryUUID or UniqueId");
            else if (0 != Request.DocumentEntryEntryUUID.Count() && (0 != Request.DocumentEntryUniqueId.Count()))
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryParamNumber, "Both EntryUUID and UniqueId present in request.");

            if (0 != Request.DocumentEntryEntryUUID.Count())
            {
                //using UUID
                List<string> uuids = new List<string>();
                foreach (var o in Request.DocumentEntryEntryUUID)
                    uuids.Add(o.UUID.ToString());

                document_matches = from document in db.Documents
                                   from uid in uuids
                                   where uid == document.UUID
                                   select document;
            }
            else
            {
                //using UniqueID
                List<string> uids = new List<string>();
                foreach (var o in Request.DocumentEntryUniqueId)
                    uids.Add(o.UUID.ToString());

                document_matches = from document in db.Documents
                                   from uid in uids
                                   where uid == document.UniqueID
                                   select document;
            }

            association_matches = from assoc in db.Associations
                                  from doc in document_matches
                                  where assoc.SourceUUID == doc.UUID || assoc.TargetUUID == doc.UUID
                                  select assoc;

            LogMessageEvent(string.Format("Found {0} documents", document_matches.Count()));
            LogMessageEvent(string.Format("Found {0} associations", association_matches.Count()));

            //Do we need to verify the PatientIds are all same
            if (Request.QueryReturnType == QueryReturnType.LeafClass && 1 < document_matches.Count())
            {
                var query = document_matches.GroupBy(s => s.PatientID);
                if (1 != query.Count())
                {
                    //TODO add new error code.....
                    return new XdsQueryResponse(XdsErrorCode.GeneralException, "Not Single Patient");
                }
            }

            return ReturnResults(Request, db, document_matches, null, null, association_matches);
        }

        private XdsQueryResponse HandleGetSubmissionSetAndContents(XdsQueryRequest Request)
        {
            LogMessageEvent("Handling Get Submission sets and Contents");

            IQueryable<XdsSubmissionSet> submissionset_matches;
            IQueryable<XdsAssociation> association_matches;
            IQueryable<XdsDocument> document_matches;
            IQueryable<XdsFolder> folder_matches;

            XdsDataBase db = new XdsDataBase();

            if (Request.SubmissionSetEntryUUID == null && Request.SubmissionSetUniqueId == null)
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "Missing EntryUUID or UniqueId");
            else if (Request.SubmissionSetEntryUUID != null && Request.SubmissionSetUniqueId != null)
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryParamNumber, "Both EntryUUID and UniqueId present in request.");

            int type_has_member = (int)AssociationType.HasMember;

            if (Request.SubmissionSetEntryUUID != null)
            {
                string submissionset_uuid = Request.SubmissionSetEntryUUID.UUID;

                //using UUID
                submissionset_matches = from submissionset in db.SubmissionSets
                                        where submissionset.UUID == submissionset_uuid
                                        select submissionset;

                association_matches = from assoc in db.Associations
                                      where assoc.TypeAsInt == type_has_member
                                      && assoc.SourceUUID == submissionset_uuid
                                      select assoc;

                document_matches = from doc in db.Documents
                                   from assoc in association_matches
                                   where doc.UUID == assoc.TargetUUID
                                   select doc;

                folder_matches = from folder in db.Folders
                                 from assoc in association_matches
                                 where folder.UUID == assoc.TargetUUID
                                 select folder;
            }
            else
            {
                string uid = Request.SubmissionSetUniqueId.UUID;

                //using UniqueId
                submissionset_matches = from submissionset in db.SubmissionSets
                                        where submissionset.UniqueID == uid
                                        select submissionset;

                //TODO but we need the UUID for any searching of the db.Assoications table is this correct
                string uuid = submissionset_matches.Single().UUID.ToString();

                association_matches = from assoc in db.Associations
                                      where assoc.TypeAsInt == type_has_member
                                      && assoc.SourceUUID == uuid //TODO does the assoc table ever hold uniqueids
                                      select assoc;

                document_matches = from doc in db.Documents
                                   from assoc in association_matches
                                   where doc.UUID == assoc.TargetUUID //TODO does the assoc table ever hold uniqueids
                                   select doc;

                folder_matches = from folder in db.Folders
                                 from assoc in association_matches
                                 where folder.UUID == assoc.TargetUUID //TODO does the assoc table ever hold uniqueids
                                 select folder;
            }

            //Filter docs for FormatCodes And ConfidentialityCodes
            document_matches = FilterForCodes(Request, document_matches);

            LogMessageEvent(string.Format("Found {0} documents", document_matches.Count()));
            LogMessageEvent(string.Format("Found {0} associations", association_matches.Count()));
            LogMessageEvent(string.Format("Found {0} submission sets", submissionset_matches.Count()));
            LogMessageEvent(string.Format("Found {0} folders", folder_matches.Count()));

            //Do we need to verify the PatientIds are all same
            if (Request.QueryReturnType == QueryReturnType.LeafClass)
            {
                //join the results on PatientID,  if result > 1 then records are not from single patient
                var res = submissionset_matches.Join(document_matches.Join(folder_matches, f => f.PatientID, d => d.PatientID, (f, d) => new { patientId = f.PatientID }), f => f.PatientID, d => d.patientId, (f, d) => new { patientId = f.PatientID });
                var patientIds = res.GroupBy(s => s.patientId);
                if (1 < patientIds.Count())
                {
                    //TODO add new error code.....
                    return new XdsQueryResponse(XdsErrorCode.GeneralException, "Not Single Patient");
                }
            }

            return ReturnResults(Request, db, document_matches, submissionset_matches, folder_matches, association_matches); ;
        }

        private XdsQueryResponse HandleGetFolderAndContents(XdsQueryRequest Request)
        {
            LogMessageEvent("Handling Get Folder and Contents");

            IQueryable<XdsFolder> folder_matches;
            IQueryable<XdsAssociation> association_matches;
            IQueryable<XdsDocument> document_matches;

            XdsDataBase db = new XdsDataBase();

            if (string.IsNullOrEmpty(Request.FolderEntryUUID.ToString()) && string.IsNullOrEmpty(Request.FolderUniqueId.ToString()))
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "Missing EntryUUID or UniqueId");
            else if (0 != Request.FolderEntryUUID.Count() && (0 != Request.FolderUniqueId.Count()))
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryParamNumber, "Both EntryUUID and UniqueId present in request.");

            List<string> uuids = new List<string>();
            int type_has_member = (int)AssociationType.HasMember;

            if (Request.FolderEntryUUID.Count > 0)
            {
                //using UUID
                foreach (var f in Request.FolderEntryUUID)
                    uuids.Add(f.UUID.ToString());

                folder_matches = from folder in db.Folders
                                 from uid in uuids
                                 where folder.UUID == uid
                                 select folder;
            }
            else
            {
                //using UniqueId
                foreach (var f in Request.FolderUniqueId)
                    uuids.Add(f.UUID.ToString());


                folder_matches = from folder in db.Folders
                                 from uid in uuids
                                 where folder.UniqueID == uid
                                 select folder;
            }

            association_matches = from assoc in db.Associations
                                  from folder in folder_matches
                                  where assoc.TypeAsInt == type_has_member
                                  && assoc.SourceUUID == folder.UUID
                                  select assoc;

            document_matches = from doc in db.Documents
                               from assoc in association_matches
                               where doc.UUID == assoc.TargetUUID
                               select doc;

            //Filter docs for FormatCodes And ConfidentialityCodes
            document_matches = FilterForCodes(Request, document_matches);

            LogMessageEvent(string.Format("Found {0} documents", document_matches.Count()));
            LogMessageEvent(string.Format("Found {0} associations", association_matches.Count()));
            LogMessageEvent(string.Format("Found {0} folders", folder_matches.Count()));

            //Do we need to verify the PatientIds are all same
            if (Request.QueryReturnType == QueryReturnType.LeafClass)
            {
                //join the results on PatientID,  if result > 1 then records are not from single patient
                var res = folder_matches.Join(document_matches, f => f.PatientID, d => d.PatientID, (f, d) => new { patientId = f.PatientID });
                var patientIds = res.GroupBy(s => s.patientId);
                if (1 < patientIds.Count())
                {
                    //TODO add new error code.....
                    return new XdsQueryResponse(XdsErrorCode.GeneralException, "Not Single Patient");
                }
            }

            return ReturnResults(Request, db, document_matches, null, folder_matches, association_matches);
        }

        private XdsQueryResponse HandleGetFoldersForDocument(XdsQueryRequest Request)
        {
            LogMessageEvent("Handling Get Folders and Documents");

            IQueryable<XdsFolder> folder_matches;

            XdsDataBase db = new XdsDataBase();

            //validation
            if (string.IsNullOrEmpty(Request.DocumentEntryEntryUUID.ToString()) && string.IsNullOrEmpty(Request.DocumentEntryUniqueId.ToString()))
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "Missing EntryUUID or UniqueId");
            else if (0 != Request.DocumentEntryEntryUUID.Count() && (0 != Request.DocumentEntryUniqueId.Count()))
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryParamNumber, "Both EntryUUID and UniqueId present in request.");

            int type_has_member = (int)AssociationType.HasMember;
            List<string> uuids = new List<string>();

            if (0 != Request.DocumentEntryEntryUUID.Count())
            {
                foreach (var o in Request.DocumentEntryEntryUUID)
                    uuids.Add(o.UUID.ToString());
            }
            else
            {
                IQueryable<XdsDocument> docs;
                List<string> uniquesIds = new List<string>();

                foreach (var o in Request.DocumentEntryUniqueId)
                    uniquesIds.Add(o.UUID.ToString());

                //Convert uniqueids to uuids
                docs = from d in db.Documents
                       from u in uniquesIds
                       where d.UniqueID == u
                       select d;

                foreach (var doc in docs)
                    uuids.Add(doc.UUID.ToString());
            }

            folder_matches = from association in db.Associations
                             from uid in uuids
                             from folder in db.Folders
                             where association.TypeAsInt == type_has_member
                             && association.TargetUUID == uid
                             && association.SourceUUID == folder.UUID
                             select folder;

            LogMessageEvent(string.Format("Found {0} folders", folder_matches.Count()));

            return ReturnResults(Request, db, null, null, folder_matches, null);
        }

        private XdsQueryResponse HandleGetFolders(XdsQueryRequest Request)
        {
            LogMessageEvent("Handling Get Folders");

            IQueryable<XdsFolder> matches;
            XdsDataBase db = new XdsDataBase();

            if (string.IsNullOrEmpty(Request.FolderEntryUUID.ToString()) && string.IsNullOrEmpty(Request.FolderUniqueId.ToString()))
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "Missing EntryUUID or UniqueId");
            else if (0 != Request.FolderEntryUUID.Count() && (0 != Request.FolderUniqueId.Count()))
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryParamNumber, "Both EntryUUID and UniqueId present in request.");

            if (0 != Request.FolderEntryUUID.Count())
            {
                List<string> uuids = new List<string>();
                foreach (var o in Request.FolderEntryUUID)
                    uuids.Add(o.UUID.ToString());

                matches = from folder in db.Folders
                          from uid in uuids
                          where uid == folder.UUID
                          select folder;
            }
            else
            {
                List<string> uids = new List<string>();
                foreach (var o in Request.FolderUniqueId)
                    uids.Add(o.UUID.ToString());

                matches = from folder in db.Folders
                          from uid in uids
                          where uid == folder.UniqueID
                          select folder;
            }

            LogMessageEvent(string.Format("Found {0} folders", matches.Count()));

            //Do we need to verify the PatientIds are all same
            if (Request.QueryReturnType == QueryReturnType.LeafClass && 1 < matches.Count())
            {
                var query = matches.GroupBy(s => s.PatientID);
                if (1 != query.Count())
                {
                    //TODO add new error code.....
                    return new XdsQueryResponse(XdsErrorCode.GeneralException, "Not Single Patient");
                }
            }

            return ReturnResults(Request, db, null, null, matches, null);
        }

        private XdsQueryResponse HandleFindFolders(XdsQueryRequest Request)
        {
            LogMessageEvent("Handling Find Folders");

            IQueryable<XdsFolder> folder_matches;
            XdsDataBase db = new XdsDataBase();

            //validation
            if (string.IsNullOrEmpty(Request.PatientId.ToString()))
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "Missing PatientId");

            if (string.IsNullOrEmpty(Request.FolderStatus.ToString()))
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "Missing Folder Status");

            int folder_status = (int)Request.FolderStatus;

            folder_matches = from folder in db.Folders
                             where folder.PatientID == Request.PatientId
                                //&& folder.LastUpdateTime < Request.FolderLastUpdateTimeTo
                                //&& folder.LastUpdateTime > Request.FolderLastUpdateTimeFrom
                                && folder.AvailabilityStatusAsInt == folder_status
                             select folder;

            LogMessageEvent(string.Format("Found {0} folders", folder_matches.Count()));

            //Filter for LastUpdateTime
            if (Request.FolderLastUpdateTimeFrom != null && Request.FolderLastUpdateTimeTo != null)
                folder_matches = folder_matches.Where(f => f.LastUpdateTime < Request.FolderLastUpdateTimeTo && f.LastUpdateTime > Request.FolderLastUpdateTimeFrom);

            //Filter For Codes
            IEnumerable<XdsFolder> linqQuery = folder_matches.AsEnumerable();
            foreach (var constraint in Request.FolderCodeSet)
                linqQuery = linqQuery.Where(f => MatchCodeListInList(f.CodeList, constraint));

            return ReturnResults(Request, db, null, null, linqQuery, null);
        }

        private XdsQueryResponse HandleGetRelatedDocuments(XdsQueryRequest Request)
        {
            LogMessageEvent("Handling Get Related Documents");

            //Find ASsocaiations
            IQueryable<XdsAssociation> association_matches;
            IQueryable<XdsDocument> doc_matches;
            XdsDataBase db = new XdsDataBase();

            //validation
            if (string.IsNullOrEmpty(Request.DocumentEntryEntryUUID.ToString()) && string.IsNullOrEmpty(Request.DocumentEntryUniqueId.ToString()))
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "Missing EntryUUID or UniqueId");
            else if (0 != Request.DocumentEntryEntryUUID.Count() && (0 != Request.DocumentEntryUniqueId.Count()))
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryParamNumber, "Both EntryUUID and UniqueId present in request.");

            List<string> uuids = new List<string>();
            foreach (var o in Request.DocumentEntryEntryUUID)
                uuids.Add(o.UUID.ToString());

            int assoc_type = (int)Request.AssociationTypes[0];

            association_matches = from association in db.Associations
                                  from doc in db.Documents
                                  from uid in uuids
                                  where (uid == association.SourceUUID && association.TargetUUID == doc.UUID
                                      || uid == association.TargetUUID && association.SourceUUID == doc.UUID)
                                      && (association.TypeAsInt == assoc_type)
                                  select association;

            doc_matches = from doc in db.Documents
                          from assocation in association_matches
                          where (assocation.SourceUUID == doc.UUID || assocation.TargetUUID == doc.UUID)
                          select doc;

            LogMessageEvent(string.Format("Found {0} associations", association_matches.Count()));
            LogMessageEvent(string.Format("Found {0} documents", doc_matches.Count()));

            return ReturnResults(Request, db, doc_matches, null, null, association_matches);
        }

        private XdsQueryResponse HandleGetDocuments(XdsQueryRequest Request)
        {
            LogMessageEvent("Handling Get Documents");

            IQueryable<XdsDocument> matches;
            XdsDataBase db = new XdsDataBase();

            if (string.IsNullOrEmpty(Request.DocumentEntryEntryUUID.ToString()) && string.IsNullOrEmpty(Request.DocumentEntryUniqueId.ToString()))
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "Missing EntryUUID or UniqueId");
            else if (0 != Request.DocumentEntryEntryUUID.Count() && (0 != Request.DocumentEntryUniqueId.Count()))
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryParamNumber, "Both EntryUUID and UniqueId present in request.");

            if (0 != Request.DocumentEntryEntryUUID.Count())
            {
                List<string> uuids = new List<string>();
                foreach (var o in Request.DocumentEntryEntryUUID)
                    uuids.Add(o.UUID.ToString());

                IQueryable<XdsDocument> documents = db.Documents;

                matches = from document in documents
                          from uid in uuids
                          where uid == document.UUID
                          select document;
            }
            else
            {
                List<string> uids = new List<string>();
                foreach (var o in Request.DocumentEntryUniqueId)
                    uids.Add(o.UUID.ToString());

                matches = from document in db.Documents
                          from uid in uids
                          where uid == document.UniqueID
                          select document;
            }

            LogMessageEvent(string.Format("Found {0} documents", matches.Count()));

            //Do we need to verify the PatientIds are all same
            if (Request.QueryReturnType == QueryReturnType.LeafClass && 1 < matches.Count())
            {
                var query = matches.GroupBy(s => s.PatientID);
                if (1 != query.Count())
                {
                    //TODO add new error code.....
                    return new XdsQueryResponse(XdsErrorCode.GeneralException, "Not Single Patient");
                }
            }

            return ReturnResults(Request, db, matches, null, null, null);
        }

        private XdsQueryResponse HandleGetSubmissionSets(XdsQueryRequest Request)
        {
            LogMessageEvent("Handling Get Subbmissionsets");

            XdsDataBase db = new XdsDataBase();

            // validation
            if (0 == Request.AssociationUUID.Count)
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "Missing EntryUUID");

            //DB query
            IQueryable<XdsAssociation> associations = db.Associations;
            IQueryable<XdsSubmissionSet> submissionSets = db.SubmissionSets;

            // apply filters
            IQueryable<XdsSubmissionSet> submissionset_matches;

            List<string> strUuids = new List<string>();
            foreach (var obj in Request.AssociationUUID)
                strUuids.Add(obj.UUID.ToString());

            submissionset_matches = from submissionSet in submissionSets
                                    from association in associations
                                    from uuid1 in strUuids
                                    where submissionSet.UUID == association.SourceUUID && association.TargetUUID == uuid1 && association.TypeAsInt == (int)AssociationType.HasMember
                                    select submissionSet;

            LogMessageEvent(string.Format("Found {0} submission sets", submissionset_matches.Count()));

            //Do we need to verify the PatientIds are all same
            if (Request.QueryReturnType == QueryReturnType.LeafClass && 1 < submissionset_matches.Count())
            {
                var query = submissionset_matches.GroupBy(s => s.PatientID);
                if (1 != query.Count())
                {
                    //TODO add new error code.....
                    return new XdsQueryResponse(XdsErrorCode.GeneralException, "Not Single Patient");
                }
            }

            return ReturnResults(Request, db, null, submissionset_matches, null, null);
        }

        private XdsQueryResponse HandleGetAssociations(XdsQueryRequest request)
        {
            LogMessageEvent("Handling Get Associations");

            XdsQueryResponse response = new XdsQueryResponse();

            // validation
            if (0 == request.AssociationUUID.Count)
            return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "Missing Association UID");

            using (XdsDataBase db = new XdsDataBase())
            {
                IQueryable<XdsAssociation> association_matches;
                
                List<string> uuids = new List<string>();
                foreach (var s in request.AssociationUUID)
                    uuids.Add(s.UUID.ToString());
                
                association_matches =  from a in db.Associations
                                       from uid in uuids
                                       where  a.SourceUUID == uid || a.TargetUUID == uid
                                       select a;

                foreach (var assoc in association_matches.AsEnumerable())
                    response.ReturnedAssociations.Add(assoc);
           
                LogMessageEvent(string.Format("Found {0} associations", association_matches.Count()));
            }


            return response;
        }

        XdsRegistryResponse server_RegisterReceived(XdsSubmissionSet SubmissionSet, XdsRequestInfo RequestInfo)
        {
            LogMessageEvent("Register Document Request Received");

            try
            {
                XdsRegistryResponse resp = new XdsRegistryResponse();
                SubmissionSet.ReplaceSymbolicNames();

                using (XdsDataBase db = new XdsDataBase())
                {
                    // this must all be ATOMIC - a registry must either do EVERYTHING or NOTHING - it cannot return partial success
                    // Validate Patient ID
                    if (!db.Patients.Any(patient => patient.PatientId == SubmissionSet.PatientInfo.CompositeId))
                        return new XdsRegistryResponse(XdsErrorCode.XDSUnknownPatientId, DummyContext);

                    //XdsPatient p = db.Patients.Where(patient => patient.CompositeId == SubmissionSet.PatientInfo.CompositeId).First();
                    // TODO - lots of other validation as per IHE rules

                    // set status to available for all documents, folders and the submission set itself

                    SubmissionSet.AvailabilityStatus = AvailabilityStatusCode.Approved;

                    foreach (var d in SubmissionSet.Documents)
                    {
                        d.AvailabilityStatus = AvailabilityStatusCode.Approved;
                        //d.PatientInfo = SubmissionSet.PatientInfo;
                    }

                    foreach (var d in SubmissionSet.Folders)
                        d.AvailabilityStatus = AvailabilityStatusCode.Approved;

                    if (resp.Errors.ErrorList.Count > 0)
                    {
                        resp.Status = RegistryResponseStatus.Failure;
                    }
                    else
                    {
                        resp.Status = RegistryResponseStatus.Success;

                        //Set lastUpdateTime on all associated folders
                        foreach (var f in SubmissionSet.Folders)
                            f.LastUpdateTime = DateTime.Now;

                        //db.Patients.Attach(SubmissionSet.PatientInfo);
                        db.SubmissionSets.Add(SubmissionSet);
                        db.SaveChanges();

                        //Existing folders
                        //Find folders by  from assoc
                        var folder_matches = from assoc in SubmissionSet.Associations
                                             from folder in db.Folders
                                             where assoc.SourceUUID == folder.UUID
                                             select folder;

                        foreach (var f in folder_matches)
                            f.LastUpdateTime = DateTime.Now;
                    }
                    return resp;
                }
            }
            catch (Exception ex)
            {
                return new XdsRegistryResponse(ex, XdsErrorCode.GeneralException);
            }
        }
        #endregion

        XdsQueryResponse HandleFindDocumentQuery(XdsQueryRequest request)
        {
            LogMessageEvent("Handling Find Document Query");

            using (XdsDataBase db = new XdsDataBase())
            {
                // validation
                if (request.DocumentEntryStatus == AvailabilityStatusCode.None)
                    return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "$XDSDocumentEntryStatus is missing in FindDocuments query");
                
                if (string.IsNullOrEmpty(request.PatientId))
                    return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "Missing Patient ID");

                if (!db.Patients.Any(p => p.PatientId == request.PatientId)) // No patient found
                    return new XdsQueryResponse(XdsErrorCode.XDSUnknownPatientId, DummyContext);

                var Documents = DocumentQuery(request, db);

                LogMessageEvent(string.Format("Found {0} documents", Documents.Count()));

                return ReturnResults(request, db, Documents, null, null, null);
            }
        }

        XdsQueryResponse HandleFindSubmissionSetQuery(XdsQueryRequest request)
        {
            LogMessageEvent("Handling Find Submissionset Query");

            using (XdsDataBase db = new XdsDataBase())
            {
                if (string.IsNullOrEmpty(request.PatientId))
                    return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "Missing Patient ID");

                if (!db.Patients.Any(p => p.PatientId == request.PatientId)) // No patient found
                    return new XdsQueryResponse(XdsErrorCode.XDSUnknownPatientId, DummyContext);

                if (request.SubmissionSetStatus == AvailabilityStatusCode.None)
                    return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "$XDSSubmissionSetStatus is missing in FindDocuments query");

                var SubmissionSets = SubmissionSetQuery(request, db);

                LogMessageEvent(string.Format("Found {0} subbmission sets", SubmissionSets.Count()));

                return ReturnResults(request, db, null, SubmissionSets, null, null);
            }
        }

        XdsQueryResponse HandleGetAll(XdsQueryRequest request)
        {
            LogMessageEvent("Handling Get All");

            XdsDataBase db = new XdsDataBase();

            //Validation
            if (string.IsNullOrEmpty(request.PatientId))
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "Missing Patient ID");
            if (!db.Patients.Any(p => p.PatientId == request.PatientId)) // No patient found
                return new XdsQueryResponse(XdsErrorCode.XDSUnknownPatientId, DummyContext);
            if (request.DocumentEntryStatus == AvailabilityStatusCode.None)
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "$XDSDocumentEntryStatus is missing in GetAll query");
            if (request.SubmissionSetStatus == AvailabilityStatusCode.None)
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "$XDSSubmissionSetStatus is missing in GetAll query");
            if (request.FolderStatus == AvailabilityStatusCode.None)
                return new XdsQueryResponse(XdsErrorCode.XDSStoredQueryMissingParam, "$XDSFolderStatus is missing in GetAll query");

            //Query
            var submission_sets = SubmissionSetQuery(request, db);
            var documents = DocumentQuery(request, db);

            var folders = from f in db.Folders
                          where f.PatientID == request.PatientId && f.AvailabilityStatusAsInt == (int)request.FolderStatus
                          select f;

            var associations = (from a in db.Associations
                                from f in folders
                                from s in submission_sets
                                from d in documents
                                where (a.TargetUUID == f.UUID || a.SourceUUID == f.UUID)
                                || (a.TargetUUID == d.UUID || a.SourceUUID == d.UUID)
                                || (a.TargetUUID == s.UUID || a.SourceUUID == s.UUID)
                                select a).Distinct();

            LogMessageEvent(string.Format("Found {0} documents", documents.Count()));
            LogMessageEvent(string.Format("Found {0} associations", associations.Count()));
            LogMessageEvent(string.Format("Found {0} folders", folders.Count()));
            LogMessageEvent(string.Format("Found {0} submission sets", submission_sets.Count()));

            return ReturnResults(request, db, documents, submission_sets, folders, associations);
        }

        #region High Level Shared methods
        
        private static IQueryable<XdsDocument> DocumentQuery(XdsQueryRequest request, XdsDataBase db)
        {
            IQueryable<XdsDocument> source = db.Documents;

            // decide what other data is needed for querying
            if (request.DocumentEntryTypeCodes.Count > 0)
                source = source.Include(x => x.TypeCode);
            if (request.DocumentEntryConfidentialityCodeSet.Count > 0)
                source = source.Include(x => x.ConfidentialityCodes);

            var efMatchingDocuments = from  d in source
                                      where d.PatientID == request.PatientId
                                      && ((int)request.DocumentEntryStatus & d.AvailabilityStatusAsInt) > 0
                                      select d;

            efMatchingDocuments = FilterForTimeParams(request, efMatchingDocuments);

            efMatchingDocuments = FilterForCodes(request, efMatchingDocuments);

            return efMatchingDocuments;
        }

        private static IQueryable<XdsDocument> FilterForCodes(XdsQueryRequest request, IQueryable<XdsDocument> efMatchingDocuments)
        {
            IEnumerable<XdsDocument> linqQuery = efMatchingDocuments.AsEnumerable();

            if (request.DocumentEntryClassCodes.Count > 0)
                linqQuery = linqQuery.Where(x => MatchCodeInList(x.ClassCode, request.DocumentEntryClassCodes));

            if (request.DocumentEntryTypeCodes.Count > 0)
                linqQuery = linqQuery.Where(x => MatchCodeInList(x.TypeCode, request.DocumentEntryTypeCodes));

            if (request.DocumentEntryFormatCodes.Count > 0)
                linqQuery = linqQuery.Where(x => MatchCodeInList(x.FormatCode, request.DocumentEntryFormatCodes));

            foreach (var constraint in request.DocumentEntryConfidentialityCodeSet)
            {
                linqQuery = linqQuery.Where(x => MatchCodeListInList(x.ConfidentialityCodes, constraint));
            }

            foreach (var constraint in request.DocumentEntryEventCodeSet)
            {
                linqQuery = linqQuery.Where(x => MatchCodeListInList(x.EventCodeList, constraint));
            }
            return linqQuery.AsQueryable();
        }

        private static IQueryable<XdsDocument> FilterForTimeParams(XdsQueryRequest request, IQueryable<XdsDocument> efMatchingDocuments)
        {
            if (request.DocumentEntryCreationTimeFrom != null)
                efMatchingDocuments = efMatchingDocuments.Where(x => x.CreationTime >= request.DocumentEntryCreationTimeFrom);

            if (request.DocumentEntryCreationTimeTo != null)
                efMatchingDocuments = efMatchingDocuments.Where(x => x.CreationTime <= request.DocumentEntryCreationTimeTo);

            if (request.DocumentEntryServiceStartTimeFrom != null)
                efMatchingDocuments = efMatchingDocuments.Where(x => x.ServiceStartTime >= request.DocumentEntryServiceStartTimeFrom);

            if (request.DocumentEntryServiceStartTimeTo != null)
                efMatchingDocuments = efMatchingDocuments.Where(x => x.ServiceStartTime >= request.DocumentEntryServiceStartTimeTo);

            if (request.DocumentEntryServiceStopTimeFrom != null)
                efMatchingDocuments = efMatchingDocuments.Where(x => x.ServiceStopTime >= request.DocumentEntryServiceStopTimeFrom);

            if (request.DocumentEntryServiceStopTimeTo != null)
                efMatchingDocuments = efMatchingDocuments.Where(x => x.ServiceStopTime >= request.DocumentEntryServiceStopTimeTo);

            return efMatchingDocuments;
        }

        private static IQueryable<XdsSubmissionSet> SubmissionSetQuery(XdsQueryRequest request, XdsDataBase db)
        {
            IQueryable<XdsSubmissionSet> source = db.SubmissionSets;

            // needed for author query
            if (request.SubmissionSetAuthorPerson != null)
            {
                source = source.Include(x => x.Authors);
            }

            var efSubmissionSets = from d in source
                                   where d.PatientID == request.PatientId
                                   && ((int)request.SubmissionSetStatus & d.AvailabilityStatusAsInt) > 0
                                   select d;

            if (request.SubmissionSetSourceId != null)
                efSubmissionSets = efSubmissionSets.Where(x => request.SubmissionSetSourceId.Contains(x.SourceID));

            if (request.SubmissionSetSubmissionTimeFrom != null)
                efSubmissionSets = efSubmissionSets.Where(x => x.SubmissionTime <= request.SubmissionSetSubmissionTimeFrom);

            if (request.SubmissionSetSubmissionTimeTo != null)
                efSubmissionSets = efSubmissionSets.Where(x => x.SubmissionTime <= request.SubmissionSetSubmissionTimeTo);

            // above this line is EF queries (simple only)
            // filters below use client evaluation
            var linqSubmissionSets = efSubmissionSets;

            if (request.SubmissionSetAuthorPerson != null)
                linqSubmissionSets = linqSubmissionSets.Where(x => x.HasAuthor(request.SubmissionSetAuthorPerson));

            return linqSubmissionSets;
        }

        private static XdsQueryResponse ReturnResults(XdsQueryRequest request, XdsDataBase db,
            IEnumerable<XdsDocument> Documents,
            IEnumerable<XdsSubmissionSet> SubmissionSets,
            IEnumerable<XdsFolder> Folders,
            IEnumerable<XdsAssociation> Associations)
        {

            XdsQueryResponse resp = new XdsQueryResponse();

            if (Documents == null)
                Documents = new List<XdsDocument>();
            if (SubmissionSets == null)
                SubmissionSets = new List<XdsSubmissionSet>();
            if (Folders == null)
                Folders = new List<XdsFolder>();
            if (Associations == null)
                Associations = new List<XdsAssociation>();

            if (request.QueryReturnType == QueryReturnType.ObjectRef)
            {
                if (Documents.Count() + SubmissionSets.Count() + Folders.Count() + Associations.Count() > MaxObjectRefResults) // too many results
                    return new XdsQueryResponse(new Exception("Too many results"), XdsErrorCode.XDSTooManyResults);

                foreach (var d in Documents)
                    resp.ReturnedObjectRefs.Add(new XdsObjectRef(d.UUID));
                foreach (var d in SubmissionSets)
                    resp.ReturnedObjectRefs.Add(new XdsObjectRef(d.UUID));
                foreach (var d in Folders)
                    resp.ReturnedObjectRefs.Add(new XdsObjectRef(d.UUID));
                foreach (var d in Associations)
                    resp.ReturnedObjectRefs.Add(new XdsObjectRef(d.UUID));

                resp.Status = RegistryResponseStatus.Success;
                return resp;
            }
            else
            {
                if (Documents.Count() > MaxLeafClassResults) // too many results
                    return new XdsQueryResponse(new Exception("Too many results"), XdsErrorCode.XDSTooManyResults);

                // This may strill need more sub-data to be loaded
                foreach (var d in Documents)
                {
                    // load related - this is NOT necessarily the most efficient way!
                    db.Entry(d).Collection(p => p.Authors).Load();
                    db.Entry(d).Collection(p => p.ConfidentialityCodes).Load();
                    db.Entry(d).Collection(p => p.EventCodeList).Load();
                    resp.ReturnedDocuments.Add(d);
                }
                foreach (var d in SubmissionSets)
                {
                    db.Entry(d).Collection(p => p.Authors).Load();
                    resp.ReturnedSubmissionSets.Add(d);
                }
                foreach (var d in Folders)
                {
                    resp.ReturnedFolders.Add(d);
                }
                foreach (var a in Associations)
                {
                    resp.ReturnedAssociations.Add(a);
                }

                resp.Status = RegistryResponseStatus.Success;
                return resp;
            }
        }
        #endregion
        
        #region utilities
        private static bool MatchCodeListInList(List<XdsCode> stored, List<XdsCode> constraint)
        {
            foreach (var code in stored)
            {
                if (MatchCodeInList(code, constraint))
                    return true;
            }
            return false;
        }

        private static bool MatchCodeInList(XdsCode code, List<XdsCode> list)
        {
            if (code == null)
                return false;

            if (list.Count == 0)
                return true;

            foreach (var c in list)
            {
                if (c.CodingScheme == code.CodingScheme && c.CodeValue == code.CodeValue)
                    return true;
            }
            return false;
        }
        #endregion
    }

    static class Extensions
    {
        public static bool HasAuthor(this XdsSubmissionSet s, string Pattern)
        {
            // this implements SQL "LIKE" syntax    
            var matcher = new Regex(@"\A"
                + new Regex(@"\.|\$|\^|\{|\[|\(|\||\)|\*|\+|\?|\\")
                    .Replace(Pattern, ch => @"\" + ch).Replace('_', '.')
                    .Replace("%", ".*")
                + @"\z", RegexOptions.Singleline);

            foreach (var a in s.Authors)
            {

                if (matcher.IsMatch(a.Name))
                    return true;
            }
            return false;
        }
    }
}

