/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2021 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.installer.postinstall.server;

import org.jboss.as.cli.CommandContext;
import org.jboss.as.cli.CommandContextFactory;
import org.jboss.as.cli.CommandLineException;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

abstract class AbstractEmbeddedServer implements EmbeddedServer {
    protected final Path path;
    private CommandContext commandContext;
    private Set<String> availableConfigurations;
    private String currentConfiguration;

    public AbstractEmbeddedServer(Path path) {
        this.path = path;
    }

    protected abstract String getStartupCommand();

    protected abstract String getShutdownCommand();

    protected Set<String> findConfigurations() {
        // find configuration files in the installation
        final Path configPath = getConfigDirectory();
        final File[] configFiles = configPath.toFile().listFiles(getConfigFileFilter());

        // save result as set
        Set<String> result = new HashSet<>(configFiles.length);
        for (File configFile : configFiles) {
            result.add(configFile.getName());
        }
        return Collections.unmodifiableSet(result);
    }

    @Override
    public void start(String config) throws ServerOperationException {
        try {
            commandContext = CommandContextFactory.getInstance().newCommandContext();
            commandContext.setSilent(true);
            commandContext.handle(String.format(getStartupCommand(), path, config));

            currentConfiguration = config;
        } catch (CommandLineException e) {
            throw new ServerOperationException(String.format("Failed to start server at %s with %s configuration", path.toString(), config), e);
        }
    }

    @Override
    public void shutdown() throws ServerOperationException {
        try {
            commandContext.handle(getShutdownCommand());

            currentConfiguration = null;
        } catch (CommandLineException e) {
            throw new ServerOperationException(String.format("Failed to stop server at %s with %s configuration", path.toString(), currentConfiguration), e);
        }
    }

    @Override
    public void close() {
        if (commandContext != null && !commandContext.isTerminated()) {
            commandContext.terminateSession();
            commandContext = null;
            currentConfiguration = null;
        }
    }

    @Override
    public ModelNode execute(ModelNode op, String desc) throws OperationFailedException {
        try {
            final ModelNode res = commandContext.execute(op, desc);
            if (!res.get("outcome").asString().equals("success")) {
                throw new OperationFailedException(res.get("failure-description").asString());
            }
            return res.get("result");
        } catch (CommandLineException | IOException e) {
            throw new OperationFailedException(e);
        }
    }

    @Override
    public Set<String> availableConfigurations() {
        if (availableConfigurations == null) {
            availableConfigurations = findConfigurations();
        }
        return availableConfigurations;
    }

    @Override
    public String currentConfiguration() {
        return currentConfiguration;
    }

    @Override
    public ModelNode relativise(Path path, ModelNode node) {
        final EmbeddedServer.ServerPath serverPath = this.toRelativePathNode(path);
        if (serverPath.getRelativeTo().isPresent()) {
            node.get("path").set(serverPath.getPath());
            node.get("relative-to").set(serverPath.getRelativeTo().get());
        } else {
            node.get("path").set(serverPath.getPath());
        }
        return node;
    }

    @Override
    public Optional<Path> getJBossCliXml() {
        final Path jbossCliXml = this.path.resolve("bin").resolve("jboss-cli.xml");
        if (Files.exists(jbossCliXml)) {
            return Optional.of(jbossCliXml);
        }
        return Optional.empty();
    }

    public abstract List<Path> getTemporaryPaths();

    protected abstract FileFilter getConfigFileFilter();

    protected abstract Path getConfigDirectory();
}
