package org.commcare.adapters;

import android.app.Activity;

import net.sqlcipher.database.SQLiteDatabase;

import org.commcare.CommCareApplication;
import org.commcare.cases.entity.Entity;
import org.commcare.cases.entity.NodeEntityFactory;
import org.commcare.cases.util.StringUtils;
import org.commcare.modern.util.Pair;
import org.commcare.utils.SessionUnavailableException;
import org.javarosa.core.model.instance.TreeReference;
import org.javarosa.core.services.Logger;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;


/**
 * Filter entity list via all string-representable entity fields
 */
public class EntityStringFilterer extends EntityFiltererBase {
    private final boolean isFilterEmpty;
    private final String[] searchTerms;
    private final ArrayList<Pair<Integer, Integer>> matchScores = new ArrayList<>();
    private final boolean isFuzzySearchEnabled;

    public EntityStringFilterer(EntityListAdapter adapter,
                                String[] searchTerms,
                                boolean isFuzzySearchEnabled,
                                NodeEntityFactory nodeFactory,
                                List<Entity<TreeReference>> fullEntityList,
                                Activity context) {
        super(context, nodeFactory, adapter, fullEntityList);

        this.isFuzzySearchEnabled = isFuzzySearchEnabled;
        this.isFilterEmpty = searchTerms == null || searchTerms.length == 0;
        this.searchTerms = searchTerms;

        if (isFilterEmpty) {
            matchList.addAll(fullEntityList);
        }
    }

    @Override
    protected void filter() {
        long startTime = System.currentTimeMillis();

        if (!isFilterEmpty) {
            buildMatchList();
        }

        if (isCancelled()) {
            return;
        }

        long time = System.currentTimeMillis() - startTime;
        if (time > 1000) {
            Logger.log("cache", "Presumably finished caching new entities, time taken: " + time + "ms");
        }
    }

    private void buildMatchList() {
        Locale currentLocale = Locale.getDefault();
        //It's a bit sketchy here, because this DB lock will prevent
        //anything else from processing
        SQLiteDatabase db;
        try {
            db = CommCareApplication.instance().getUserDbHandle();
        } catch (SessionUnavailableException e) {
            this.cancelSearch();
            return;
        }
        db.beginTransaction();
        try {
            for (int index = 0; index < fullEntityList.size(); ++index) {
                //Every once and a while we should make sure we're not blocking anything with the database
                if (index % 500 == 0) {
                    db.yieldIfContendedSafely();
                }
                Entity<TreeReference> e = fullEntityList.get(index);
                if (isCancelled()) {
                    db.setTransactionSuccessful();
                    return;
                }

                boolean add = false;
                int score = 0;
                filter:
                for (String filter : searchTerms) {
                    add = false;
                    for (int i = 0; i < e.getNumFields(); ++i) {
                        String field = e.getNormalizedField(i);
                        if (!"".equals(field) && field.toLowerCase(currentLocale).contains(filter)) {
                            add = true;
                            continue filter;
                        } else if (isFuzzySearchEnabled) {
                            // We possibly now want to test for edit distance for
                            // fuzzy matching
                            for (String fieldChunk : e.getSortFieldPieces(i)) {
                                Pair<Boolean, Integer> match = StringUtils.fuzzyMatch(filter, fieldChunk);
                                if (match.first) {
                                    add = true;
                                    score += match.second;
                                    continue filter;
                                }
                            }
                        }
                    }
                    if (!add) {
                        break;
                    }
                }
                if (add) {
                    matchScores.add(Pair.create(index, score));
                }
            }
            // If fuzzy search is enabled need to re-sort based on edit distance
            if (isFuzzySearchEnabled) {
                Collections.sort(matchScores, new Comparator<Pair<Integer, Integer>>() {
                    @Override
                    public int compare(Pair<Integer, Integer> lhs, Pair<Integer, Integer> rhs) {
                        return lhs.second - rhs.second;
                    }
                });
            }

            for (Pair<Integer, Integer> match : matchScores) {
                matchList.add(fullEntityList.get(match.first));
            }

            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
    }
}

@@ -318,16 +318,57 @@
 l.Pair;%0A
+import org.commcare.util.EntitySortUtil;%0A
 import o
@@ -2131,24 +2131,383 @@
    %7D%0A    %7D%0A%0A
+    private Entity%3CTreeReference%3E getEntityAtIndex(SQLiteDatabase db, int index) %7B%0A        if (index %25 500 == 0) %7B%0A            db.yieldIfContendedSafely();%0A        %7D%0A        Entity%3CTreeReference%3E e = fullEntityList.get(index);%0A        if (isCancelled()) %7B%0A            db.setTransactionSuccessful();%0A            return null;%0A        %7D%0A        return e;%0A    %7D%0A%0A
     private 
@@ -2973,984 +2973,120 @@
     
-for (int index = 0; index %3C fullEntityList.size(); ++index) %7B%0A                //Every once and a while we should make sure we're not blocking anything with the database%0A                if (index %25 500 == 0) %7B%0A                    db.yieldIfContendedSafely();%0A                %7D%0A                Entity%3CTreeReference%3E e = fullEntityList.get(index);%0A                if (isCancelled()) %7B%0A                    db.setTransactionSuccessful();%0A                    return;%0A                %7D%0A%0A                boolean add = false;%0A                int score = 0;%0A                filter:%0A                for (String filter : searchTerms) %7B%0A                    add = false;%0A                    for (int i = 0; i %3C e.getNumFields(); ++i) %7B%0A                        String field = e.getNormalizedField(i);%0A                        if (!%22%22.equals(field) && field.toLowerCase(currentLocale).contains(filter)) %7B%0A                            add = true;%0A                            continue filter;%0A   
+EntitySortUtil.sortEntities(fullEntityList,%0A                    searchTerms,%0A                    currentLocale,%0A
     
@@ -3105,20 +3105,8 @@
     
- %7D else if (
 isFu
@@ -3113,35 +3113,33 @@
 zzySearchEnabled
-) %7B
+,
 %0A               
@@ -3147,77 +3147,21 @@
     
-        // We possibly now want to test for edit distance for%0A       
+matchScores,%0A
     
@@ -3180,26 +3180,18 @@
     
- // fuzzy matching
+matchList,
 %0A   
@@ -3211,691 +3211,91 @@
     
-        for (String fieldChunk : e.getSortFieldPieces(i)) %7B%0A                                Pair%3CBoolean, Integer%3E match = StringUtils.fuzzyMatch(filter, fieldChunk);%0A                                if (match.first) %7B%0A                                    add = true;%0A                                    score += match.second;%0A                                    continue filter;%0A                                %7D%0A                            %7D%0A                        %7D%0A                    %7D%0A                    if (!add) %7B%0A                        break;%0A                    %7D%0A                %7D%0A                if (add) %7B%0A                    matchScores.add(Pair.create(index, score));
+new EntitySortUtil.EntitySortCallbackListener() %7B%0A                        @Override
 %0A   
@@ -3303,34 +3303,32 @@
             
-%7D%0A
         
     %7D%0A      
@@ -3319,605 +3319,176 @@
     
-    %7D%0A            // If fuzzy search is enabled need to re-sort based on edit distance%0A            if (isFuzzySearchEnabled) %7B%0A                Collections.sort(matchScores, new Comparator%3CPair%3CInteger, Integer%3E%3E() %7B%0A                    @Override%0A                    public int compare(Pair%3CInteger, Integer%3E lhs, Pair%3CInteger, Integer%3E rhs) %7B%0A                        return lhs.second - rhs.second;%0A                    %7D%0A                %7D);%0A            %7D%0A%0A            for (Pair%3CInteger, Integer%3E match : matchScores) %7B%0A                matchList.add(fullEntityList.get(match.first));%0A            %7D%0A
+protected Entity%3CTreeReference%3E getEntity(int index) %7B%0A                            return getEntityAtIndex(db, index);%0A                        %7D%0A                    %7D);
 %0A   

Use EntitySortUtil