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


import android.support.annotation.NonNull;

import org.hisp.dhis.android.core.common.D2CallException;
import org.hisp.dhis.android.core.common.GenericCallData;
import org.hisp.dhis.android.core.common.IdentifiableObjectStore;
import org.hisp.dhis.android.core.common.Payload;
import org.hisp.dhis.android.core.common.SyncCall;
import org.hisp.dhis.android.core.data.api.OuMode;
import org.hisp.dhis.android.core.data.database.DatabaseAdapter;
import org.hisp.dhis.android.core.data.database.Transaction;
import org.hisp.dhis.android.core.organisationunit.OrganisationUnitModel;
import org.hisp.dhis.android.core.organisationunit.OrganisationUnitStore;
import org.hisp.dhis.android.core.systeminfo.SystemInfo;
import org.hisp.dhis.android.core.systeminfo.SystemInfoCall;
import org.hisp.dhis.android.core.trackedentity.TeiQuery;
import org.hisp.dhis.android.core.trackedentity.TeisEndPointCall;
import org.hisp.dhis.android.core.trackedentity.TrackedEntityInstance;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import retrofit2.Response;
import retrofit2.Retrofit;

public final class TrackerEntitiesDataCall extends SyncCall<List<TrackedEntityInstance>> {

    private final boolean limitByOrgUnit;
    private final int teiLimit;
    private final DatabaseAdapter databaseAdapter;
    private final Retrofit retrofit;
    private final IdentifiableObjectStore<OrganisationUnitModel> organisationUnitStore;
    private final D2CallException.Builder httpExceptionBuilder;

    private TrackerEntitiesDataCall(@NonNull DatabaseAdapter databaseAdapter,
                                    @NonNull Retrofit retrofit,
                                    @NonNull IdentifiableObjectStore<OrganisationUnitModel> organisationUnitStore,
                                    int teiLimit,
                                    boolean limitByOrgUnit) {
        this.databaseAdapter = databaseAdapter;
        this.retrofit = retrofit;
        this.organisationUnitStore = organisationUnitStore;
        this.teiLimit = teiLimit;
        this.limitByOrgUnit = limitByOrgUnit;
        this.httpExceptionBuilder = D2CallException.builder().isHttpError(true).errorDescription("TEIs call failed");
    }

    @Override
    public List<TrackedEntityInstance> call() throws D2CallException {
        this.setExecuted();
        Transaction transaction = databaseAdapter.beginNewTransaction();

        try {
            Response<SystemInfo> systemInfoResponse = SystemInfoCall.FACTORY.create(databaseAdapter, retrofit).call();

            if (!systemInfoResponse.isSuccessful()) {
                throw httpExceptionBuilder.httpErrorCode(systemInfoResponse.code()).build();
            }

            SystemInfo systemInfo = systemInfoResponse.body();
            GenericCallData genericCallData = GenericCallData.create(databaseAdapter, retrofit,
                    systemInfo.serverDate());

            return trackerCall(genericCallData);

        } catch (Exception e) {
            throw httpExceptionBuilder.originalException(e).build();
        } finally {
            transaction.end();
        }
    }
    
    private List<TrackedEntityInstance> trackerCall(GenericCallData genericCallData) throws D2CallException {
        Set<String> organisationUnitUids = organisationUnitStore.selectUids();
        List<TrackedEntityInstance> trackedEntityInstances = new ArrayList<>();
        TeiQuery.Builder teiQueryBuilder = TeiQuery.Builder.create();
        int pageSize = TeiQuery.Builder.create().build().getPageSize();
        int numPages = (int) Math.ceil((double) teiLimit / pageSize);

        if (limitByOrgUnit) {
            Set<String> orgUnitWrapper = new HashSet<>();
            for (String orgUnitUid : organisationUnitUids) {
                orgUnitWrapper.clear();
                orgUnitWrapper.add(orgUnitUid);
                teiQueryBuilder.withOrgUnits(orgUnitWrapper);
                trackedEntityInstances.addAll(getTrackedEntityInstances(teiQueryBuilder, genericCallData, pageSize,
                        numPages));
            }
        } else {
            teiQueryBuilder.withOrgUnits(organisationUnitUids).withOuMode(OuMode.DESCENDANTS);
            trackedEntityInstances = getTrackedEntityInstances(teiQueryBuilder, genericCallData, pageSize, numPages);
        }

        return trackedEntityInstances;
    }

    private List<TrackedEntityInstance> getTrackedEntityInstances(TeiQuery.Builder teiQueryBuilder,
            GenericCallData genericCallData, int pageSize, int numPages) throws D2CallException {
        Response<Payload<TrackedEntityInstance>> trackerCallResponse = null;
        List<TrackedEntityInstance> trackedEntityInstances = new ArrayList<>();

        for (int page = 1; page <= numPages; page++) {
            try {
                if (page == numPages) {
                    teiQueryBuilder.withPage(page).withPageLimit(teiLimit - ((page - 1) * pageSize));
                }
                teiQueryBuilder.withPage(page);
                 trackerCallResponse = TeisEndPointCall.create(genericCallData, teiQueryBuilder.build()).call();

                if (!trackerCallResponse.isSuccessful()) {
                    throw httpExceptionBuilder.httpErrorCode(trackerCallResponse.code()).build();
                }

                trackedEntityInstances.addAll(trackerCallResponse.body().items());
            } catch (Exception e) {
                throw httpExceptionBuilder.originalException(e).build();
            }
        }
        return trackedEntityInstances;
    }

    public static TrackerEntitiesDataCall create(DatabaseAdapter databaseAdapter, Retrofit retrofit,
                                                 int teiLimit, boolean limitByOrgUnit) {
        return new TrackerEntitiesDataCall(
                databaseAdapter,
                retrofit,
                OrganisationUnitStore.create(databaseAdapter),
                teiLimit,
                limitByOrgUnit
        );
    }
}
@@ -1001,16 +1001,165 @@
 nstance;
+%0Aimport org.hisp.dhis.android.core.user.UserOrganisationUnitLinkStore;%0Aimport org.hisp.dhis.android.core.user.UserOrganisationUnitLinkStoreInterface;
 %0A%0Aimport
@@ -1645,24 +1645,112 @@
 nUnitStore;%0A
+    private final UserOrganisationUnitLinkStoreInterface userOrganisationUnitLinkStore;%0A
     private 
@@ -1838,16 +1838,29 @@
 ataCall(
+%0A            
 @NonNull
@@ -1897,32 +1897,8 @@
 er,%0A
-                        
     
@@ -1937,32 +1937,8 @@
 it,%0A
-                        
     
@@ -2032,36 +2032,103 @@
             
-            
+@NonNull UserOrganisationUnitLinkStoreInterface userOrganisationUnitLinkStore,%0A
             
@@ -2145,32 +2145,8 @@
 it,%0A
-                        
     
@@ -2317,24 +2317,100 @@
 nUnitStore;%0A
+        this.userOrganisationUnitLinkStore = userOrganisationUnitLinkStore;%0A
         this
@@ -3668,45 +3668,8 @@
 Uids
- = organisationUnitStore.selectUids()
 ;%0A  
@@ -3981,32 +3981,103 @@
 mitByOrgUnit) %7B%0A
+            organisationUnitUids = organisationUnitStore.selectUids();%0A
             Set%3C
@@ -4512,16 +4512,114 @@
  else %7B%0A
+            organisationUnitUids = userOrganisationUnitLinkStore.queryRootOrganisationUnitUids();%0A
         
@@ -5150,15 +5150,8 @@
 onse
- = null
 ;%0A  
@@ -6418,16 +6418,87 @@
 apter),%0A
+                UserOrganisationUnitLinkStore.create(databaseAdapter),%0A
         

call by root orgUnits