content: /*
   *  Copyright (C) 2015 Stichting Akvo (Akvo Foundation)
   *
   *  This file is part of Akvo FLOW.
   *
   *  Akvo FLOW is free software: you can redistribute it and modify it under the terms of
   *  the GNU Affero General Public License (AGPL) as published by the Free Software Foundation,
   *  either version 3 of the License or any later version.
   *
   *  Akvo FLOW is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
   *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
   *  See the GNU Affero General Public License included below for more details.
   *
   *  The full license text can also be seen at <http://www.gnu.org/licenses/agpl.html>.
   */
  
  package org.akvo.gae.remoteapi;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import com.google.appengine.api.datastore.DatastoreService;
  import com.google.appengine.api.datastore.Entity;
  import com.google.appengine.api.datastore.EntityNotFoundException;
  import com.google.appengine.api.datastore.FetchOptions;
  import com.google.appengine.api.datastore.KeyFactory;
  import com.google.appengine.api.datastore.Query;
  import com.google.appengine.api.datastore.Query.Filter;
  import com.google.appengine.api.datastore.Query.FilterOperator;
  
  import static org.akvo.gae.remoteapi.DataUtils.*;
  
  /**
   * This is a data modification script to rectify anomalies with survey instances in monitored
   * groups. It resets the surveyedLocaleId of monitoring form survey instances to point to the same
   * locale created by the registration form with which the monitoring instance shares a
   * survyedLocaleIdentifier
   */
  public class CorrectSurveyedLocale implements Process {
  
      @Override
      public void execute(DatastoreService ds, String[] args) throws Exception {
          if (args.length == 0) {
              System.err
                      .println("Usage: "
                              + RemoteAPI.class.getName()
                              + "CorrectSurveyedLocale <appid> <username> <password> <appid> <surveyId>");
              System.exit(1);
          }
  
          // retrieve registration form id for surveygroup matching monitored group id passed in
          Long surveyId = Long.parseLong(args[0]);
          Entity monitoringSurvey = retrieveMonitoringSurvey(ds, surveyId);
          List<Entity> surveyForms = retrieveSurveyForms(ds, surveyId);
  
          // map locale identifiers and surveyedlocale ids for all survey instances created by
          // registration form
          Long registrationFormId = (Long) monitoringSurvey.getProperty("newLocaleSurveyId");
  
          if (registrationFormId == null) {
              System.err.println("Unable to identify registration form for survey: " + surveyId);
              System.exit(1);
          }
  
          // retrieve all monitoring surveys for survey group
          List<Entity> dataPoints = retrieveSurveyInstances(ds, registrationFormId);
          Map<String, Long> surveyedLocaleDeviceIdentifierMap = mapDeviceIdentifierSurveyedLocaleId(dataPoints);
  
          // for each monitoring survey
          for (Entity monitoringForm : surveyForms) {
              Long formId = new Long(monitoringForm.getKey().getId());
              if (registrationFormId.equals(formId)) {
                  continue; // skip registration form processing
              }
  
              List<Entity> monitoringFormInstances = retrieveSurveyInstances(ds, formId);
              List<Entity> correctedMonitoringFormInstances = correctSurveyedLocale(
                      monitoringFormInstances, surveyedLocaleDeviceIdentifierMap, ds);
              batchSaveEntities(ds, correctedMonitoringFormInstances);
          }
      }
  
      /**
       * Retrieve the monitoring survey entity
       *
       * @param ds
       * @param surveyId
       * @return
       * @throws EntityNotFoundException
       */
      private Entity retrieveMonitoringSurvey(DatastoreService ds, Long surveyId)
              throws EntityNotFoundException {
          return ds.get(KeyFactory.createKey(SURVEY_KIND, surveyId));
      }
  
      /**
       * Retrieve all forms belonging to the monitoring survey
       *
       * @param ds
       * @param surveyId
       * @return
       */
      private List<Entity> retrieveSurveyForms(DatastoreService ds, Long surveyId) {
          Filter surveyFilter = new Query.FilterPredicate("surveyGroupId", FilterOperator.EQUAL,
                  surveyId);
          Query q = new Query(FORM_KIND).setFilter(surveyFilter);
          return ds.prepare(q).asList(FetchOptions.Builder.withDefaults());
      }
  
      /**
       * Retrieve all form instances (responses) linked to a form Id.
       *
       * @param ds
       * @param formId
       * @return
       */
      private List<Entity> retrieveSurveyInstances(DatastoreService ds, Long formId) {
          Filter formFilter = new Query.FilterPredicate("surveyId", FilterOperator.EQUAL,
                  formId);
          Query q = new Query(FORM_INSTANCE_KIND).setFilter(formFilter);
  
          List<Entity> formInstances = new ArrayList<Entity>();
          for (Entity formInstance : ds.prepare(q).asIterable()) {
              formInstances.add(formInstance);
          }
  
          return formInstances;
      }
  
      /**
       * Create a mapping between the identifier and the data point id (surveyedLocaleId) of
       * registration form responses
       *
       * @param dataPoints
       * @return
       */
      private Map<String, Long> mapDeviceIdentifierSurveyedLocaleId(List<Entity> dataPoints) {
          Map<String, Long> dataPointIdentifiersMap = new HashMap<String, Long>();
          for (Entity dataPoint : dataPoints) {
              String identifier = (String) dataPoint.getProperty(DATA_POINT_STRING_ID);
              Long dataPointId = (Long) dataPoint.getProperty(DATA_POINT_NUMERICAL_ID);
              if (identifier != null && dataPointId != null) {
                  dataPointIdentifiersMap.put(identifier, dataPointId);
              } else {
                  System.out.println(dataPoint.getKey() + " missing identifier (" + identifier
                          + ") or surveyedLocaleId (" + dataPointId + ")");
              }
          }
          return dataPointIdentifiersMap;
      }
  
      /**
       * Reset the surveyedLocaleId for instances with matching identifiers and non-matching surveyed
       * locale ids
       *
       * @param monitoringFormInstances
       * @param surveyedLocaleDeviceIdentifierMap
       * @param ds TODO
       * @return
       */
      private List<Entity> correctSurveyedLocale(List<Entity> monitoringFormInstances,
              Map<String, Long> surveyedLocaleDeviceIdentifierMap, DatastoreService ds) {
          List<Entity> correctedFormInstances = new ArrayList<Entity>();
          List<Long> oldSurveyedLocaleIds = new ArrayList<Long>();
  
          for (Entity formInstance : monitoringFormInstances) {
              String identifier = (String) formInstance.getProperty(DATA_POINT_STRING_ID);
              Long surveyedLocaleId = (Long) formInstance.getProperty(DATA_POINT_NUMERICAL_ID);
  
              if (identifier == null || !surveyedLocaleDeviceIdentifierMap.containsKey(identifier)) {
                  System.out.println(formInstance.getKey() + " missing identifier");
                  continue;
              }
  
              Long dataPointId = surveyedLocaleDeviceIdentifierMap.get(identifier);
              if (!dataPointId.equals(surveyedLocaleId)) {
                  formInstance.setProperty(DATA_POINT_NUMERICAL_ID, dataPointId);
                  correctedFormInstances.add(formInstance);
                  System.out.println("Changing " + formInstance.getKey() + ";" + identifier + ";"
                          + surveyedLocaleId + "=>"
                          + dataPointId);
                  oldSurveyedLocaleIds.add(surveyedLocaleId);
              }
          }
  
          batchDelete(ds, oldSurveyedLocaleIds, DATA_POINT_KIND);
          return correctedFormInstances;
      }
  }
  