public static ICouchbaseResponseState GetChangesPost(ICouchbaseListenerContext context)
        {
            DBMonitorCouchbaseResponseState responseState = new DBMonitorCouchbaseResponseState();

            var responseObject = PerformLogicWithDatabase(context, true, db =>
            {
                var response = context.CreateResponse();
                responseState.Response = response;
                var body = context.BodyAs<Dictionary<string, object>>();
                ProcessBody(body);
                if (body.GetCast<ChangesFeedMode>("feed") < ChangesFeedMode.Continuous) {
                    if(context.CacheWithEtag(db.GetLastSequenceNumber().ToString())) {
                        response.InternalStatus = StatusCode.NotModified;
                        return response;
                    }
                }

                var options = ChangesOptions.Default;
                responseState.Db = db;
                responseState.ContentOptions = body.GetCast<DocumentContentOptions>("content_options");
                responseState.ChangesFeedMode = body.GetCast<ChangesFeedMode>("feed");
                responseState.ChangesIncludeDocs = body.GetCast<bool>("include_docs");
                options.IncludeDocs = responseState.ChangesIncludeDocs;
                responseState.ChangesIncludeConflicts = body.GetCast<string>("style") == "all_docs";
                options.IncludeConflicts = responseState.ChangesIncludeConflicts;
                options.ContentOptions = responseState.ContentOptions;
                options.SortBySequence = !options.IncludeConflicts;
                options.Limit = body.GetCast<int>("limit", options.Limit);
                int since = body.GetCast<int>("since");

                string filterName = body.GetCast<string>("filter");
                if(filterName != null) {
                    Status status = new Status();
                    responseState.ChangesFilter = db.GetFilter(filterName, status);
                    if(responseState.ChangesFilter == null) {
                        return context.CreateResponse(status.Code);
                    }

                    responseState.FilterParams = context.GetQueryParams();
                }


                RevisionList changes = db.ChangesSince(since, options, responseState.ChangesFilter, responseState.FilterParams);
                if((responseState.ChangesFeedMode >= ChangesFeedMode.Continuous) || 
                    (responseState.ChangesFeedMode == ChangesFeedMode.LongPoll && changes.Count == 0)) {
                    // Response is going to stay open (continuous, or hanging GET):
                    response.Chunked = true;
                    if(responseState.ChangesFeedMode == ChangesFeedMode.EventSource) {
                        response["Content-Type"] = "text/event-stream; charset=utf-8";
                    }

                    if(responseState.ChangesFeedMode >= ChangesFeedMode.Continuous) {
                        response.WriteHeaders();
                        foreach(var rev in changes) {
                            response.SendContinuousLine(ChangesDictForRev(rev, responseState), context.ChangesFeedMode);
                        }
                    }

                    responseState.SubscribeToDatabase(db);
                    int heartbeat = body.GetCast<int>("heartbeat", Int32.MinValue);
                    if(heartbeat != Int32.MinValue) {
                        if(heartbeat <= 0) {
                            responseState.IsAsync = false;
                            return context.CreateResponse(StatusCode.BadParam);
                        }

                        heartbeat = Math.Max(heartbeat, (int)MinHeartbeat.TotalMilliseconds);
                        string heartbeatResponse = context.ChangesFeedMode == ChangesFeedMode.EventSource ? "\n\n" : "\r\n";
                        responseState.StartHeartbeat(heartbeatResponse, TimeSpan.FromMilliseconds(heartbeat));
                    }

                    return context.CreateResponse();
                } else {
                    if(responseState.ChangesIncludeConflicts) {
                        response.JsonBody = new Body(ResponseBodyForChanges(changes, since, options.Limit, responseState));
                    } else {
                        response.JsonBody = new Body(ResponseBodyForChanges(changes, since, responseState));
                    }

                    return response;
                }
            });

            responseState.Response = responseObject;
            return responseState;
        }
        /// <summary>
        /// Returns a sorted list of changes made to documents in the database, in time order of application.
        /// </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/database/changes.html#get--db-_changes
        /// </remarks>
        public static ICouchbaseResponseState GetChanges(ICouchbaseListenerContext context)
        {
            DBMonitorCouchbaseResponseState responseState = new DBMonitorCouchbaseResponseState();

            var responseObject = PerformLogicWithDatabase(context, true, db =>
            {
                var response = context.CreateResponse();
                responseState.Response = response;
                if (context.ChangesFeedMode < ChangesFeedMode.Continuous) {
                    if(context.CacheWithEtag(db.GetLastSequenceNumber().ToString())) {
                        response.InternalStatus = StatusCode.NotModified;
                        return response;
                    }
                }
                    
                var options = ChangesOptions.Default;
                responseState.Db = db;
                responseState.ContentOptions = context.ContentOptions;
                responseState.ChangesFeedMode = context.ChangesFeedMode;
                responseState.ChangesIncludeDocs = context.GetQueryParam<bool>("include_docs", bool.TryParse, false);
                options.IncludeDocs = responseState.ChangesIncludeDocs;
                responseState.ChangesIncludeConflicts = context.GetQueryParam("style") == "all_docs";
                options.IncludeConflicts = responseState.ChangesIncludeConflicts;
                options.ContentOptions = context.ContentOptions;
                options.SortBySequence = !options.IncludeConflicts;
                options.Limit = context.GetQueryParam<int>("limit", int.TryParse, options.Limit);
                int since = context.GetQueryParam<int>("since", int.TryParse, 0);

                string filterName = context.GetQueryParam("filter");
                if(filterName != null) {
                    Status status = new Status();
                    responseState.ChangesFilter = db.GetFilter(filterName, status);
                    if(responseState.ChangesFilter == null) {
                        return context.CreateResponse(status.Code);
                    }

                    responseState.FilterParams = context.GetQueryParams();
                }


                RevisionList changes = db.ChangesSince(since, options, responseState.ChangesFilter, responseState.FilterParams);
                if((context.ChangesFeedMode >= ChangesFeedMode.Continuous) || 
                    (context.ChangesFeedMode == ChangesFeedMode.LongPoll && changes.Count == 0)) {
                    // Response is going to stay open (continuous, or hanging GET):
                    response.Chunked = true;
                    if(context.ChangesFeedMode == ChangesFeedMode.EventSource) {
                        response["Content-Type"] = "text/event-stream; charset=utf-8";
                    }

                    if(context.ChangesFeedMode >= ChangesFeedMode.Continuous) {
                        response.WriteHeaders();
                        foreach(var rev in changes) {
                            var success = response.SendContinuousLine(ChangesDictForRev(rev, responseState), context.ChangesFeedMode);
                            if(!success) {
                                return context.CreateResponse(StatusCode.BadRequest);
                            }
                        }
                    }

                    responseState.SubscribeToDatabase(db);
                    string heartbeatParam = context.GetQueryParam("heartbeat");
                    if(heartbeatParam != null) {
                        int heartbeat;
                        if(!int.TryParse(heartbeatParam, out heartbeat) || heartbeat <= 0) {
                            responseState.IsAsync = false;
                            return context.CreateResponse(StatusCode.BadParam);
                        }

                        var heartbeatSpan = TimeSpan.FromMilliseconds(heartbeat);
                        if(heartbeatSpan < MinHeartbeat) {
                            heartbeatSpan = MinHeartbeat;
                        }

                        string heartbeatResponse = context.ChangesFeedMode == ChangesFeedMode.EventSource ? "\n\n" : "\r\n";
                        responseState.StartHeartbeat(heartbeatResponse, heartbeatSpan);
                    }

                    return response;
                } else {
                    if(responseState.ChangesIncludeConflicts) {
                        response.JsonBody = new Body(ResponseBodyForChanges(changes, since, options.Limit, responseState));
                    } else {
                        response.JsonBody = new Body(ResponseBodyForChanges(changes, since, responseState));
                    }

                    return response;
                }
            });

            responseState.Response = responseObject;
            return responseState;
        }