/* UtilLib - Copyright (c) 2011-2018 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package net.mapeadores.util.conditions;

import java.text.Collator;
import java.text.RuleBasedCollator;
import java.util.List;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.text.collation.CollationUnit;


/**
 *
 * @author Vincent Calame
 */
public class TextTestEngine {

    private final RuleBasedCollator collator;
    private final boolean andMode;
    private final Tester tester;
    private final Lang lang;
    private final CollatedStringTestUnit[] excludingTestUnitArray;
    private final CollatedStringTestUnit[] includingTestUnitArray;

    private TextTestEngine(TextCondition condition, Lang lang) {
        this.lang = lang;
        collator = (RuleBasedCollator) Collator.getInstance(lang.toLocale());
        collator.setStrength(Collator.PRIMARY);
        List<TextTest> excludingList = condition.getExcludingTextTestList();
        int excludingLength = excludingList.size();
        if (excludingLength > 0) {
            excludingTestUnitArray = new CollatedStringTestUnit[excludingLength];
            for (int i = 0; i < excludingLength; i++) {
                excludingTestUnitArray[i] = CollatedStringTestUnit.newInstance(excludingList.get(i), collator);
            }
        } else {
            excludingTestUnitArray = null;
        }
        List<TextTest> includingList = condition.getIncludingTextTestList();
        int includingLength = includingList.size();
        if (includingLength > 0) {
            includingTestUnitArray = new CollatedStringTestUnit[includingLength];
            for (int i = 0; i < includingLength; i++) {
                includingTestUnitArray[i] = CollatedStringTestUnit.newInstance(includingList.get(i), collator);
            }
        } else {
            includingTestUnitArray = null;
        }
        andMode = condition.isAndOperator();
        if (andMode) {
            tester = new AndTester();
        } else {
            tester = new OrTester();
        }
    }

    public Lang getLang() {
        return lang;
    }

    public void start() {
        tester.clear();
    }

    public boolean canStop() {
        return tester.canStop();
    }

    public boolean addString(String s) {
        String collatedString = CollationUnit.collate(s, collator);
        tester.addString(collatedString);
        if (tester.canStop()) {
            return true;
        } else {
            return false;
        }
    }

    public boolean getResult() {
        return tester.getResult();
    }

    public boolean isSelected(String s) {
        String collatedString;
        if ((s == null) || (s.length() == 0)) {
            collatedString = "";
        } else {
            collatedString = CollationUnit.collate(s, collator);
        }
        if (andMode) {
            return isAndAccepted(collatedString);
        } else {
            return isOrAccepted(collatedString);
        }
    }

    private boolean isAndAccepted(String collatedString) {
        if (includingTestUnitArray != null) {
            for (CollatedStringTestUnit testUnit : includingTestUnitArray) {
                if (!testUnit.accept(collatedString)) {
                    return false;
                }
            }
        }
        if (excludingTestUnitArray != null) {
            for (CollatedStringTestUnit testUnit : excludingTestUnitArray) {
                if (!testUnit.accept(collatedString)) {
                    return false;
                }
            }
        }
        return true;
    }

    private boolean isOrAccepted(String collatedString) {
        if (includingTestUnitArray != null) {
            for (CollatedStringTestUnit testUnit : includingTestUnitArray) {
                if (testUnit.accept(collatedString)) {
                    return true;
                }
            }
        }
        if (excludingTestUnitArray != null) {
            for (CollatedStringTestUnit testUnit : excludingTestUnitArray) {
                if (testUnit.accept(collatedString)) {
                    return true;
                }
            }
        }
        return false;
    }

    public static TextTestEngine newInstance(TextCondition condition, Lang lang) {
        if (condition == null) {
            throw new IllegalArgumentException("condition is null");
        }
        if (lang == null) {
            throw new IllegalArgumentException("lang is null");
        }
        return new TextTestEngine(condition, lang);
    }


    private abstract class Tester {

        protected boolean doExclude;
        protected boolean doInclude;

        Tester() {
            init();
        }

        void init() {
            doExclude = (excludingTestUnitArray != null);
            doInclude = (includingTestUnitArray != null);
        }

        abstract void addString(String collatedString);

        abstract boolean canStop();

        abstract boolean getResult();

        abstract void clear();

    }


    private class OrTester extends Tester {

        private boolean success = false;
        private final int length;
        private final boolean[] failExcludeArray;
        private int failCount = 0;

        OrTester() {
            if (excludingTestUnitArray != null) {
                this.length = excludingTestUnitArray.length;
            } else {
                this.length = 0;
            }
            failExcludeArray = new boolean[length];
        }

        @Override
        public void addString(String collatedString) {
            if (success) {
                return;
            }
            if (doInclude) {
                int includeLength = includingTestUnitArray.length;
                for (int i = 0; i < includeLength; i++) {
                    if (includingTestUnitArray[i].accept(collatedString)) {
                        success = true;
                        return;
                    }
                }
            }
            if (doExclude) {
                for (int i = 0; i < length; i++) {
                    if (!failExcludeArray[i]) {
                        if (!excludingTestUnitArray[i].accept(collatedString)) {
                            failExcludeArray[i] = true;
                            failCount++;
                            if (failCount == length) {
                                doExclude = false;
                            }
                        }
                    }
                }
            }
        }

        @Override
        boolean canStop() {
            return (success == true);
        }

        @Override
        boolean getResult() {
            if (success) {
                return true;
            }
            if (excludingTestUnitArray == null) {
                return false;
            }
            return (failCount != length);
        }

        @Override
        void clear() {
            init();
            success = false;
            failCount = 0;
            for (int i = 0; i < length; i++) {
                failExcludeArray[i] = false;
            }
        }

    }


    private class AndTester extends Tester {

        private boolean failure;
        private final int length;
        private final boolean[] successIncludeArray;
        private int successCount = 0;

        AndTester() {
            if (includingTestUnitArray != null) {
                this.length = includingTestUnitArray.length;
            } else {
                this.length = 0;
            }
            successIncludeArray = new boolean[length];
        }

        @Override
        void addString(String collatedString) {
            if (failure) {
                return;
            }
            if (doInclude) {
                for (int i = 0; i < length; i++) {
                    if (!successIncludeArray[i]) {
                        if (includingTestUnitArray[i].accept(collatedString)) {
                            successIncludeArray[i] = true;
                            successCount++;
                            if (successCount == length) {
                                doInclude = false;
                            }
                        }
                    }
                }
            }
            if (doExclude) {
                int excludeLength = excludingTestUnitArray.length;
                for (int i = 0; i < excludeLength; i++) {
                    if (!excludingTestUnitArray[i].accept(collatedString)) {
                        failure = true;
                        return;
                    }
                }
            }
        }

        @Override
        boolean canStop() {
            return (failure == true);
        }

        @Override
        boolean getResult() {
            if (failure) {
                return false;
            }
            if (includingTestUnitArray == null) {
                return true;
            }
            return (successCount == length);
        }

        @Override
        void clear() {
            init();
            failure = false;
            successCount = 0;
            for (int i = 0; i < length; i++) {
                successIncludeArray[i] = false;
            }
        }

    }

}
