/*
 * Copyright 2015 The Project Buendia Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy
 * of the License at: http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distrib-
 * uted under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
 * OR CONDITIONS OF ANY KIND, either express or implied.  See the License for
 * specific language governing permissions and limitations under the License.
 */

package org.openmrs.projectbuendia.webservices.rest;

import org.openmrs.Obs;
import org.openmrs.api.context.Context;
import org.openmrs.module.webservices.rest.SimpleObject;
import org.openmrs.module.webservices.rest.web.RequestContext;
import org.openmrs.module.webservices.rest.web.RestConstants;
import org.openmrs.module.webservices.rest.web.annotation.Resource;
import org.openmrs.module.webservices.rest.web.resource.api.Listable;
import org.openmrs.module.webservices.rest.web.resource.api.Searchable;
import org.openmrs.module.webservices.rest.web.response.ResponseException;
import org.openmrs.projectbuendia.Utils;
import org.projectbuendia.openmrs.api.ProjectBuendiaService;
import org.projectbuendia.openmrs.api.SyncToken;
import org.projectbuendia.openmrs.api.db.SyncPage;
import org.projectbuendia.openmrs.webservices.rest.RestController;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * A resource that allows observations to be incrementally synced.
 * Note: this resource is read-only. For creates, see {@link EncounterResource}.
 */
@Resource(
    name = RestController.REST_VERSION_1_AND_NAMESPACE + "/observations",
    supportedClass = Obs.class,
    supportedOpenmrsVersions = "1.10.*,1.11.*")
public class ObservationResource implements Listable, Searchable {

    private final ProjectBuendiaService buendiaService;

    public ObservationResource() {
        buendiaService = Context.getService(ProjectBuendiaService.class);
    }

    @Override
    public SimpleObject getAll(RequestContext context) throws ResponseException {
        return handleSync(context);
    }

    @Override
    public SimpleObject search(RequestContext context) throws ResponseException {
        return handleSync(context);
    }

    private SimpleObject handleSync(RequestContext context) {
        Date syncFromDate = RequestUtil.mustParseSyncFromDate(context);
        SyncToken syncFrom = syncFromDate == null ? null : new SyncToken(syncFromDate, null);

        // Set the next sync from time to be one second in the past because there's issues
        // due to OpenMRS rounding truncating insertion times. See
        // https://github.com/projectbuendia/client/pull/90
        Date nextSyncFrom = new Date(System.currentTimeMillis() - 1000);

        SyncPage<Obs> observations =
                buendiaService.getObservationsModifiedAtOrAfter(syncFrom, syncFrom != null, 0);
        List<SimpleObject> jsonResults = new ArrayList<>(observations.results.size());
        for (Obs obs : observations.results) {
            jsonResults.add(obsToJson(obs));
        }
        return ResponseUtil.createIncrementalSyncResults(jsonResults, nextSyncFrom);
    }

    private SimpleObject obsToJson(Obs obs) {
        SimpleObject object = new SimpleObject()
            .add("uuid", obs.getUuid())
            .add("voided", obs.isVoided());

        if (obs.isVoided()) {
            return object;
        }

        object
            .add("patient_uuid", obs.getPerson().getUuid())
            .add("encounter_uuid", obs.getEncounter().getUuid())
            .add("concept_uuid", obs.getConcept().getUuid())
            .add("timestamp", Utils.toIso8601(obs.getObsDatetime()));

        boolean isExecutedOrder =
                DbUtil.getOrderExecutedConcept().equals(obs.getConcept()) && obs.getOrder() != null;
        if (isExecutedOrder) {
            object.add("value", obs.getOrder().getUuid());
        } else {
            object.add("value", ObservationsHandler.obsValueToString(obs));
        }

        return object;
    }

    @Override
    public String getUri(Object instance) {
        Obs obs = (Obs) instance;
        Resource res = getClass().getAnnotation(Resource.class);
        return RestConstants.URI_PREFIX + res.name() + "/" + obs.getUuid();
    }
}

@@ -1912,16 +1912,70 @@
 able %7B%0A%0A
+    private static final int MAX_OBS_PER_PAGE = 500;%0A%0A
     priv
@@ -2487,20 +2487,25 @@
         
-Date
+SyncToken
  syncFro
@@ -2505,20 +2505,16 @@
 syncFrom
-Date
  = Reque
@@ -2533,24 +2533,21 @@
 arseSync
-FromDate
+Token
 (context
@@ -2561,383 +2561,36 @@
     
-SyncToken syncFrom = syncFromDate == null ? null : new SyncToken(syncFromDate, null);%0A%0A        // Set the next sync from time to be one second in the past because there's issues%0A        // due to OpenMRS rounding truncating insertion times. See%0A        // https://github.com/projectbuendia/client/pull/90%0A        Date nextSyncFrom = new Date(System.currentTimeMillis() - 1000
+Date requestTime = new Date(
 );%0A%0A
@@ -2690,16 +2690,41 @@
 OrAfter(
+%0A                        
 syncFrom
@@ -2743,17 +2743,32 @@
 = null, 
-0
+MAX_OBS_PER_PAGE
 );%0A     
@@ -2951,16 +2951,299 @@
       %7D%0A
+        SyncToken newToken = SyncTokenUtils.clampSyncTokenToBufferedRequestTime(%0A                observations.syncToken, requestTime);%0A        // If we fetched a full page, there's probably more data available.%0A        boolean more = observations.results.size() == MAX_OBS_PER_PAGE;%0A
         
@@ -3310,18 +3310,20 @@
 , ne
-xtSyncFrom
+wToken, more
 );%0A 

Implement pagination for Observation requests.