/// <summary>
        /// Returns document by the specified docid from the specified db. Unless you request a
        /// specific revision, the latest revision of the document will always be returned.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/document/common.html#get--db-docid
        /// </remarks>
        public static ICouchbaseResponseState GetDocument(ICouchbaseListenerContext context)
        {
            return(DatabaseMethods.PerformLogicWithDatabase(context, true, db => {
                var response = context.CreateResponse();
                string docId = context.DocumentName;
                bool isLocalDoc = docId.StartsWith("_local", StringComparison.InvariantCulture);

                DocumentContentOptions options = context.ContentOptions;
                string openRevsParam = context.GetQueryParam("open_revs");
                bool mustSendJson = context.ExplicitlyAcceptsType("application/json");
                Status status = new Status();
                if (openRevsParam == null || isLocalDoc)
                {
                    //Regular GET:
                    var revId = context.GetQueryParam("rev").AsRevID(); //often null
                    RevisionInternal rev;
                    bool includeAttachments = false, sendMultipart = false;
                    if (isLocalDoc)
                    {
                        rev = db.Storage.GetLocalDocument(docId, revId);
                    }
                    else
                    {
                        includeAttachments = options.HasFlag(DocumentContentOptions.IncludeAttachments);
                        if (includeAttachments)
                        {
                            sendMultipart = !mustSendJson;
                            options &= ~DocumentContentOptions.IncludeAttachments;
                        }


                        rev = db.GetDocument(docId, revId, true, status);
                        if (rev != null)
                        {
                            rev = ApplyOptions(options, rev, context, db, status);
                        }

                        if (rev == null)
                        {
                            response.StatusReason = status.Code == StatusCode.Deleted ? "deleted" : "missing";
                            response.InternalStatus = StatusCode.NotFound;
                            return response;
                        }
                    }

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

                    if (context.CacheWithEtag(rev.RevID?.ToString()))
                    {
                        response.InternalStatus = StatusCode.NotModified;
                        return response;
                    }

                    if (!isLocalDoc && includeAttachments)
                    {
                        int minRevPos = 1;
                        var attsSince = context.GetJsonQueryParam("atts_since")?.AsList <string>()?.AsRevIDs();
                        var ancestorId = db.Storage.FindCommonAncestor(rev, attsSince);
                        if (ancestorId != null)
                        {
                            minRevPos = ancestorId.Generation + 1;
                        }

                        bool attEncodingInfo = context.GetQueryParam <bool>("att_encoding_info", bool.TryParse, false);
                        db.ExpandAttachments(rev, minRevPos, sendMultipart, attEncodingInfo);
                    }

                    if (sendMultipart)
                    {
                        response.MultipartWriter = MultipartWriterForRev(db, rev, "multipart/related");
                    }
                    else
                    {
                        response.JsonBody = rev.GetBody();
                    }
                }
                else
                {
                    // open_revs query:
                    IList <IDictionary <string, object> > result;
                    if (openRevsParam.Equals("all"))
                    {
                        // ?open_revs=all returns all current/leaf revisions:
                        bool includeDeleted = context.GetQueryParam <bool>("include_deleted", bool.TryParse, false);
                        RevisionList allRevs = db.Storage.GetAllDocumentRevisions(docId, true, includeDeleted);

                        result = new List <IDictionary <string, object> >();
                        foreach (var rev in allRevs)
                        {
                            if (!includeDeleted && rev.Deleted)
                            {
                                continue;
                            }

                            var loadedRev = db.RevisionByLoadingBody(rev, status);
                            if (loadedRev != null)
                            {
                                ApplyOptions(options, loadedRev, context, db, status);
                            }

                            if (loadedRev != null)
                            {
                                result.Add(new Dictionary <string, object> {
                                    { "ok", loadedRev.GetProperties() }
                                });
                            }
                            else if (status.Code <= StatusCode.InternalServerError)
                            {
                                result.Add(new Dictionary <string, object> {
                                    { "missing", rev.RevID }
                                });
                            }
                            else
                            {
                                response.InternalStatus = status.Code;
                                return response;
                            }
                        }
                    }
                    else
                    {
                        // ?open_revs=[...] returns an array of specific revisions of the document:
                        var openRevs = context.GetJsonQueryParam("open_revs").AsList <object>();
                        if (openRevs == null)
                        {
                            response.InternalStatus = StatusCode.BadParam;
                            return response;
                        }

                        result = new List <IDictionary <string, object> >();
                        foreach (var revIDObj in openRevs)
                        {
                            var revID = revIDObj as string;
                            if (revID == null)
                            {
                                response.InternalStatus = StatusCode.BadId;
                                return response;
                            }

                            var rev = db.GetDocument(docId, revID.AsRevID(), true);
                            if (rev != null)
                            {
                                rev = ApplyOptions(options, rev, context, db, status);
                            }

                            if (rev != null)
                            {
                                result.Add(new Dictionary <string, object> {
                                    { "ok", rev.GetProperties() }
                                });
                            }
                            else
                            {
                                result.Add(new Dictionary <string, object> {
                                    { "missing", revID }
                                });
                            }
                        }
                    }

                    if (mustSendJson)
                    {
                        response["Content-Type"] = "application/json";
                        response.JsonBody = new Body(result.Cast <object>().ToList());
                    }
                    else
                    {
                        response.SetMultipartBody(result.Cast <object>().ToList(), "multipart/mixed");
                    }
                }

                return response;
            }).AsDefaultState());
        }
        /// <summary>
        /// Returns document by the specified docid from the specified db. Unless you request a 
        /// specific revision, the latest revision of the document will always be returned.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/document/common.html#get--db-docid
        /// <remarks>
        public static ICouchbaseResponseState GetDocument(ICouchbaseListenerContext context)
        {
            return DatabaseMethods.PerformLogicWithDatabase(context, true, db => {
                var response = context.CreateResponse();
                string docId = context.DocumentName;
                bool isLocalDoc = docId.StartsWith("_local");

                DocumentContentOptions options = context.ContentOptions;
                string openRevsParam = context.GetQueryParam("open_revs");
                bool mustSendJson = context.ExplicitlyAcceptsType("application/json");
                if (openRevsParam == null || isLocalDoc) {
                    //Regular GET:
                    string revId = context.GetQueryParam("rev"); //often null
                    RevisionInternal rev;
                    bool includeAttachments = false, sendMultipart = false;
                    if (isLocalDoc) {
                        rev = db.Storage.GetLocalDocument(docId, revId);
                    } else {
                        includeAttachments = options.HasFlag(DocumentContentOptions.IncludeAttachments);
                        if(includeAttachments) {
                            sendMultipart = !mustSendJson;
                            options &= ~DocumentContentOptions.IncludeAttachments;
                        }

                        Status status = new Status();
                        rev = db.GetDocument(docId, revId, true, status);
                        if(rev != null) {
                            rev = ApplyOptions(options, rev, context, db, status);
                        }

                        if(rev == null) {
                            if(status.Code == StatusCode.Deleted) {
                                response.StatusReason = "deleted";
                            } else {
                                response.StatusReason = "missing";
                            }

                            response.InternalStatus = status.Code;
                            return response;
                        }
                    }

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

                    if(context.CacheWithEtag(rev.GetRevId())) {
                        response.InternalStatus = StatusCode.NotModified;
                        return response;
                    }

                    if(!isLocalDoc && includeAttachments) {
                        int minRevPos = 1;
                        IList<string> attsSince = context.GetJsonQueryParam("atts_since").AsList<string>();
                        string ancestorId = db.Storage.FindCommonAncestor(rev, attsSince);
                        if(ancestorId != null) {
                            minRevPos = RevisionInternal.GenerationFromRevID(ancestorId) + 1;
                        }

                        Status status = new Status();
                        bool attEncodingInfo = context.GetQueryParam<bool>("att_encoding_info", bool.TryParse, false);
                        if(!db.ExpandAttachments(rev, minRevPos, sendMultipart, attEncodingInfo, status)) {
                            response.InternalStatus = status.Code;
                            return response;
                        }
                    }

                    if(sendMultipart) {
                        response.MultipartWriter = db.MultipartWriterForRev(rev, "multipart/related");
                    } else {
                        response.JsonBody = rev.GetBody();
                    }
                } else {
                    // open_revs query:
                    IList<IDictionary<string, object>> result;
                    if(openRevsParam.Equals("all")) {
                        // ?open_revs=all returns all current/leaf revisions:
                        bool includeDeleted = context.GetQueryParam<bool>("include_deleted", bool.TryParse, false);
                        RevisionList allRevs = db.Storage.GetAllDocumentRevisions(docId, true);

                        result = new List<IDictionary<string, object>>();
                        foreach(var rev in allRevs) {
                            if(!includeDeleted && rev.IsDeleted()) {
                                continue;
                            }

                            Status status = new Status();
                            RevisionInternal loadedRev = db.RevisionByLoadingBody(rev, status);
                            if(loadedRev != null) {
                                ApplyOptions(options, loadedRev, context, db, status);
                            }

                            if(loadedRev != null) {
                                result.Add(new Dictionary<string, object> { { "ok", loadedRev.GetProperties() } });
                            } else if(status.Code <= StatusCode.InternalServerError) {
                                result.Add(new Dictionary<string, object> { { "missing", rev.GetRevId() } });
                            } else {
                                response.InternalStatus = status.Code;
                                return response;
                            }
                        }
                    } else {
                        // ?open_revs=[...] returns an array of specific revisions of the document:
                        var openRevs = context.GetJsonQueryParam("open_revs").AsList<object>();
                        if(openRevs == null) {
                            response.InternalStatus = StatusCode.BadParam;
                            return response;
                        }

                        result = new List<IDictionary<string, object>>();
                        foreach(var revIDObj in openRevs) {
                            var revID = revIDObj as string;
                            if(revID == null) {
                                response.InternalStatus = StatusCode.BadId;
                                return response;
                            }

                            Status status = new Status();
                            var rev = db.GetDocument(docId, revID, true, status);
                            if(rev != null) {
                                rev = ApplyOptions(options, rev, context, db, status);
                            }

                            if(rev != null) {
                                result.Add(new Dictionary<string, object>{ { "ok", rev.GetProperties() } });
                            } else {
                                result.Add(new Dictionary<string, object>{ { "missing", revID } });
                            }
                        }
                    }

                    if(mustSendJson) {
                        response["Content-Type"] = "application/json";
                        response.JsonBody = new Body(result.Cast<object>().ToList());
                    } else {
                        response.SetMultipartBody(result.Cast<object>().ToList(), "multipart/mixed");
                    }
                }

                return response;
            }).AsDefaultState();
        }