/* BdfServer_Commands - Copyright (c) 2011-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 fr.exemole.bdfserver.commands.corpus;

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.EditSession;
import fr.exemole.bdfserver.api.interaction.Domains;
import fr.exemole.bdfserver.api.namespaces.CommandSpace;
import fr.exemole.bdfserver.tools.instruction.AbstractBdfCommand;
import fr.exemole.bdfserver.tools.instruction.BdfErrors;
import java.text.ParseException;
import java.util.List;
import net.fichotheque.ExistingIdException;
import net.fichotheque.Subset;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.corpus.NoMasterIdException;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.tools.duplication.DuplicationEngine;
import net.fichotheque.tools.duplication.DuplicationFilter;
import net.fichotheque.tools.duplication.DuplicationFilterParser;
import net.fichotheque.tools.duplication.DuplicationParameters;
import net.fichotheque.tools.duplication.SubsetMatch;
import net.fichotheque.utils.CorpusUtils;
import net.mapeadores.util.logging.CommandMessageBuilder;
import net.mapeadores.util.logging.ErrorMessageException;
import net.mapeadores.util.request.RequestMap;
import net.mapeadores.util.text.StringUtils;


/**
 *
 * @author Vincent Calame
 */
public class DuplicationCommand extends AbstractBdfCommand {

    public final static String COMMANDNAME = "Duplication";
    public final static String COMMANDKEY = "_ CRP-23";
    public final static String MODE_PARAMNAME = "mode";
    public final static String SELECTION_MODE_PARAMVALUE = "selection";
    public final static String LIST_MODE_PARAMVALUE = "list";
    public final static String IDTOKENS_PARAMNAME = "idtokens";
    public final static String LINKTOORIGIN_PARAMNAME = "linktoorigin";
    public final static String MODE_LINKTOORIGIN_PARAMNAME = "linktoorigin_mode";
    public final static String WEIGHT_LINKTOORIGIN_PARAMNAME = "linktoorigin_weight";
    public final static String DESTINATION_PARAMNAME = "destination";
    public final static String WITHDESTINATIONIDS_PARAMNAME = "withdestinationids";
    public final static String FILTER_PARAMNAME = "filter";
    public final static String FILTER_ALL_PARAMVALUE = "all";
    public final static String FILTER_FICHEONLY_PARAMVALUE = "ficheonly";
    public final static String FILTER_EXCLUDE_PARAMVALUE = "exclude";
    public final static String EXCLUDE_PARAMNAME = "exclude";
    public final static String STORE_PARAMNAME = "store";
    public final static String FIELDMATCHING_PARAMNAME = "fieldmatching";
    private DuplicationParameters duplicationParameters;
    private boolean excludeStore;
    private String[] excludeTokens;

    public DuplicationCommand(BdfServer bdfServer, RequestMap requestMap) {
        super(bdfServer, requestMap);
    }

    @Override
    public boolean needSynchronisation() {
        return true;
    }

    @Override
    protected void doCommand() {
        SubsetMatch subsetMatch;
        try (EditSession session = startEditSession(Domains.CORPUS, COMMANDNAME)) {
            subsetMatch = DuplicationEngine.run(duplicationParameters, session.getFichothequeEditor());
            if (excludeStore) {
                session.getFichothequeEditor().putAttribute(duplicationParameters.getOriginCorpus().getCorpusMetadata(), CommandSpace.DUPLICATION_EXCLUDE_KEY, excludeTokens);
            }
        }
        putResultObject(SUBSETMATCH_OBJ, subsetMatch);
        setDone("_ done.corpus.duplication", duplicationParameters.getOriginCorpus().getSubsetKey(), duplicationParameters.getDestinationCorpus().getSubsetKey(), subsetMatch.size());
    }

    @Override
    protected void checkParameters() throws ErrorMessageException {
        checkFichothequeAdmin();
        Corpus corpus = requestHandler.getMandatoryCorpus();
        String mode = getMandatory(MODE_PARAMNAME);
        Corpus destinationCorpus = (Corpus) requestHandler.getMandatorySubset(SubsetKey.CATEGORY_CORPUS, DESTINATION_PARAMNAME);
        boolean withDestinationIds = false;
        if (mode.equals(LIST_MODE_PARAMVALUE)) {
            withDestinationIds = requestHandler.isTrue(WITHDESTINATIONIDS_PARAMNAME);
        }
        if ((destinationCorpus.getMasterSubset() != null) && (!withDestinationIds)) {
            throw BdfErrors.error("_ error.unsupported.destinationidsrequired");
        }
        this.duplicationParameters = new DuplicationParameters(corpus, destinationCorpus);
        if (mode.equals(SELECTION_MODE_PARAMVALUE)) {
            List<FicheMeta> ficheMetaList = CorpusUtils.getFicheMetaListByCorpus(bdfUser.getSelectedFiches(), corpus);
            if (ficheMetaList.isEmpty()) {
                throw BdfErrors.error("_ error.empty.originselection");
            }
            for (FicheMeta ficheMeta : ficheMetaList) {
                duplicationParameters.addFicheMeta(ficheMeta);
            }
        } else if (mode.equals(LIST_MODE_PARAMVALUE)) {
            parseListMode(withDestinationIds);
        }
        parseFilter();
        parseFieldMatching();
        parseLinkToOrigin();
    }

    private void parseListMode(boolean withDestinationIds) throws ErrorMessageException {
        String idTokens = getMandatory(IDTOKENS_PARAMNAME);
        CommandMessageBuilder commandMessageBuilder = CommandMessageBuilder.init();
        if (withDestinationIds) {
            String[] lines = StringUtils.getLineTokens(idTokens, StringUtils.EMPTY_EXCLUDE);
            if (lines.length == 0) {
                throw BdfErrors.error("_ error.empty.idtokens");
            } else {
                parseMatchLines(duplicationParameters, lines, commandMessageBuilder);
            }
        } else {
            String[] tokens = StringUtils.getTechnicalTokens(idTokens, true);
            if (tokens.length == 0) {
                throw BdfErrors.error("_ error.empty.idtokens");
            } else {
                parseSimpleIdTokens(duplicationParameters, tokens, commandMessageBuilder);
            }
        }
        if (commandMessageBuilder.hasMultiError()) {
            throw BdfErrors.error(commandMessageBuilder, "_ error.list.idtokens");
        }
    }

    private void parseSimpleIdTokens(DuplicationParameters duplicationParameters, String[] tokens, CommandMessageBuilder commandMessageBuilder) {
        for (String token : tokens) {
            try {
                int id = Integer.parseInt(token);
                if (id > 0) {
                    FicheMeta origin = duplicationParameters.getOriginCorpus().getFicheMetaById(id);
                    if (origin != null) {
                        duplicationParameters.addFicheMeta(origin);
                    } else {
                        commandMessageBuilder.addMultiError("_ error.unknown.id", token);
                    }
                } else {
                    commandMessageBuilder.addMultiError("_ error.wrong.id", token);
                }
            } catch (NumberFormatException nfe) {
                commandMessageBuilder.addMultiError("_ error.wrong.id", token);
            }
        }
    }

    private void parseMatchLines(DuplicationParameters duplicationParameters, String[] lines, CommandMessageBuilder commandMessageBuilder) {
        Subset masterSubset = duplicationParameters.getDestinationCorpus().getMasterSubset();
        for (String line : lines) {
            String[] tokens = StringUtils.getTechnicalTokens(line, true);
            if (tokens.length < 2) {
                commandMessageBuilder.addMultiError("_ error.wrong.id", line);
            } else {
                FicheMeta origin = null;
                String firstToken = tokens[0];
                try {
                    int id = Integer.parseInt(firstToken);
                    if (id > 0) {
                        origin = duplicationParameters.getOriginCorpus().getFicheMetaById(id);
                        if (origin == null) {
                            commandMessageBuilder.addMultiError("_ error.unknown.id", firstToken);
                        }
                    } else {
                        commandMessageBuilder.addMultiError("_ error.wrong.id", firstToken);
                    }
                } catch (NumberFormatException nfe) {
                    commandMessageBuilder.addMultiError("_ error.wrong.id", firstToken);
                }
                if (origin != null) {
                    String secondToken = tokens[1];
                    try {
                        int destinationId = Integer.parseInt(secondToken);
                        if (destinationId < 1) {
                            if (masterSubset != null) {
                                commandMessageBuilder.addMultiError("_ error.empty.mandatorydestinationid", secondToken);
                            } else {
                                duplicationParameters.addFicheMeta(origin);
                            }
                        } else {
                            try {
                                duplicationParameters.addFicheMeta(origin, destinationId);
                            } catch (ExistingIdException eie) {
                                commandMessageBuilder.addMultiError("_ error.existing.id", secondToken);
                            } catch (NoMasterIdException nmie) {
                                commandMessageBuilder.addMultiError("error.unknown.mastersubsetitem", secondToken, masterSubset.getSubsetKeyString());
                            }
                        }
                    } catch (NumberFormatException nfe) {
                        commandMessageBuilder.addMultiError("_ error.wrong.destinationid", secondToken);
                    }
                }
            }
        }
    }

    private void parseFieldMatching() throws ErrorMessageException {
        String matching = requestHandler.getTrimedParameter(FIELDMATCHING_PARAMNAME);
        if (matching.isEmpty()) {
            return;
        }
        CommandMessageBuilder commandMessageBuilder = CommandMessageBuilder.init();
        String[] lines = StringUtils.getLineTokens(matching, StringUtils.EMPTY_EXCLUDE);
        for (String line : lines) {
            int idx = line.indexOf('=');
            if (idx == -1) {
                commandMessageBuilder.addMultiError("_ error.empty.separator", "=", line);
            }
            String originPart = line.substring(0, idx).trim();
            String destinationPart = line.substring(idx + 1).trim();
            FieldKey originKey = null;
            FieldKey destinationKey = null;
            try {
                originKey = FieldKey.parse(originPart);
            } catch (ParseException pe) {
                commandMessageBuilder.addMultiError("_ error.wrong.fieldkey", originPart);
            }
            try {
                destinationKey = FieldKey.parse(destinationPart);
            } catch (ParseException pe) {
                commandMessageBuilder.addMultiError("_ error.wrong.fieldkey", destinationPart);
            }
            if ((originKey != null) && (destinationKey != null)) {
                if (originKey.getCategory() != destinationKey.getCategory()) {
                    commandMessageBuilder.addMultiError("_ error.unsupported.differentcategory", line);
                } else {
                    duplicationParameters.addFieldMatching(originKey, destinationKey);
                }
            }
        }
        if (commandMessageBuilder.hasMultiError()) {
            throw BdfErrors.error(commandMessageBuilder, "_ error.list.fieldmatching");
        }
    }

    private void parseLinkToOrigin() throws ErrorMessageException {
        boolean linkToOrigin = requestHandler.isTrue(LINKTOORIGIN_PARAMNAME);
        if (linkToOrigin) {
            String linkToOriginMode = getMandatory(MODE_LINKTOORIGIN_PARAMNAME).trim();
            String linkToOriginWeight = getMandatory(WEIGHT_LINKTOORIGIN_PARAMNAME).trim();
            int weight = 1;
            if (linkToOriginWeight.length() > 0) {
                boolean done = false;
                try {
                    weight = Integer.parseInt(linkToOriginWeight);
                    if (weight > 0) {
                        done = true;
                    }
                } catch (NumberFormatException nfe) {
                }
                if (!done) {
                    throw BdfErrors.error("_ error.wrong.weight", linkToOriginWeight);
                }
            }
            try {
                duplicationParameters.setLinkWithOrigin(linkToOriginMode, weight);
            } catch (ParseException pe) {
                throw BdfErrors.error("_ error.wrong.mode", linkToOriginWeight);
            }
        }
    }

    private void parseFilter() throws ErrorMessageException {
        String filter = getMandatory(FILTER_PARAMNAME);
        switch (filter) {
            case FILTER_FICHEONLY_PARAMVALUE:
                duplicationParameters.setFicheFilter(DuplicationParameters.ONLY_FICHE_FILTER, null);
                break;
            case FILTER_EXCLUDE_PARAMVALUE:
                duplicationParameters.setFicheFilter(DuplicationParameters.CUSTOM_FILTER, parseExcludeFilter());
                break;
            default:
                duplicationParameters.setFicheFilter(DuplicationParameters.ALL_FILTER, null);
        }
    }

    private DuplicationFilter parseExcludeFilter() throws ErrorMessageException {
        this.excludeStore = requestHandler.isTrue(STORE_PARAMNAME);
        this.excludeTokens = StringUtils.getTechnicalTokens(getMandatory(EXCLUDE_PARAMNAME), true);
        return DuplicationFilterParser.parseExclude(excludeTokens);
    }


}
