/* FichothequeLib_API - Copyright (c) 2006-2025 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.corpus.metadata;

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


/**
 * <p>
 * Identifie un champ d'un corpus. Il y a quatre types de champs :
 * <ul>
 * <li>Les champs spéciaux au nombre de cinq : ID, LANG, REDACTEURS, TITRE et
 * SUBTITLE. Les quatre premiers sont obligatoires et doivent toujours se
 * retrouver dans un corpus, SUBTITLE est facultatif.
 * <li>Les chams Propriété, situés dans l'en-tête de la fiche ils contiennent au
 * maximum une valeur de fieldName FicheItem.
 * <li>Les champs Information, situés dans l'en-tête et qui contiennent zéro,
 * une ou plusieurs valeurs de fieldName FicheItem
 * <li>Les champs Section situés dans le corps de fiche, qui représentent des
 * blocs de texte
 * </ul>
 *
 * @author Vincent Calame
 */
public final class FieldKey implements Serializable {

    private static final Map<String, FieldKey> internMap = new HashMap<String, FieldKey>();
    public final static String SPECIAL_ID = "id";
    public final static String SPECIAL_TITLE = "title";
    public final static String SPECIAL_SUBTITLE = "subtitle";
    public final static String SPECIAL_LANG = "lang";
    public final static String SPECIAL_OWNERS = "owners";
    public static final short SPECIAL_CATEGORY = 0;
    public static final short PROP_CATEGORY = 1;
    public static final short INFO_CATEGORY = 2;
    public static final short SECTION_CATEGORY = 3;
    public static final String SPECIAL_CATEGORY_STRING = "special";
    public static final String PROP_CATEGORY_STRING = "prop";
    public static final String INFO_CATEGORY_STRING = "info";
    public static final String SECTION_CATEGORY_STRING = "section";
    private final String fieldKeyString;
    private final short category;
    private final String fieldName;
    public final static FieldKey ID = new FieldKey(SPECIAL_ID);
    public final static FieldKey TITLE = new FieldKey(SPECIAL_TITLE);
    public final static FieldKey SUBTITLE = new FieldKey(SPECIAL_SUBTITLE);
    public final static FieldKey LANG = new FieldKey(SPECIAL_LANG);
    public final static FieldKey OWNERS = new FieldKey(SPECIAL_OWNERS);

    private FieldKey(String fieldKeyString) {
        this.fieldKeyString = fieldKeyString;
        this.category = SPECIAL_CATEGORY;
        this.fieldName = "";
        intern(this);
    }

    private FieldKey(String fieldKeyString, String fieldName, short category) {
        this.fieldKeyString = fieldKeyString;
        this.fieldName = fieldName;
        this.category = category;
        intern(this);
    }

    private synchronized static void intern(FieldKey fieldKey) {
        internMap.put(fieldKey.fieldKeyString, fieldKey);
    }

    /**
     *
     * @throws ParseException si s est incorrect
     */
    public static FieldKey parse(String s) throws ParseException {
        switch (s) {
            case "texte":
                s = "section_texte";
                break;
            case "idcorpus":
                s = SPECIAL_ID;
                break;
            case "titre":
                s = SPECIAL_TITLE;
                break;
            case "users":
            case "redacteurs":
                s = SPECIAL_OWNERS;
                break;
            case "soustitre":
                s = SPECIAL_SUBTITLE;
                break;
        }
        FieldKey current = internMap.get(s);
        if (current != null) {
            return current;
        }
        int idx = s.indexOf('_');
        if (idx > 0) {
            String category = s.substring(0, idx);
            String fieldName = s.substring(idx + 1);
            StringUtils.checkTechnicalName(fieldName, false);
            switch (category) {
                case "prop":
                case "propriete":
                case "property":
                    return new FieldKey(PROP_CATEGORY_STRING + "_" + fieldName, fieldName, PROP_CATEGORY);
                case "info":
                case "information":
                    return new FieldKey(INFO_CATEGORY_STRING + "_" + fieldName, fieldName, INFO_CATEGORY);
                case "annexe":
                case "section":
                    return new FieldKey(SECTION_CATEGORY_STRING + "_" + fieldName, fieldName, SECTION_CATEGORY);
            }
        }
        throw new ParseException("unkown fieldkey", 0);
    }

    public static FieldKey parse(short category, String fieldName) throws ParseException {
        StringUtils.checkTechnicalName(fieldName, false);
        return new FieldKey(categoryToString(category) + "_" + fieldName, fieldName, category);
    }

    /**
     * É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 FieldKey build(String s) {
        try {
            return parse(s);
        } 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 FieldKey build(short category, String fieldName) {
        try {
            return parse(category, fieldName);
        } catch (ParseException pe) {
            throw new IllegalArgumentException(pe.getMessage());
        }
    }

    public String getKeyString() {
        return fieldKeyString;
    }

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

    public boolean isSpecial() {
        return (category == SPECIAL_CATEGORY);
    }

    public boolean isProp() {
        return (category == PROP_CATEGORY);
    }

    public boolean isInfo() {
        return (category == INFO_CATEGORY);
    }

    public boolean isSection() {
        return (category == SECTION_CATEGORY);
    }

    public short getCategory() {
        return category;
    }

    /**
     * Retourne <em>true</em> si le champ est obligatoire dans une fiche.
     */
    public boolean isMandatoryField() {
        if (category != SPECIAL_CATEGORY) {
            return false;
        }
        if (fieldKeyString.equals(SPECIAL_SUBTITLE)) {
            return false;
        }
        return true;
    }

    public String getFieldName() {
        return fieldName;
    }

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

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof FieldKey)) {
            return false;
        }
        FieldKey fk = (FieldKey) obj;
        return fk.fieldKeyString.equals(this.fieldKeyString);
    }

    /**
     * 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 category) {
        switch (category) {
            case SPECIAL_CATEGORY:
                return SPECIAL_CATEGORY_STRING;
            case PROP_CATEGORY:
                return PROP_CATEGORY_STRING;
            case INFO_CATEGORY:
                return INFO_CATEGORY_STRING;
            case SECTION_CATEGORY:
                return SECTION_CATEGORY_STRING;
            default:
                throw new IllegalArgumentException("wrong Field category 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) {
        switch (s) {
            case "prop":
            case "propriete":
            case "property":
                return PROP_CATEGORY;
            case "info":
            case "information":
                return INFO_CATEGORY;
            case "annexe":
            case "section":
                return SECTION_CATEGORY;
            case "special":
                return SPECIAL_CATEGORY;
        }
        throw new IllegalArgumentException("Wrong category string");
    }

}
