/* UtilLib - Copyright (c) 2005-2014 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.text.collation;

import java.io.Serializable;
import java.text.CollationElementIterator;
import java.text.RuleBasedCollator;
import java.util.ArrayList;
import java.util.List;
import net.mapeadores.util.text.SubstringPosition;


/**
 * La classe CollationUnit représente une chaîne « nettoyée » par une instance
 * de RuleBasedCollator. Par « nettoyée », on entend le fait que chaque
 * caractère de la chaîne source est évaluée par le RuleBasedCollator et
 * remplacé le ou les caractères qui le représente dans le premier ordre
 * (primaryOrder) du RuleBasedCollator.
 * <p>
 * Par exemple, « É » sera traité comme un « e » et « œ » comme « oe ». Un
 * caractère de la chaîne d'origine peut être remplacé par 0, 1 ou plusieurs
 * caractères dans la chaîne « collatée ».
 * <p>
 * Un tableau de correspondance est conservé afin de signaler à quel caractère
 * de la chaîne d'origine correspond un caractère de la chaîne « nettoyée ».
 * <p>
 * Les instances de CollationUnit sont utilisées pour faire des tris et des
 * recherches sur des chaînes en tenant compte des règles d'un RuleBaseCollator.
 * <p>
 * L'utilisation de CollationUnit poursuit les mêmes but que la classe
 * CollationKey du paquetage java.text avec les différences suivantes :
 * <ul>
 * <li>CollationUnit ne tient compte que du premier ordre (voir le concept de
 * collator.getStength() == Collator.PRIMARY). Il occupe deux fois moins de
 * place.
 * <li>Le résultat de CollationUnit est une chapine (obtenue avec la méthode
 * toString()). Il est donc possible de bénéficier de toutes les méthodes sur
 * les chaînes pour faire des recherches ou des tris.
 * </ul>
 * <p>
 * Attention, la chaîne retournée par toString() permet des traitements de
 * classements mais n'est pas lisible pour autant. Autrement dit, pour la chaîne
 * d'origine « Œuvre », toString() ne retourne pas « oeuvre » mais « aWghdW »
 * (que renvoie également « oeuvre »). Il n'est pas possible de reconstituer
 * cette chaîne simplifiée.
 * <p>
 * Voir le code source du constructeur pour plus de détails.
 *
 * @author Vincent Calame
 */
public final class CollationUnit implements Serializable {

    private final String sourceString;
    private final String collatedString;
    private final int[] correspondances;

    /**
     * Crèe une nouvelle instance de CollationUnit avec
     * <code>sourceString</code> comme chaîne d'origine en utilisant les règles
     * de « collation » fixée par <code>collator</code>.
     * <p>
     * Note :  <code>collator </code> doit être une instance de RuleBasedCollator
     * et non de Collator car le constructeur se base sur une instance de
     * CollationElementIterator.
     */
    public CollationUnit(String sourceString, RuleBasedCollator collator) {
        this.sourceString = sourceString;
        CollationElementIterator collationElementIterator = collator.getCollationElementIterator(sourceString);
        StringBuilder buf = new StringBuilder();
        List<Integer> correspondancesList = new ArrayList<Integer>();
        boolean onSpaceChar = false;
        while (true) {
            int order = collationElementIterator.next();
            int offset = collationElementIterator.getOffset() - 1;
            if (order == CollationElementIterator.NULLORDER) {
                break;
            }
            int primary = CollationElementIterator.primaryOrder(order);
            char c = sourceString.charAt(offset);
            if (primary > 0) {
                if (assimilateToSpace(c)) {
                    if (!onSpaceChar) {
                        buf.append((char) 0);
                        correspondancesList.add(offset);
                        onSpaceChar = true;
                    }
                } else {
                    buf.append((char) primary);
                    correspondancesList.add(offset);
                    onSpaceChar = false;
                }
            } else if (primary == 0) {
                if (!onSpaceChar) {
                    if (Character.isSpaceChar(c)) {
                        buf.append((char) 0);
                        correspondancesList.add(offset);
                        onSpaceChar = true;
                    }
                }
            }
        }
        collatedString = buf.toString();
        int size = correspondancesList.size();
        correspondances = new int[size];
        for (int i = 0; i < size; i++) {
            correspondances[i] = correspondancesList.get(i);
        }
    }

    /**
     * Retourne la chaîne à l'origine
     */
    public String getSourceString() {
        return sourceString;
    }

    public String getCollatedString() {
        return collatedString;
    }

    /**
     * Retourne le nombre de caractères de la chaîne après « collation ». On n'a
     * pas forcément <em>getSourceString().length() == lenght()</em>.
     * <p>
     * Cette méthode est raccourci de <em>toString().length()</em>
     */
    public int length() {
        return collatedString.length();
    }

    /**
     * Retourne sous forme d'objet String la chaîne après « collation ».
     */
    @Override
    public String toString() {
        return collatedString;
    }

    /*
     * Retourne l'index dans la chaîne d'origine du caractère qui a produit le caractère à la position <code>collatedIndex</code> dans la chaîne « nettoyée » résultant.
     */
    public int sourceIndexOf(int collatedIndex) {
        return correspondances[collatedIndex];
    }

    public SubstringPosition getSubstringPositionInSourceString(SubstringPosition positionInCollatedString) {
        int startIndex = sourceIndexOf(positionInCollatedString.getBeginIndex());
        int endIndex = sourceIndexOf(positionInCollatedString.getEndIndex());
        return new SubstringPosition(startIndex, endIndex - startIndex + 1);
    }

    /**
     * Méthode statique qui retourne le même résultat que (new
     * CollationUnit(sourceString,collationElementIterator)).toString(), à ceci
     * près qu'elle ne fait pas de traitement sur les correspondances entre les
     * caractères de la source et ceux du résultat.
     */
    public static String collate(String sourceString, RuleBasedCollator collator) {
        return collate(sourceString, collator, false);
    }

    public static String collate(String sourceString, RuleBasedCollator collator, boolean ignoreNonLetter) {
        if ((sourceString == null) || (sourceString.length() == 0)) {
            return "";
        }
        CollationElementIterator collationElementIterator = collator.getCollationElementIterator(sourceString);
        StringBuilder buf = new StringBuilder();
        boolean onSpaceChar = false;
        while (true) {
            int order = collationElementIterator.next();
            int offset = collationElementIterator.getOffset() - 1;
            if (order == CollationElementIterator.NULLORDER) {
                break;
            }
            int primary = CollationElementIterator.primaryOrder(order);
            char c = sourceString.charAt(offset);
            if (primary > 0) {
                if (assimilateToSpace(c)) {
                    if (!ignoreNonLetter) {
                        if (!onSpaceChar) {
                            buf.append((char) 0);
                            onSpaceChar = true;
                        }
                    }
                } else {
                    buf.append((char) primary);
                    onSpaceChar = false;
                }
            } else if (primary == 0) {
                if (!ignoreNonLetter) {
                    if (!onSpaceChar) {
                        if (Character.isSpaceChar(c)) {
                            buf.append((char) 0);
                            onSpaceChar = true;
                        }
                    }
                }
            }
        }
        return buf.toString();
    }

    public static boolean checkReplace(String oldSourceString, String newSourceString) {
        boolean replace = false;
        int newLength = newSourceString.length();
        int oldLength = oldSourceString.length();
        if (newLength < oldLength) {
            replace = true;
        } else if (newLength == oldLength) {
            if (newSourceString.compareTo(oldSourceString) > 0) {
                replace = true;
            }
        }
        return replace;
    }

    private static boolean assimilateToSpace(char carac) {
        switch (Character.getType(carac)) {
            case Character.CONTROL:
            case Character.CONNECTOR_PUNCTUATION:
            case Character.FINAL_QUOTE_PUNCTUATION:
            case Character.INITIAL_QUOTE_PUNCTUATION:
            case Character.OTHER_PUNCTUATION:
            case Character.LINE_SEPARATOR:
            case Character.PARAGRAPH_SEPARATOR:
            case Character.SPACE_SEPARATOR:
                return true;
            default:
                return false;
        }
    }

}
