/// <summary>
        /// Factory method to create the light-weight online service context (without fetching service document and metadata document which are known already)
        /// </summary>
        /// <param name="target">The target resource to be crawled</param>
        /// <param name="acceptHeaderValue">The accept header value defined in the Http header</param>
        /// <param name="serviceRoot">The URI pointing to the service document resource</param>
        /// <param name="serviceDocument">The content of service document</param>
        /// <param name="metadataDocument">The content of metadata document</param>
        /// <param name="jobId">The validation job identifier</param>
        /// <param name="maximumPayloadSize">The maximum number of bytes allowed to retrieve from the target resource</param>
        /// <param name="reqHeaders">Http headers sent as part of request</param>
        /// <returns>The constructed service context</returns>
        /// <exception cref="CrawlRuntimeException">Throws exception when target resource exceeds the maximum size allowed</exception>
        public static ServiceContext Create(Uri target,
                                            string acceptHeaderValue,
                                            Uri serviceRoot,
                                            string serviceDocument,
                                            string metadataDocument,
                                            Guid jobId,
                                            int maximumPayloadSize,
                                            IEnumerable <KeyValuePair <string, string> > reqHeaders,
                                            string category = "core")
        {
            Response          response      = WebHelper.Get(target, acceptHeaderValue, maximumPayloadSize, reqHeaders);
            ODataMetadataType odataMetadata = ServiceContextFactory.MapAcceptHeaderToMetadataType(acceptHeaderValue);
            var    payloadFormat            = response.ResponsePayload.GetFormatFromPayload();
            var    payloadType = response.ResponsePayload.GetTypeFromPayload(payloadFormat);
            string entityType  = response.ResponsePayload.GetFullEntityType(payloadType, payloadFormat, metadataDocument);

            return(new ServiceContext(target,
                                      jobId,
                                      response.StatusCode,
                                      response.ResponseHeaders,
                                      response.ResponsePayload,
                                      entityType,
                                      serviceRoot,
                                      serviceDocument,
                                      metadataDocument,
                                      false,
                                      reqHeaders,
                                      odataMetadata,
                                      null,
                                      category));
        }
        /// <summary>
        /// Helper method to find the service document of the OData service this context refers to
        /// </summary>
        /// <param name="maximumPayloadSize">maximum payload size in byte</param>
        /// <param name="uri">uri hint, which is used to derive various possible uris in hope one of them is the service base endpoint</param>
        /// <param name="acceptHeaderValue">value of header of Accept that will be used in request getting the response</param>
        /// <param name="IgnoreInputUri">if true, the given uri is known other than service base</param>
        /// <param name="serviceBaseUri">out parameter: the service base uri if one shall be found</param>
        /// <param name="responseServiceDocument">out parameter: the response object including payload, headers and status code</param>
        /// <param name="reqHeaders">Http headers to be sent out to server</param>
        /// <returns>true if a valid service document is found; otherwise false</returns>
        private static bool TryGetServiceDocument(int maximumPayloadSize,
                                                  Uri uri,
                                                  string acceptHeaderValue,
                                                  bool IgnoreInputUri,
                                                  out Uri serviceBaseUri,
                                                  out Response responseServiceDocument,
                                                  IEnumerable <KeyValuePair <string, string> > reqHeaders)
        {
            if (!IgnoreInputUri)
            {
                try
                {
                    var response = WebHelper.Get(uri, acceptHeaderValue, maximumPayloadSize, reqHeaders);
                    if (response.IsServiceDocument())
                    {
                        serviceBaseUri          = uri;
                        responseServiceDocument = response;
                        return(true);
                    }
                }
                catch (OversizedPayloadException)
                {
                    // does nothing
                }
            }

            var segments = uri.Segments;

            if (segments != null && segments.Length > 0)
            {
                Uri uriParent;
                if (Uri.TryCreate(uri.GetLeftPart(UriPartial.Authority) + string.Join("", segments.Take(segments.Length - 1).ToArray()), UriKind.Absolute, out uriParent))
                {
                    if (uri != uriParent)
                    {
                        return(ServiceContextFactory.TryGetServiceDocument(maximumPayloadSize, uriParent, acceptHeaderValue, false, out serviceBaseUri, out responseServiceDocument, reqHeaders));
                    }
                }
            }

            serviceBaseUri          = null;
            responseServiceDocument = null;
            return(false);
        }
        /// <summary>
        /// Factory method to set up the OData Interop context based on uri, desired format and job id
        /// </summary>
        /// <param name="destination">uri string pointing to a OData service endpoint</param>
        /// <param name="format">format preference, could be either "atom" or "json" - default and falling back to atom</param>
        /// <param name="jobId">unique identifier to tag this context</param>
        /// <param name="maximumPayloadSize">the maximum number of bytes rule engine is willing to retrieve from the Uri provided</param>
        /// <param name="reqHeaders">Http headers sent as part of request</param>
        /// <returns>context object representing the interop request session</returns>
        public static ServiceContext Create(string destination, string format, Guid jobId, int maximumPayloadSize, IEnumerable <KeyValuePair <string, string> > reqHeaders, string category = "core")
        {
            Uri    inputUri;
            Uri    serviceBaseUri          = null;
            string serviceDocument         = null;
            string metadataDocument        = null;
            string entityType              = null;
            string jsonFullMetadataPayload = null;
            var    serviceStatus           = ServiceStatus.GetInstance();

            if (string.IsNullOrEmpty(format))
            {
                throw new ArgumentException(Resource.ArgumentNotNullOrEmpty, "format");
            }

            try
            {
                if (destination.EndsWith(@"/"))
                {
                    destination = destination.TrimEnd('/');
                }

                inputUri = new Uri(destination);
            }
            catch (UriFormatException)
            {
                if (!destination.StartsWith(@"http://") && !destination.StartsWith(@"https://"))
                {
                    inputUri = new Uri(SupportedScheme.SchemeHttp + "://" + destination);
                }
                else
                {
                    inputUri = new Uri(Uri.EscapeUriString(destination));
                }
            }

            if (!SupportedScheme.Instance.Contains(inputUri.Scheme))
            {
                throw new ArgumentException(
                          string.Format(CultureInfo.CurrentCulture, Resource.formatSchemeNotSupported, inputUri.Scheme),
                          "destination");
            }

            string            acceptHeader  = ServiceContextFactory.MapFormatToAcceptValue(format);
            ODataMetadataType odataMetadata = ServiceContextFactory.MapFormatToMetadataType(format);
            Response          response      = WebHelper.Get(inputUri, acceptHeader, maximumPayloadSize, reqHeaders);

            if (response.StatusCode == HttpStatusCode.NotFound)
            {
                return(null);
            }

            var payloadFormat = response.ResponsePayload.GetFormatFromPayload();
            var payloadType   = ContextHelper.GetPayloadType(response.ResponsePayload, payloadFormat, response.ResponseHeaders);

            switch (payloadType)
            {
            case PayloadType.ServiceDoc:
                serviceBaseUri  = inputUri;
                serviceDocument = response.ResponsePayload;
                break;

            case PayloadType.Metadata:
                if (inputUri.AbsoluteUri.EndsWith(Constants.OptionMetadata, StringComparison.Ordinal))
                {
                    if (payloadFormat == PayloadFormat.JsonLight)
                    {
                        serviceBaseUri  = new Uri(serviceStatus.RootURL);
                        serviceDocument = serviceStatus.ServiceDocument;
                    }
                    else
                    {
                        serviceBaseUri = new Uri(inputUri.AbsoluteUri.Substring(0, inputUri.AbsoluteUri.Length - Constants.OptionMetadata.Length));
                        var respSvcDoc = WebHelper.Get(serviceBaseUri, acceptHeader, maximumPayloadSize, reqHeaders);
                        serviceDocument = respSvcDoc.ResponsePayload;
                    }
                }

                break;

            default:
                if (payloadType.IsValidPayload())
                {
                    string   fullPath = inputUri.GetLeftPart(UriPartial.Path).TrimEnd('/');
                    var      uriPath  = new Uri(fullPath);
                    Uri      baseUri;
                    Response serviceDoc;
                    if (ServiceContextFactory.TryGetServiceDocument(maximumPayloadSize, uriPath, acceptHeader, uriPath.AbsoluteUri == fullPath, out baseUri, out serviceDoc, reqHeaders))
                    {
                        serviceBaseUri  = baseUri;
                        serviceDocument = serviceDoc.ResponsePayload;
                    }
                }

                break;
            }

            if (payloadType == PayloadType.Metadata)
            {
                metadataDocument = response.ResponsePayload;
            }
            else
            {
                if (serviceBaseUri != null)
                {
                    if (payloadFormat == PayloadFormat.JsonLight && payloadType == PayloadType.ServiceDoc)
                    {
                        metadataDocument = serviceStatus.MetadataDocument;
                    }
                    else
                    {
                        try
                        {
                            string metadataURL      = serviceBaseUri.AbsoluteUri.EndsWith(@"/") ? Constants.OptionMetadata.TrimStart('/') : Constants.OptionMetadata;
                            var    responseMetadata = WebHelper.Get(new Uri(serviceBaseUri.AbsoluteUri + metadataURL), null, maximumPayloadSize, reqHeaders);
                            if (responseMetadata != null)
                            {
                                metadataDocument = responseMetadata.ResponsePayload;
                            }
                        }
                        catch (OversizedPayloadException)
                        {
                            // do nothing
                        }
                    }
                }
            }

            if (payloadFormat == PayloadFormat.JsonLight)
            {
                try
                {
                    // Specify full metadata accept header
                    string acceptHeaderOfFullMetadata = string.Empty;
                    if (acceptHeader.Equals(Constants.V3AcceptHeaderJsonFullMetadata) ||
                        acceptHeader.Equals(Constants.V3AcceptHeaderJsonMinimalMetadata) ||
                        acceptHeader.Equals(Constants.V3AcceptHeaderJsonNoMetadata))
                    {
                        acceptHeaderOfFullMetadata = Constants.V3AcceptHeaderJsonFullMetadata;
                    }
                    else
                    {
                        acceptHeaderOfFullMetadata = Constants.V4AcceptHeaderJsonFullMetadata;
                    }

                    // Send full metadata request and get full metadata response.
                    var responseFullMetadata = WebHelper.Get(inputUri, acceptHeaderOfFullMetadata, maximumPayloadSize, reqHeaders);
                    if (responseFullMetadata != null)
                    {
                        jsonFullMetadataPayload = responseFullMetadata.ResponsePayload;
                        entityType = jsonFullMetadataPayload.GetFullEntityType(payloadType, payloadFormat, metadataDocument);
                    }
                }
                catch (OversizedPayloadException)
                {
                    // do nothing
                }
            }
            else
            {
                entityType = response.ResponsePayload.GetFullEntityType(payloadType, payloadFormat, metadataDocument);
            }

            return(new ServiceContext(inputUri,
                                      jobId,
                                      response.StatusCode,
                                      response.ResponseHeaders,
                                      response.ResponsePayload,
                                      entityType,
                                      serviceBaseUri,
                                      serviceDocument,
                                      metadataDocument,
                                      false,
                                      reqHeaders,
                                      odataMetadata,
                                      jsonFullMetadataPayload,
                                      category));
        }
        /// <summary>
        /// Creates an offline service context with specified job id from payload context and metadata document.
        /// </summary>
        /// <param name="payload">The payload context</param>
        /// <param name="metadata">The metadata document</param>
        /// <param name="jobId">The job id</param>
        /// <param name="respHeaders">The optional Http response headers</param>
        /// <param name="reqHeaders">The request headers</param>
        /// <returns>Service context object which represents the offline interop validation session</returns>
        public static ServiceContext Create(string payload, string metadata, Guid jobId, string respHeaders, IEnumerable <KeyValuePair <string, string> > reqHeaders)
        {
            if (string.IsNullOrEmpty(payload) && string.IsNullOrEmpty(metadata))
            {
                throw new ArgumentException("invalid parameters");
            }

            if (string.IsNullOrEmpty(payload))
            {
                payload  = metadata;
                metadata = null;
            }

            Uri               inputUri         = new Uri(Constants.DefaultOfflineTarget);
            string            entityType       = null;
            Uri               serviceBaseUri   = null;
            string            serviceDocument  = null;
            string            metadataDocument = null;
            var               payloadFormat    = payload.GetFormatFromPayload();
            var               payloadType      = payload.GetTypeFromPayload(payloadFormat);
            ODataMetadataType metadataType     = ServiceContextFactory.GetMetadataTypeFromOfflineHeader(respHeaders);

            if (payloadType != PayloadType.Metadata && !string.IsNullOrEmpty(metadata))
            {
                var metaFormat = metadata.GetFormatFromPayload();
                if (metaFormat == PayloadFormat.Xml)
                {
                    var metaType = metadata.GetTypeFromPayload(metaFormat);
                    if (metaType == PayloadType.Metadata)
                    {
                        //do sanity check to ensure a matching metadata, should payload be a feed or an entry
                        if (IsMetadataMacthing(payload, metadata, payloadFormat, payloadType))
                        {
                            metadataDocument = metadata;
                        }
                    }
                }
            }

            switch (payloadType)
            {
            case PayloadType.ServiceDoc:
                serviceDocument = payload;
                if (payloadFormat == PayloadFormat.Xml || payloadFormat == PayloadFormat.Atom)
                {
                    XElement   payloadXml = XElement.Parse(payload);
                    XNamespace ns         = "http://www.w3.org/XML/1998/namespace"; // xmlns:xml definition according to http://www.w3.org/TR/REC-xml-names/
                    var        xmlBase    = payloadXml.Attribute(ns + "base");      // rfc5023: it MAY have an "xml:base" attribute ... serving as the base URI
                    if (xmlBase != null && !string.IsNullOrEmpty(xmlBase.Value))
                    {
                        inputUri = new Uri(xmlBase.Value);
                    }
                }
                break;

            case PayloadType.Metadata:
                serviceDocument  = null;
                metadataDocument = payload;
                break;

            case PayloadType.Error:
                break;

            case PayloadType.Feed:
            case PayloadType.Entry:
            {
                entityType = payload.GetFullEntityType(payloadType, payloadFormat, metadataDocument);
                string shortEntityTypeName = entityType.GetLastSegment();
                string target = payload.GetIdFromFeedOrEntry(payloadFormat);
                if (!string.IsNullOrEmpty(target))
                {
                    var projectedProperties = payload.GetProjectedPropertiesFromFeedOrEntry(payloadFormat, metadataDocument, shortEntityTypeName);
                    if (projectedProperties != null && projectedProperties.Any())
                    {
                        var opt = string.Join(",", projectedProperties);
                        try
                        {
                            inputUri = new Uri(target + "?$select=" + opt);
                        }
                        catch (UriFormatException)
                        {
                            //do nothing
                        }
                    }
                    else
                    {
                        try
                        {
                            inputUri = new Uri(target);
                        }
                        catch (UriFormatException)
                        {
                            //do nothing
                        }
                    }
                }
            }
            break;
            }

            return(new ServiceContext(inputUri, jobId, null, respHeaders, payload, entityType, serviceBaseUri, serviceDocument, metadataDocument, true, reqHeaders, metadataType));
        }