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

import android.support.annotation.NonNull;

import org.hisp.dhis.android.core.common.D2CallException;
import org.hisp.dhis.android.core.common.D2CallExecutor;
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.organisationunit.OrganisationUnitModel;
import org.hisp.dhis.android.core.user.UserOrganisationUnitLinkModel;
import org.hisp.dhis.android.core.user.UserOrganisationUnitLinkStore;
import org.hisp.dhis.android.core.user.UserOrganisationUnitLinkStoreInterface;

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

import retrofit2.Retrofit;

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

    private final boolean limitByOrgUnit;
    private final int teiLimit;
    private final DatabaseAdapter databaseAdapter;
    private final Retrofit retrofit;
    private final UserOrganisationUnitLinkStoreInterface userOrganisationUnitLinkStore;

    private TrackedEntityInstanceWithLimitCall(
            @NonNull DatabaseAdapter databaseAdapter,
            @NonNull Retrofit retrofit,
            @NonNull UserOrganisationUnitLinkStoreInterface userOrganisationUnitLinkStore,
            int teiLimit,
            boolean limitByOrgUnit) {
        this.databaseAdapter = databaseAdapter;
        this.retrofit = retrofit;
        this.userOrganisationUnitLinkStore = userOrganisationUnitLinkStore;
        this.teiLimit = teiLimit;
        this.limitByOrgUnit = limitByOrgUnit;
    }

    @Override
    public List<TrackedEntityInstance> call() throws D2CallException {
        this.setExecuted();

        return new D2CallExecutor().executeD2CallTransactionally(databaseAdapter,
                new Callable<List<TrackedEntityInstance>>() {
                    @Override
                    public List<TrackedEntityInstance> call() throws D2CallException {
                        return getTrackedEntityInstances();
                    }
                });
    }
    
    private List<TrackedEntityInstance> getTrackedEntityInstances() throws D2CallException {
        Set<String> organisationUnitUids;
        List<TrackedEntityInstance> trackedEntityInstances = new ArrayList<>();
        TeiQuery.Builder teiQueryBuilder = TeiQuery.Builder.create();
        int pageSize = teiQueryBuilder.build().getPageSize();
        int numPages = (int) Math.ceil((double) teiLimit / pageSize);

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

        return trackedEntityInstances;
    }

    private List<TrackedEntityInstance> getTrackedEntityInstancesWithPaging(
            TeiQuery.Builder teiQueryBuilder, int pageSize, int numPages)
            throws D2CallException {
        List<TrackedEntityInstance> trackedEntityInstances = new ArrayList<>();
        D2CallExecutor executor = new D2CallExecutor();

        for (int page = 1; page <= numPages; page++) {
            if (page == numPages) {
                teiQueryBuilder.withPage(page).withPageLimit(teiLimit - ((page - 1) * pageSize));
            }
            teiQueryBuilder.withPage(page);
            List<TrackedEntityInstance> pageTrackedEntityInstances = executor.executeD2Call(
                    TrackedEntityInstancesEndpointCall.create(retrofit, teiQueryBuilder.build()));

            trackedEntityInstances.addAll(pageTrackedEntityInstances);

            if (pageTrackedEntityInstances.size() < pageSize) {
                break;
            }
        }

        executor.executeD2Call(
                TrackedEntityInstancePersistenceCall.create(databaseAdapter, retrofit, trackedEntityInstances));

        return trackedEntityInstances;
    }

    private Set<String> getOrgUnitUids() {
        Set<UserOrganisationUnitLinkModel> userOrganisationUnitLinks = userOrganisationUnitLinkStore.selectAll(
                UserOrganisationUnitLinkModel.factory);

        Set<String> organisationUnitUids = new HashSet<>();

        for (UserOrganisationUnitLinkModel linkModel: userOrganisationUnitLinks) {
            if (linkModel.organisationUnitScope().equals(
                    OrganisationUnitModel.Scope.SCOPE_DATA_CAPTURE.name())) {
                organisationUnitUids.add(linkModel.organisationUnit());
            }
        }

        return organisationUnitUids;
    }

    public static TrackedEntityInstanceWithLimitCall create(DatabaseAdapter databaseAdapter, Retrofit retrofit,
                                                            int teiLimit, boolean limitByOrgUnit) {
        return new TrackedEntityInstanceWithLimitCall(
                databaseAdapter,
                retrofit,
                UserOrganisationUnitLinkStore.create(databaseAdapter),
                teiLimit,
                limitByOrgUnit
        );
    }
}
@@ -662,16 +662,139 @@
 terface;
+%0Aimport org.hisp.dhis.android.core.utils.services.ApiPagingEngine;%0Aimport org.hisp.dhis.android.core.utils.services.Paging;
 %0A%0Aimport
@@ -2696,67 +2696,86 @@
     
-int numPages = (int) Math.ceil((double) teiLimit / pageSize
+List%3CPaging%3E pagingList = ApiPagingEngine.getPaginationList(pageSize, teiLimit
 );%0A%0A
@@ -3228,31 +3228,23 @@
 der, pag
-eSize, numPages
+ingList
 ));%0A    
@@ -3560,23 +3560,15 @@
  pag
-eSize, numPages
+ingList
 );%0A 
@@ -3750,34 +3750,31 @@
 er, 
-int pageSize, int numPages
+List%3CPaging%3E pagingList
 )%0A  
@@ -3962,180 +3962,118 @@
 or (
-int page = 1; page %3C= numPages; page++) %7B%0A            if (page == numPages) %7B%0A                teiQueryBuilder.withPage(page).withPageLimit(teiLimit - ((page - 1) * 
+Paging paging : pagingList) %7B%0A            teiQueryBuilder.withPage(paging.page()).withPageSize(paging.
 pageSize
 ));%0A
@@ -4072,69 +4072,13 @@
 Size
+(
 ));%0A
-            %7D%0A            teiQueryBuilder.withPage(page);
 %0A   
@@ -4263,24 +4263,67 @@
 build()));%0A%0A
+            if (paging.isLastPage()) %7B%0A    
             
@@ -4378,18 +4378,292 @@
 nstances
-);
+.subList(%0A                        paging.previousItemsToSkipCount(),%0A                        pageTrackedEntityInstances.size() - paging.posteriorItemsToSkipCount()));%0A            %7D else %7B%0A                trackedEntityInstances.addAll(pageTrackedEntityInstances);%0A            %7D
 %0A%0A      
@@ -4708,24 +4708,31 @@
 ize() %3C 
+paging.
 pageSize
 ) %7B%0A    
@@ -4723,16 +4723,18 @@
 pageSize
+()
 ) %7B%0A    

fix TrackedEntityInstanceWithLimitCall