/* FichothequeLib_API - Copyright (c) 2006-2023 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.fichotheque;

import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import net.mapeadores.util.text.StringUtils;


/**
 *
 * @author Vincent Calame
 */
public final class SubsetKey implements Comparable {

    private final static Map<String, SubsetKey> internMap = new HashMap<String, SubsetKey>();
    public final static short CATEGORY_CORPUS = 1;
    public final static short CATEGORY_THESAURUS = 2;
    public final static short CATEGORY_SPHERE = 3;
    public final static short CATEGORY_ADDENDA = 4;
    public final static short CATEGORY_ALBUM = 5;
    public final static String CATEGORY_CORPUS_STRING = "corpus";
    public final static String CATEGORY_THESAURUS_STRING = "thesaurus";
    public final static String CATEGORY_SPHERE_STRING = "sphere";
    public final static String CATEGORY_ADDENDA_STRING = "addenda";
    public final static String CATEGORY_ALBUM_STRING = "album";
    private final String subsetKeyString;
    private final String subsetName;
    private final short category;

    private SubsetKey(String subsetKeyString, short category, String subsetName) {
        this.category = category;
        this.subsetKeyString = subsetKeyString;
        this.subsetName = subsetName;
        intern(this);
    }

    private static SubsetKey initSubsetKey(short category, String subsetName) {
        String subsetKeyString = toKeyString(category, subsetName);
        SubsetKey current = (SubsetKey) internMap.get(subsetKeyString);
        if (current != null) {
            return current;
        }
        return new SubsetKey(subsetKeyString, category, subsetName);
    }

    private synchronized static void intern(SubsetKey subsetKey) {
        internMap.put(subsetKey.subsetKeyString, subsetKey);
    }

    private static String toKeyString(short category, String subsetName) {
        StringBuilder buf = new StringBuilder();
        buf.append(categoryToString(category));
        buf.append("_");
        buf.append(subsetName);
        return buf.toString();
    }

    /**
     *
     * @throws IllegalArgumentException si l'argument category est incorrect
     * @throws ParseException si subsetName est incorrect
     */
    public static SubsetKey parse(short category, String subsetName) throws ParseException {
        if ((category < 1) || (category > 5)) {
            throw new IllegalArgumentException("wrong category value");
        } else {
            StringUtils.checkTechnicalName(subsetName, false);
            return initSubsetKey(category, subsetName);
        }
    }

    /**
     *
     * @throws ParseException si s est incorrect
     */
    public static SubsetKey parse(String s) throws ParseException {
        if (s == null) {
            throw new ParseException("string null", 0);
        }
        SubsetKey current = (SubsetKey) internMap.get(s);
        if (current != null) {
            return current;
        }
        int idx = s.indexOf('_');
        if (idx == -1) {
            throw new ParseException("missing '_' character", s.length() - 1);
        }
        if (idx == 0) {
            throw new ParseException("starts with '_' character", 0);
        }
        if (idx == (s.length() - 1)) {
            throw new ParseException("ends with '_' character", s.length() - 1);
        }
        short category = 0;
        try {
            category = categoryToShort(s.substring(0, idx));
        } catch (IllegalArgumentException iae) {
            throw new ParseException("wrong category string: " + s.substring(0, idx), idx - 1);
        }
        return SubsetKey.parse(category, s.substring(idx + 1));
    }

    /**
     * Équivalent de parse() mais en envoyant IllegalArgumentException plutôt
     * que ParseException. à utiliser quand on est sûr de la syntaxe et permet
     * d'éviter un try {} catch {}
     */
    public static SubsetKey build(short category, String subsetName) {
        try {
            return parse(category, subsetName);
        } catch (ParseException pe) {
            throw new IllegalArgumentException(pe.getMessage());
        }
    }

    /**
     * Équivalent de parse() mais en envoyant IllegalArgumentException plutôt
     * que ParseException. à utiliser quand on est sûr de la syntaxe et permet
     * d'éviter un try {} catch {}
     */
    public static SubsetKey build(String s) {
        try {
            return parse(s);
        } catch (ParseException pe) {
            throw new IllegalArgumentException(pe.getMessage());
        }
    }

    /**
     * Forme spéciale utilisée dans les rattachements avec fiche_ et motcle_
     *
     * @throws ParseException si s est incorrect
     */
    public static SubsetKey parseParentageSubsetKey(String s) throws ParseException {
        if (s == null) {
            throw new ParseException("string null", 0);
        }
        int idx = s.indexOf('_');
        if (idx == -1) {
            throw new ParseException("missing '_' character", s.length() - 1);
        }
        if (idx == 0) {
            throw new ParseException("starts with '_' character", 0);
        }
        if (idx == (s.length() - 1)) {
            throw new ParseException("ends with '_' character", s.length() - 1);
        }
        String prefix = s.substring(0, idx);
        short category;
        if (prefix.equals("fiche")) {
            category = SubsetKey.CATEGORY_CORPUS;
        } else if (prefix.equals("motcle")) {
            category = SubsetKey.CATEGORY_THESAURUS;
        } else {
            throw new ParseException("wrong prefix string: " + prefix, 0);
        }
        return SubsetKey.parse(category, s.substring(idx + 1));
    }

    /*
     * Retourne une chaîne non nulle et non vide.
     */
    public String getSubsetName() {
        return subsetName;
    }

    public short getCategory() {
        return category;
    }

    public String getCategoryString() {
        return categoryToString(category);
    }

    @Override
    public int hashCode() {
        return subsetKeyString.hashCode();
    }

    @Override
    public boolean equals(Object other) {
        if (other == null) {
            return false;
        }
        if (other == this) {
            return true;
        }
        if (other.getClass() != this.getClass()) {
            return false;
        }
        SubsetKey otherSubsetKey = (SubsetKey) other;
        return ((otherSubsetKey.category == this.category) && (otherSubsetKey.subsetName.equals(this.subsetName)));
    }

    public boolean isCorpusSubset() {
        if (category != CATEGORY_CORPUS) {
            return false;
        }
        return subsetName.length() != 1;
    }

    public boolean isThesaurusSubset() {
        if (category != CATEGORY_THESAURUS) {
            return false;
        }
        return subsetName.length() != 1;
    }

    public boolean isSphereSubset() {
        if (category != CATEGORY_SPHERE) {
            return false;
        }
        return subsetName.length() != 1;
    }

    public boolean isAlbumSubset() {
        if (category != CATEGORY_ALBUM) {
            return false;
        }
        return subsetName.length() != 1;
    }

    public boolean isAddendaSubset() {
        return (category == CATEGORY_ADDENDA);
    }

    /**
     * La comparaison se fait d'abord sur la catégorie et ensuite sur le nom.
     * L'ordre des catégories est le suivant : corpus &lt; thésaurus &lt; sphère
     * &lt; addenda &lt;album
     *
     */
    @Override
    public int compareTo(Object obj) {
        if (obj == this) {
            return 0;
        }
        SubsetKey other = (SubsetKey) obj;
        if (this.category < other.category) {
            return -1;
        }
        if (this.category > other.category) {
            return 1;
        }
        if (this.subsetName.length() == 1) {
            if (other.subsetName.length() == 1) {
                return this.subsetName.compareTo(other.subsetName);
            } else {
                return -1;
            }
        } else if (other.subsetName.length() == 1) {
            return 1;
        } else {
            return this.subsetName.compareTo(other.subsetName);
        }
    }

    public String getKeyString() {
        return subsetKeyString;
    }

    @Override
    public String toString() {
        return subsetKeyString;
    }

    /**
     * Retourne une expression sous forme de chaîne de la catégorie
     * correspondante.
     *
     * @throws IllegalArgumentException si la valeur de la catégorie est
     * incorrecte
     */
    public static String categoryToString(short subsetCategory) {
        switch (subsetCategory) {
            case SubsetKey.CATEGORY_CORPUS:
                return CATEGORY_CORPUS_STRING;
            case SubsetKey.CATEGORY_THESAURUS:
                return CATEGORY_THESAURUS_STRING;
            case SubsetKey.CATEGORY_SPHERE:
                return CATEGORY_SPHERE_STRING;
            case SubsetKey.CATEGORY_ADDENDA:
                return CATEGORY_ADDENDA_STRING;
            case SubsetKey.CATEGORY_ALBUM:
                return CATEGORY_ALBUM_STRING;
            default:
                throw new IllegalArgumentException("wrong subsetCategory value");
        }
    }

    /**
     * Retourne une expression sous forme de chaîne de la catégorie
     * correspondante.
     *
     * @throws IllegalArgumentException si la valeur de la catégorie est
     * incorrecte
     */
    public static short categoryToShort(String s) {
        if (s.equals("corpus")) {
            return CATEGORY_CORPUS;
        }
        if (s.equals("thesaurus")) {
            return CATEGORY_THESAURUS;
        }
        if (s.equals("sphere")) {
            return CATEGORY_SPHERE;
        }
        if ((s.equals("addenda")) || (s.equals("document"))) {
            return CATEGORY_ADDENDA;
        }
        if (s.equals("album")) {
            return CATEGORY_ALBUM;
        }
        throw new IllegalArgumentException("Wrong category string");
    }

    public static String toParentageString(SubsetKey subsetKey) {
        if (subsetKey.isCorpusSubset()) {
            return "fiche_" + subsetKey.getSubsetName();
        } else if (subsetKey.isThesaurusSubset()) {
            return "motcle_" + subsetKey.getSubsetName();
        } else {
            throw new IllegalArgumentException("wrong subsetKey type: " + subsetKey);
        }
    }

}
