/* FichothequeLib_Tools - Copyright (c) 2009-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.tools.parsers.junction;

import java.io.File;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.fichotheque.FichothequeEditor;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.fichotheque.addenda.Addenda;
import net.fichotheque.addenda.Document;
import net.fichotheque.include.IncludeKey;
import net.fichotheque.tools.junction.JunctionChangeEngine;
import net.fichotheque.tools.junction.TieBuffer;
import net.fichotheque.utils.AddendaUtils;
import net.mapeadores.util.request.FileValue;
import net.mapeadores.util.text.FileName;
import net.mapeadores.util.text.StringUtils;
import net.fichotheque.junction.JunctionEditor;


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

    private final static FileName DEFAULT_FILENAME = FileName.build("file.octetstream");
    private final SubsetItem mainSubsetItem;
    private final Map<String, DocumentMultipartToken> multipartTokenMap = new LinkedHashMap<String, DocumentMultipartToken>();
    private final File tmpDirectory;
    private final List<DocumentRemoveToken> documentRemoveTokenList = new ArrayList<DocumentRemoveToken>();
    private final List<DocumentCreateToken> documentCreateTokenList = new ArrayList<DocumentCreateToken>();
    private final List<DocumentChangeToken> documentChangeTokenList = new ArrayList<DocumentChangeToken>();
    private final List<DocumentAddToken> documentAddTokenList = new ArrayList<DocumentAddToken>();
    private final Set<Document> removeSet = new HashSet<Document>();
    private final Map<IncludeKey, DocumentOrderManager> orderManagerMap = new HashMap<IncludeKey, DocumentOrderManager>();

    public DocumentTokenParser(SubsetItem mainSubsetItem, File tmpDirectory) {
        this.mainSubsetItem = mainSubsetItem;
        this.tmpDirectory = tmpDirectory;
    }

    public void addFromMultipart(TokenKey tokenKey, FileValue[] fileValues) {
        IncludeKey includeKey = tokenKey.getIncludeKey();
        SubsetKey addendaKey = includeKey.getSubsetKey();
        if (!addendaKey.isAddendaSubset()) {
            throw new IllegalArgumentException("not an addenda Parameter: " + tokenKey.getParamName());
        }
        Addenda addenda = (Addenda) mainSubsetItem.getFichotheque().getSubset(addendaKey);
        if (addenda == null) {
            return;
        }
        String mode = includeKey.getMode();
        int weight = includeKey.getWeightFilter();
        for (FileValue fileValue : fileValues) {
            if (fileValue.length() < 1) {
                fileValue.free();
                continue;
            }
            FileName fileName;
            try {
                fileName = FileName.parse(fileValue.getName(), "octetstream");
            } catch (ParseException pe) {
                fileName = DEFAULT_FILENAME;
            }
            String extension = fileName.getExtension().toLowerCase();
            if (!AddendaUtils.testExtension(extension)) {
                extension = "octetstream";
            }
            String baseName = fileName.getBasename();
            DocumentMultipartToken documentMultipartToken = multipartTokenMap.get(baseName);
            if (documentMultipartToken == null) {
                documentMultipartToken = new DocumentMultipartToken(baseName, addenda, mode, weight);
                multipartTokenMap.put(baseName, documentMultipartToken);
            }
            documentMultipartToken.add(extension, fileValue);
        }
    }

    public void parse(TokenKey tokenKey, String[] parameterValues) {
        IncludeKey includeKey = tokenKey.getIncludeKey();
        SubsetKey addendaKey = includeKey.getSubsetKey();
        if (!addendaKey.isAddendaSubset()) {
            throw new IllegalArgumentException("not an addenda Parameter: " + tokenKey.getParamName());
        }
        Addenda addenda = (Addenda) mainSubsetItem.getFichotheque().getSubset(addendaKey);
        if (addenda == null) {
            return;
        }
        String[] values = tokenize(parameterValues);
        switch (tokenKey.getSubname()) {
            case "create":
                for (String value : values) {
                    DocumentCreateToken documentCreateToken = DocumentCreateToken.parse(value, addenda, includeKey);
                    if (documentCreateToken != null) {
                        documentCreateTokenList.add(documentCreateToken);
                    }
                }
                break;
            case "remove":
                for (String value : values) {
                    DocumentRemoveToken documentRemoveToken = DocumentRemoveToken.parse(value, addenda, includeKey);
                    if (documentRemoveToken != null) {
                        documentRemoveTokenList.add(documentRemoveToken);
                        removeSet.add(documentRemoveToken.getDocument());
                    }
                }
                break;
            case "change":
                for (String value : values) {
                    DocumentChangeToken documentChangeToken = DocumentChangeToken.parse(value, addenda);
                    if (documentChangeToken != null) {
                        documentChangeTokenList.add(documentChangeToken);
                    }
                }
                break;
            case "add":
                for (String value : values) {
                    DocumentAddToken documentAddToken = DocumentAddToken.parse(value, addenda, includeKey);
                    if (documentAddToken != null) {
                        documentAddTokenList.add(documentAddToken);
                    }
                }
                break;
            case "order":
                for (String value : values) {
                    Object orderObject = parseOrderToken(value);
                    if (orderObject != null) {
                        DocumentOrderManager orderManager = getOrderManager(addenda, includeKey);
                        orderManager.add(orderObject);
                    }
                }
                break;
        }
    }

    public void save(FichothequeEditor fichothequeEditor) {
        JunctionEditor junctionEditor = fichothequeEditor.getJunctionEditor();
        JunctionChangeEngine junctionChangeEngine = JunctionChangeEngine.appendOrWeightReplaceEngine(mainSubsetItem);
        for (DocumentRemoveToken documentRemoveToken : documentRemoveTokenList) {
            junctionChangeEngine.removeTie(documentRemoveToken.getDocument(), documentRemoveToken.getMode());
        }
        for (DocumentMultipartToken documentMultipartToken : multipartTokenMap.values()) {
            TieBuffer tieBuffer = documentMultipartToken.save(fichothequeEditor);
            if (tieBuffer != null) {
                junctionChangeEngine.addTie(tieBuffer);
            }
        }
        for (DocumentChangeToken documentChangeToken : documentChangeTokenList) {
            documentChangeToken.save(fichothequeEditor, tmpDirectory);
        }
        for (DocumentCreateToken documentCreateToken : documentCreateTokenList) {
            TieBuffer tieBuffer = documentCreateToken.save(fichothequeEditor, tmpDirectory);
            if (tieBuffer != null) {
                DocumentOrderManager orderManager = orderManagerMap.get(documentCreateToken.getIncludeKey());
                if (orderManager != null) {
                    orderManager.replace(documentCreateToken.getNewBasename(), tieBuffer.getSubsetItem().getId());
                }
                junctionChangeEngine.addTie(tieBuffer);
            }
        }
        for (DocumentAddToken documentAddToken : documentAddTokenList) {
            junctionChangeEngine.addTie(documentAddToken.toTieBuffer());
        }
        junctionEditor.updateJunctions(mainSubsetItem, junctionChangeEngine.toJunctionChanges());
        for (Document document : removeSet) {
            if (fichothequeEditor.getFichotheque().isRemoveable(document)) {
                fichothequeEditor.getAddendaEditor(document.getSubsetKey()).removeDocument(document);
            }
        }
        for (DocumentOrderManager orderManager : orderManagerMap.values()) {
            orderManager.save(junctionEditor);
        }
    }

    private static String[] tokenize(String[] values) {
        Set<String> tokenSet = new LinkedHashSet<String>();
        for (String value : values) {
            String[] tokens = StringUtils.getTokens(value, ';', StringUtils.EMPTY_EXCLUDE);
            for (String token : tokens) {
                tokenSet.add(token);
            }
        }
        return tokenSet.toArray(new String[tokenSet.size()]);
    }

    private DocumentOrderManager getOrderManager(Addenda addenda, IncludeKey includeKey) {
        DocumentOrderManager orderManager = orderManagerMap.get(includeKey);
        if (orderManager == null) {
            orderManager = new DocumentOrderManager(mainSubsetItem, addenda, includeKey.getMode(), includeKey.getWeightFilter());
            orderManagerMap.put(includeKey, orderManager);
        }
        return orderManager;
    }

    private Object parseOrderToken(String token) {
        int idx = token.indexOf('=');
        if (idx < 0) {
            return null;
        }
        String type = token.substring(0, idx).trim();
        String value = token.substring(idx + 1).trim();
        switch (type) {
            case "id": {
                try {
                    int id = Integer.parseInt(value);
                    return id;
                } catch (NumberFormatException nfe) {
                    return null;
                }
            }
            case "bn":
                return value;
            default:
                return null;
        }
    }

}
