using System;
using System.Collections.Generic;

using XdsObjects;
using XdsObjects.Enums;

namespace XDS_Gateways
{
    #region "Base Classes"
    internal abstract class XCAGateway
    {
        protected XdsSoapServer QueryGateway;
        protected XdsMtomServer RetrieveGateway;
        protected string HomeCommunityID;

        public XCAGateway(string homeCommunityID, string QueryAddress, string RetrieveAddress)
        {
            // Soap Server for handling Query requests
            QueryGateway = new XdsSoapServer();
            QueryGateway.Listen(QueryAddress);          

            // MTOM Server for handling Retrieve requests
            RetrieveGateway = new XdsMtomServer();
            RetrieveGateway.Listen(RetrieveAddress);           

            HomeCommunityID = homeCommunityID;
        }
    }

    /// <summary>
    /// Used to hold gateway specific info
    /// </summary>
    internal class GatewayConfiguration : XdsDomain
    {
        internal bool isLocalRegistry;
    }

    #endregion

    #region "Initiating Gateway"

    internal class XCAInitiatingGateway : XCAGateway
    {
        // List of multiple Responding Gateways the Initiating Gateway can talk to at the SAME time
        public List<GatewayConfiguration> ActiveGateways = new List<GatewayConfiguration>();
        Dictionary<string, GatewayConfiguration> HomeCommunityIDToConfig = new Dictionary<string, GatewayConfiguration>();

        internal XCAInitiatingGateway(string HomeCommunityID, string QueryAddress, string RetrieveAddress)
            : base(HomeCommunityID, QueryAddress, RetrieveAddress)
        {
            // Set up the events
            QueryGateway.RegistryStoredQueryReceived += new StoredQueryHandler(QueryGateway_RegistryStoredQueryReceived);
            RetrieveGateway.RetrieveRequestReceived += new RetrieveHandler(RetrieveGateway_RetrieveRequestReceived);
        }

        // Event fires when RegistryStoredQuery request is received
        XdsQueryResponse QueryGateway_RegistryStoredQueryReceived(XdsQueryRequest Request, XdsRequestInfo RequestInfo)
        {
            // might need to:
            // 1) Send to multiple responding GateWays
            // 2) Combine responses (like MWL in router!)
            // 3) Look up IDs for each domain we send it to!
            if (Request.PatientId == null)
            {
                // PatientID and HomeCommunityID cannot be both NULL
                if (string.IsNullOrEmpty(Request.HomeCommunityID))
                    throw new Exception("HomeComminunityID is missing in RegistryStoredQuery without PatientID.");

                string HomeCommunityID = Request.HomeCommunityID;

                if (HomeCommunityIDToConfig.ContainsKey(HomeCommunityID))
                {
                    XdsDomain SelectedConfig = HomeCommunityIDToConfig[HomeCommunityID];
                    return SelectedConfig.CrossGatewayQuery(Request);
                }
                else
                {
                    return new XdsQueryResponse(XdsErrorCode.XDSUnknownCommunity, "", HomeCommunityID);
                }
            }
            else
            {
                // If it has a patientID, send to all, no need for HomeCommunityID                
                XdsQueryResponse response = new XdsQueryResponse();
                XdsPatient patient = new XdsPatient();
                patient.CompositeId = Request.PatientId;

                int Succeeded = 0;
                int Tried = 0;

                // Could make the following in parallel, but for this example we'll simply keep it consecutive!
                foreach (GatewayConfiguration responding in ActiveGateways)
                {
                    Tried++;

                    XdsQueryResponse RemoteResponse;
                    if (responding.isLocalRegistry)
                    {
                        RemoteResponse = responding.RegistryStoredQuery(Request);
                        RemoteResponse.SetHomeCommunityID(HomeCommunityID);
                    }
                    else
                    {
                        //XdsPatient ModifiedPatient = PixQuery.GetInfo(responding, responding.XdsDomainRoot, patient);
                        //request.PatientId = ModifiedPatient.CompositeId;

                        // We do NOT do PIX query in this sample, but it's needed in the real world!!!!!!
                        // Request.PatientId = PixQuery.GetID(responding, responding.XdsDomainRoot, patient);
                        RemoteResponse = responding.CrossGatewayQuery(Request);
                    }

                    // Combine the responses from different responding gateways
                    response.Combine(RemoteResponse);

                    // Caching the Home Community ID of each Responding Gateway
                    CacheHomeCommunityIDs(responding, RemoteResponse);
                    Succeeded++;
                }

                // Work out what final response status to send back to client
                if (Succeeded == Tried)
                    response.Status = RegistryResponseStatus.Success;
                else if (Succeeded == 0)
                    response.Status = RegistryResponseStatus.Failure;
                else
                    response.Status = RegistryResponseStatus.PartialSuccess;

                return response;
            }
        }

        // Get the HomeCommunityID from any response from the Reponding Gateway
        private void CacheHomeCommunityIDs(GatewayConfiguration responding, XdsQueryResponse RemoteResponse)
        {
            foreach (XdsDocument doc in RemoteResponse.ReturnedDocuments)
                CacheOneID(doc.HomeCommunityID, responding);
            foreach (XdsSubmissionSet submission in RemoteResponse.ReturnedSubmissionSets)
                CacheOneID(submission.HomeCommunityID, responding);
            foreach (XdsFolder folder in RemoteResponse.ReturnedFolders)
                CacheOneID(folder.HomeCommunityID, responding);
            foreach (XdsObjectRef objectRef in RemoteResponse.ReturnedObjectRefs)
                CacheOneID(objectRef.HomeCommunityID, responding);
        }

        // Cache the HomeCommunityID
        private void CacheOneID(string ID, GatewayConfiguration responding)
        {
            if (string.IsNullOrEmpty(ID))
                throw new Exception("HomeCommunityID is missing in StoredQueryResponse.");
            HomeCommunityIDToConfig[ID] = responding;
        }

        // Event fires when Retrieve request is received
        XdsRetrieveResponse RetrieveGateway_RetrieveRequestReceived(XdsRetrieveRequest Request, XdsRequestInfo RequestInfo)
        {
            XdsRetrieveResponse resp = new XdsRetrieveResponse();

            // Group Requests by HomeCommunityID
            List<XdsRetrieveRequest> Requests = Request.GroupByHomeCommunityID();

            // Send CrossGateway Retrieve Request to each responding Gateway of each HomeCommunityID
            foreach (XdsRetrieveRequest request in Requests)
            {
                GatewayConfiguration config = HomeCommunityIDToConfig[request.Requests[0].HomeCommunityID];
                if (config.isLocalRegistry)
                {
                    request.ClearHomeCommunityID();
                    // Combine response
                    resp.Combine(config.RetrieveDocumentSet(request, null));
                }
                else
                    // Combine response 
                    resp.Combine(config.CrossGatewayRetrieve(request));
            }

            // Set appropriate status (Success, PartialSuccess or Failure)
            if (resp.Documents.Count == 0)
                resp.Status = RegistryResponseStatus.Failure;
            else if (resp.Documents.Count == Request.Requests.Count)
                resp.Status = RegistryResponseStatus.Success;
            else
                resp.Status = RegistryResponseStatus.PartialSuccess;

            // Return Response to client
            return resp;
        }
    }
    
    #endregion

    #region "Responding Gateway"

    internal class XCARespondingGateway : XCAGateway
    { 
        protected XdsDomain RespDomain;

        internal XCARespondingGateway(XdsDomain domain, string HomeCommunityID, string QueryAddress, string RetrieveAddress)
            : base(HomeCommunityID, QueryAddress, RetrieveAddress)
        {
            RespDomain = domain;
            // Set up events
            QueryGateway.CrossGatewayQueryReceived += new StoredQueryHandler(QueryGateway_CrossGatewayQueryReceived);
            RetrieveGateway.CrossGatewayRetrieveReceived += new RetrieveHandler(RetrieveGateway_CrossGatewayRetrieveReceived);
        }       

        // Event fires when Cross Gateway Query request is received
        XdsQueryResponse QueryGateway_CrossGatewayQueryReceived(XdsQueryRequest Request, XdsRequestInfo RequestInfo)
        {
            // Empty HomeCommunityID so that local registry won't get confused
            Request.HomeCommunityID = "";

            // Do query against local registry
            XdsQueryResponse resp = RespDomain.RegistryStoredQuery(Request);

            // Set Home Community ID to the response
            resp.SetHomeCommunityID(HomeCommunityID);

            // Return response to Initiating Gateway
            return resp;
        }

        // Event fires when Cross Gateway Retrieve request is received
        XdsRetrieveResponse RetrieveGateway_CrossGatewayRetrieveReceived(XdsRetrieveRequest Request, XdsRequestInfo RequestInfo)
        {
            foreach (XdsRetrieveItem r in Request.Requests)
            {
                if ((string.IsNullOrEmpty(r.HomeCommunityID)) || (!string.IsNullOrEmpty(r.HomeCommunityID) && r.HomeCommunityID != HomeCommunityID))
                {
                    // If there is any empty or mismatching HomeCommunityID, then return error
                    XdsRetrieveResponse err = new XdsRetrieveResponse();
                    err.AddError(XdsErrorCode.XDSUnknownCommunity, r.DocumentUniqueID, HomeCommunityID);
                    err.Status = RegistryResponseStatus.Failure;
                    return err;
                }
                else
                    // Empty HomeCommunityID so that local repository won't get confused
                    r.HomeCommunityID = "";
            }

            // Retrieve from local Repository
            XdsRetrieveResponse result = RespDomain.RetrieveDocumentSet(Request, null);

            // Return results
            return result;
        }
    }

    #endregion
}
