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.calls.Call;
import org.hisp.dhis.android.core.common.GenericCallData;
import org.hisp.dhis.android.core.common.Payload;
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 implements Call<Response<Payload<TrackedEntityInstance>>> {

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

    private boolean isExecuted;

    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 boolean isExecuted() {
        synchronized (this) {
            return isExecuted;
        }
    }

    @Override
    public Response<Payload<TrackedEntityInstance>> call() throws Exception {
        synchronized (this) {
            if (isExecuted) {
                throw new IllegalStateException("Already executed");
            }
            isExecuted = true;
        }

        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 =  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()
        );
    }
}

@@ -175,54 +175,8 @@
 g;%0A%0A
-import org.hisp.dhis.android.core.calls.Call;%0A
 impo
@@ -279,16 +279,67 @@
 ayload;%0A
+import org.hisp.dhis.android.core.common.SyncCall;%0A
 import o
@@ -939,32 +939,24 @@
 all 
-implement
+extend
 s 
+Sync
 Call
-%3CResponse
 %3CPay
@@ -983,17 +983,16 @@
 stance%3E%3E
-%3E
  %7B%0A%0A    
@@ -1289,41 +1289,8 @@
 r;%0A%0A
-    private boolean isExecuted;%0A%0A
     
@@ -1975,384 +1975,103 @@
 lic 
-boolean isExecuted() %7B%0A        synchronized (this) %7B%0A            return isExecuted;%0A        %7D%0A    %7D%0A%0A    @Override%0A    public Response%3CPayload%3CTrackedEntityInstance%3E%3E call() throws Exception %7B%0A        synchronized (this) %7B%0A            if (isExecuted) %7B%0A                throw new IllegalStateException(%22Already e
+Response%3CPayload%3CTrackedEntityInstance%3E%3E call() throws Exception %7B%0A        super.setE
 xecuted
-%22
+(
 );
-%0A            %7D%0A            isExecuted = true;%0A        %7D
 %0A%0A  
@@ -2839,16 +2839,30 @@
  size = 
+Math.min(size,
  tracker
@@ -2881,16 +2881,17 @@
 eLimit()
+)
 ;%0A      

check both size and pageLimit