/* BDF - Copyright (c) 2012-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.servlets.instructions;

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.configuration.PathConfiguration;
import fr.exemole.bdfserver.api.instruction.BdfCommand;
import fr.exemole.bdfserver.api.instruction.BdfCommandParameters;
import fr.exemole.bdfserver.api.instruction.BdfCommandResult;
import fr.exemole.bdfserver.api.instruction.BdfInstruction;
import fr.exemole.bdfserver.api.instruction.BdfInstructionConstants;
import fr.exemole.bdfserver.api.interaction.Domain;
import fr.exemole.bdfserver.api.interaction.Domains;
import fr.exemole.bdfserver.api.managers.BalayageManager;
import fr.exemole.bdfserver.api.managers.ScrutariExportManager;
import fr.exemole.bdfserver.api.managers.SqlExportManager;
import fr.exemole.bdfserver.api.users.BdfUser;
import fr.exemole.bdfserver.api.users.BdfUserConstants;
import fr.exemole.bdfserver.commands.CoreBdfCommandProvider;
import fr.exemole.bdfserver.conf.ConfConstants;
import fr.exemole.bdfserver.tools.BdfServerUtils;
import fr.exemole.bdfserver.tools.configuration.PathConfigurationBuilder;
import fr.exemole.bdfserver.tools.instruction.BdfCommandParametersBuilder;
import fr.exemole.bdfserver.tools.instruction.BdfErrors;
import fr.exemole.bdfserver.tools.instruction.BdfInstructionUtils;
import fr.exemole.bdfserver.tools.runners.BalayageRunner;
import fr.exemole.bdfserver.tools.runners.ScrutariExportRunner;
import fr.exemole.bdfserver.tools.runners.SqlExportRunner;
import fr.exemole.bdfserver.tools.users.BdfUserUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.ParseException;
import net.fichotheque.exportation.balayage.BalayageDescription;
import net.fichotheque.exportation.scrutari.ScrutariExportDef;
import net.fichotheque.exportation.sql.SqlExportDef;
import net.mapeadores.util.ini.IniParser;
import net.mapeadores.util.json.JsonProducer;
import net.mapeadores.util.localisation.MessageLocalisation;
import net.mapeadores.util.logging.CommandMessage;
import net.mapeadores.util.logging.ErrorMessageException;
import net.mapeadores.util.request.RequestMap;
import net.mapeadores.util.request.RequestMapBuilder;
import net.mapeadores.util.servlets.ResponseHandler;
import net.mapeadores.util.servlets.handlers.JsonResponseHandler;
import net.mapeadores.util.servlets.handlers.SimpleResponseHandler;
import net.mapeadores.util.text.RelativePath;
import net.mapeadores.util.text.StringUtils;


/**
 *
 * @author Vincent Calame
 */
public class RunInstruction implements BdfInstruction {

    public final static String FILE_PARAMNAME = "file";
    public final static String SQLEXPORT_PARAMNAME = "sqlexport";
    public final static String SCRUTARIEXPORT_PARAMNAME = "scrutariexport";
    public final static String BALAYAGE_PARAMNAME = "balayage";
    private final static Domain DEFAULT_DOMAIN = new Domain(Domains.EDITION, "");
    private final BdfServer bdfServer;
    private final RequestMap requestMap;

    public RunInstruction(BdfServer bdfServer, RequestMap requestMap) {
        this.bdfServer = bdfServer;
        this.requestMap = requestMap;
    }

    @Override
    public short getBdfUserNeed() {
        return BdfInstructionConstants.NONE_BDFUSER;
    }

    @Override
    public ResponseHandler runInstruction(BdfUser bdfUser) {
        String runFile = requestMap.getParameter(FILE_PARAMNAME);
        if (runFile != null) {
            RunFileEngine runFileEngine = new RunFileEngine(bdfServer, requestMap);
            return runFileEngine.run(runFile);
        } else {
            ExportEngine exportEngine = new ExportEngine(bdfServer, requestMap);
            return exportEngine.run();
        }
    }


    private static class RunFileEngine {

        private final BdfServer bdfServer;
        private final RequestMap requestMap;


        private RunFileEngine(BdfServer bdfServer, RequestMap requestMap) {
            this.bdfServer = bdfServer;
            this.requestMap = requestMap;
        }

        private ResponseHandler run(String runFile) {
            JsonProducer jsonProducer;
            try {
                jsonProducer = innerRun(runFile);
            } catch (ErrorMessageException eme) {
                jsonProducer = BdfInstructionUtils.getDefaultJsonProducer(getMessageLocalisation(), null, eme.getErrorMessage());
            }
            return JsonResponseHandler.build(jsonProducer, requestMap);
        }

        private JsonProducer innerRun(String runFile) throws ErrorMessageException {
            RelativePath relativePath;
            try {
                relativePath = RelativePath.parse(runFile);
            } catch (ParseException pe) {
                throw BdfErrors.wrongParameterValue(FILE_PARAMNAME, runFile);
            }
            synchronized (ConfConstants.VAR_RUN) {
                Path filePath = bdfServer.getBdfServerDirs().resolveSubPath(ConfConstants.VAR_RUN, relativePath);
                if (!Files.exists(filePath)) {
                    throw BdfErrors.error("_ error.unknown.file", relativePath.toString());
                }
                if (Files.isDirectory(filePath)) {
                    throw BdfErrors.error("_ error.unsupported.directory", relativePath.toString());
                }
                RunFileParser parser = new RunFileParser();
                try {
                    parser.parse(filePath);
                } catch (IOException ioe) {
                    throw BdfErrors.error("_ error.exception.io_read", relativePath.toString());
                }
                if (!parser.hasCommand()) {
                    throw BdfErrors.emptyMandatoryParameter("_command");
                }
                if (!parser.keepFile) {
                    try {
                        Files.delete(filePath);
                    } catch (IOException ioe) {
                        throw BdfErrors.error("_ error.exception.io_delete", relativePath.toString());
                    }
                }
                return runCommand(parser);
            }
        }

        private JsonProducer runCommand(RunFileParser parser) throws ErrorMessageException {
            BdfCommandParameters bdfCommandParameters = BdfCommandParametersBuilder.build(parser.command, parser.domain, bdfServer, parser.runRequestMap);
            BdfCommand bdfCommand = BdfServerUtils.getExtensionBdfCommand(bdfCommandParameters);
            if (bdfCommand == null) {
                bdfCommand = CoreBdfCommandProvider.UNIQUE_INSTANCE.getBdfCommand(bdfCommandParameters);
            }
            if (bdfCommand == null) {
                throw BdfErrors.error("_ error.unknown.bdfcommand", parser.command);
            }
            BdfUser defaultBdfUser = BdfUserUtils.getFirstAdminBdfUser(bdfServer);
            defaultBdfUser.putParameter(BdfUserConstants.EDIT_ALTERNATEGLOBALID, "_run");
            BdfCommandResult bdfCommandResult;
            if (bdfCommand.needSynchronisation()) {
                synchronized (bdfServer) {
                    bdfCommandResult = bdfCommand.doCommand(defaultBdfUser);
                }
            } else {
                bdfCommandResult = bdfCommand.doCommand(defaultBdfUser);
            }
            return BdfInstructionUtils.getDefaultJsonProducer(getMessageLocalisation(), bdfCommandResult, null);
        }

        private MessageLocalisation getMessageLocalisation() {
            return BdfInstructionUtils.getMessageLocalisation(requestMap, bdfServer, null);
        }

    }


    private static class RunFileParser {

        private Domain domain = DEFAULT_DOMAIN;
        private RequestMap runRequestMap;
        private boolean keepFile = false;
        private String command;

        private RunFileParser() {

        }


        private void setDomain(String domainString) {
            this.domain = Domain.parse(domainString);
        }

        private void setKeepFile(String keepFileValue) {
            this.keepFile = StringUtils.isTrue(keepFileValue);
        }

        private void setCommand(String command) {
            this.command = command;
        }

        private boolean hasCommand() {
            return ((command != null) && (!command.isEmpty()));
        }

        private void parse(Path filePath) throws IOException {
            RequestMapBuilder requestMapBuilder = new RequestMapBuilder();
            try (InputStream is = Files.newInputStream(filePath)) {
                IniParser.parseIni(is, (key, value) -> {
                    switch (key) {
                        case "_domain":
                            setDomain(value);
                            break;
                        case "_keep":
                            setKeepFile(value);
                            break;
                        case "_command":
                        case "cmd":
                            setCommand(value);
                            requestMapBuilder.addParameter("cmd", value);
                            break;
                        default:
                            requestMapBuilder.addParameter(key, value);
                    }

                });
            }
            runRequestMap = requestMapBuilder.toRequestMap();
        }

    }


    private static class ExportEngine {

        private final BdfServer bdfServer;
        private final RequestMap requestMap;
        private final PathConfiguration pathConfiguration;

        private ExportEngine(BdfServer bdfServer, RequestMap requestMap) {
            this.bdfServer = bdfServer;
            this.requestMap = requestMap;
            this.pathConfiguration = PathConfigurationBuilder.build(bdfServer);
        }

        private ResponseHandler run() {
            String[] sqlExportArray = requestMap.getParameterValues(SQLEXPORT_PARAMNAME);
            int result = -1;
            if (sqlExportArray != null) {
                if (result == -1) {
                    result = 0;
                }
                result = result + runSqlExport(sqlExportArray);
            }
            String[] scrutariExportArray = requestMap.getParameterValues(SCRUTARIEXPORT_PARAMNAME);
            if (scrutariExportArray != null) {
                if (result == -1) {
                    result = 0;
                }
                result = result + runScrutariExport(scrutariExportArray);
            }
            String[] balayageArray = requestMap.getParameterValues(BALAYAGE_PARAMNAME);
            if (balayageArray != null) {
                if (result == -1) {
                    result = 0;
                }
                result = result + runBalayage(balayageArray);
            }
            return SimpleResponseHandler.init(String.valueOf(result));
        }

        private int runSqlExport(String[] tokens) {
            int errorCount = 0;
            SqlExportManager sqlExportManager = bdfServer.getSqlExportManager();
            for (String token : tokens) {
                SqlExportDef sqlExportDef = sqlExportManager.getSqlExportDef(token);
                if (sqlExportDef != null) {
                    try {
                        CommandMessage message = SqlExportRunner.run(sqlExportDef, bdfServer, pathConfiguration, true);
                        if (message.isErrorMessage()) {
                            errorCount++;
                        }
                    } catch (IOException ioe) {
                        errorCount++;
                    }
                } else {
                    errorCount++;
                }
            }
            return errorCount;
        }

        private int runScrutariExport(String[] tokens) {
            int result = 0;
            ScrutariExportManager scrutariExportManager = bdfServer.getScrutariExportManager();
            for (String token : tokens) {
                ScrutariExportDef scrutariExportDef = scrutariExportManager.getScrutariExportDef(token);
                if (scrutariExportDef != null) {
                    try {
                        ScrutariExportRunner.run(scrutariExportDef, bdfServer, pathConfiguration);
                    } catch (IOException ioe) {
                        result++;
                    }
                } else {
                    result++;
                }
            }
            return result;
        }

        private int runBalayage(String[] tokens) {
            int result = 0;
            BalayageManager balayageManager = bdfServer.getBalayageManager();
            for (String balayageName : tokens) {
                String mode = null;
                int idx = balayageName.indexOf('/');
                if (idx != -1) {
                    mode = balayageName.substring(idx + 1);
                    balayageName = balayageName.substring(0, idx);
                }
                BalayageDescription balayageDescription = balayageManager.getBalayage(balayageName);
                if (balayageDescription != null) {
                    BalayageRunner.Result balayageResult = BalayageRunner.run(balayageDescription, mode, bdfServer, pathConfiguration);
                    if (balayageResult.hasError()) {
                        result++;
                    }
                } else {
                    result++;
                }
            }
            return result;
        }

    }

}
