package org.hisp.dhis.android.core.trackedentity;

import android.database.sqlite.SQLiteConstraintException;
import android.support.annotation.NonNull;
import android.util.Log;

import org.hisp.dhis.android.core.common.GenericCallData;
import org.hisp.dhis.android.core.common.Payload;
import org.hisp.dhis.android.core.common.SyncCall;
import org.hisp.dhis.android.core.data.api.Fields;
import org.hisp.dhis.android.core.data.database.DatabaseAdapter;
import org.hisp.dhis.android.core.data.database.Transaction;
import org.hisp.dhis.android.core.enrollment.Enrollment;
import org.hisp.dhis.android.core.enrollment.note.Note;
import org.hisp.dhis.android.core.event.Event;
import org.hisp.dhis.android.core.relationship.Relationship;
import org.hisp.dhis.android.core.resource.ResourceHandler;
import org.hisp.dhis.android.core.resource.ResourceModel;

import java.util.List;

import retrofit2.Response;

public final class TeisEndPointCall extends SyncCall<Payload<TrackedEntityInstance>> {

    private final TrackedEntityInstanceService trackedEntityInstanceService;
    private final DatabaseAdapter databaseAdapter;
    private final TeiQuery trackerQuery;
    private final TrackedEntityInstanceHandler trackedEntityInstanceHandler;
    private final ResourceHandler resourceHandler;

    private TeisEndPointCall(@NonNull TrackedEntityInstanceService trackedEntityInstanceService,
                     @NonNull DatabaseAdapter databaseAdapter,
                     @NonNull TeiQuery trackerQuery,
                     @NonNull TrackedEntityInstanceHandler trackedEntityInstanceHandler,
                     @NonNull ResourceHandler resourceHandler) {

        this.databaseAdapter = databaseAdapter;
        this.trackedEntityInstanceService = trackedEntityInstanceService;
        this.trackerQuery = trackerQuery;
        this.trackedEntityInstanceHandler = trackedEntityInstanceHandler;
        this.resourceHandler = resourceHandler;
    }

    @Override
    public Response<Payload<TrackedEntityInstance>> call() throws Exception {
        super.setExecuted();

        String lastSyncedTEIs = resourceHandler.getLastUpdated(ResourceModel.Type.TRACKED_ENTITY_INSTANCE);

        Response<Payload<TrackedEntityInstance>> response;

        response = trackedEntityInstanceService.getTEIs(trackerQuery.getOrgUnit(),
                TrackedEntityInstance.lastUpdated.gt(lastSyncedTEIs), fields(),
                Boolean.TRUE, trackerQuery.getPage(), trackerQuery.getPageSize()).execute();

        if (response.isSuccessful() && response.body().items() != null
                && response.body().items().size()>0) {
            List<TrackedEntityInstance> trackedEntityInstances = response.body().items();
            int size = trackedEntityInstances.size();

            if (trackerQuery.getPageLimit() > 0) {
                size = Math.min(size, trackerQuery.getPageLimit());
            }

            for (int i = 0; i < size; i++) {
                Transaction transaction = databaseAdapter.beginNewTransaction();
                TrackedEntityInstance trackedEntityInstance = trackedEntityInstances.get(i);
                try {
                    trackedEntityInstanceHandler.handle(trackedEntityInstance);
                    transaction.setSuccessful();
                } catch (SQLiteConstraintException sql) {
                    /*
                    This catch is necessary to ignore events with bad foreign keys exception
                    More info: If the foreign key have the flag
                    DEFERRABLE INITIALLY DEFERRED this exception will be throw in transaction
                    .end()
                    And the rollback will be executed only when the database is closed.
                    It is a reported as unfixed bug: https://issuetracker.google
                    .com/issues/37001653
                    */
                    Log.d(this.getClass().getSimpleName(), sql.getMessage());
                } finally {
                    transaction.end();
                }
            }
        }

        return response;
    }

    private Fields<TrackedEntityInstance> fields() {
        return Fields.<TrackedEntityInstance>builder().fields(
                TrackedEntityInstance.uid, TrackedEntityInstance.created,
                TrackedEntityInstance.lastUpdated,
                TrackedEntityInstance.organisationUnit,
                TrackedEntityInstance.trackedEntityType,
                TrackedEntityInstance.deleted,
                TrackedEntityInstance.relationships.with(Relationship.allFields),
                TrackedEntityInstance.trackedEntityAttributeValues.with(
                        TrackedEntityAttributeValue.trackedEntityAttribute,
                        TrackedEntityAttributeValue.value,
                        TrackedEntityAttributeValue.created,
                        TrackedEntityAttributeValue.lastUpdated),
                TrackedEntityInstance.enrollment.with(
                        Enrollment.uid, Enrollment.created, Enrollment.lastUpdated,
                        Enrollment.coordinate,
                        Enrollment.dateOfEnrollment, Enrollment.dateOfIncident,
                        Enrollment.enrollmentStatus,
                        Enrollment.followUp, Enrollment.program, Enrollment.organisationUnit,
                        Enrollment.trackedEntityInstance,
                        Enrollment.deleted,
                        Enrollment.events.with(
                                Event.attributeCategoryOptions, Event.attributeOptionCombo,
                                Event.uid, Event.created, Event.lastUpdated, Event.completeDate,
                                Event.coordinates,
                                Event.dueDate, Event.enrollment, Event.eventDate, Event.eventStatus,
                                Event.organisationUnit, Event.program, Event.programStage,
                                Event.deleted,
                                Event.trackedEntityDataValues.with(
                                        TrackedEntityDataValue.created,
                                        TrackedEntityDataValue.lastUpdated,
                                        TrackedEntityDataValue.dataElement,
                                        TrackedEntityDataValue.providedElsewhere,
                                        TrackedEntityDataValue.storedBy,
                                        TrackedEntityDataValue.value
                                )
                        ),
                        Enrollment.notes.with(Note.allFields)
                )
        ).build();
    }

    public static TeisEndPointCall create(GenericCallData genericCallData, TeiQuery teiQuery) {
        return new TeisEndPointCall(
                genericCallData.retrofit().create(TrackedEntityInstanceService.class),
                genericCallData.databaseAdapter(),
                teiQuery,
                TrackedEntityInstanceHandler.create(genericCallData.databaseAdapter()),
                genericCallData.resourceHandler()
        );
    }
}

@@ -385,73 +385,8 @@
 ds;%0A
-import org.hisp.dhis.android.core.data.database.DatabaseAdapter;%0A
 impo
@@ -438,24 +438,24 @@
 ransaction;%0A
+
 import org.h
@@ -918,24 +918,75 @@
 stance%3E%3E %7B%0A%0A
+    private final GenericCallData genericCallData;%0A
     private 
@@ -1054,59 +1054,8 @@
 ce;%0A
-    private final DatabaseAdapter databaseAdapter;%0A
     
@@ -1249,16 +1249,83 @@
 intCall(
+%0A            @NonNull GenericCallData genericCallData,%0A            
 @NonNull
@@ -1388,80 +1388,8 @@
 ce,%0A
-                     @NonNull DatabaseAdapter databaseAdapter,%0A         
     
@@ -1424,33 +1424,24 @@
 ackerQuery,%0A
-         
             
@@ -1520,25 +1520,16 @@
         
-       
-  
 @NonNull
@@ -1564,17 +1564,16 @@
 dler) %7B%0A
-%0A
         
@@ -1581,41 +1581,41 @@
 his.
-databaseAdapter = databaseAdapter
+genericCallData = genericCallData
 ;%0A  
@@ -2897,16 +2897,32 @@
 ction = 
+genericCallData.
 database
@@ -2928,16 +2928,18 @@
 eAdapter
+()
 .beginNe
@@ -3958,32 +3958,171 @@
  %7D%0A            %7D
+%0A%0A            resourceHandler.handleResource(ResourceModel.Type.TRACKED_ENTITY_INSTANCE,%0A                    genericCallData.serverDate());
 %0A        %7D%0A%0A    
@@ -6817,32 +6817,65 @@
 isEndPointCall(%0A
+                genericCallData,%0A
                 
@@ -6949,59 +6949,8 @@
 s),%0A
-                genericCallData.databaseAdapter(),%0A
     

call resource handler in TeisEndPointCall