package com.fortitudetec.sonar.plugins.ruby.rubocop;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.Charsets;
import com.google.common.io.Files;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.BatchSide;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.TempFolder;
import org.sonar.api.utils.command.Command;
import org.sonar.api.utils.command.CommandExecutor;
import org.sonar.api.utils.command.StreamConsumer;
import org.sonar.api.utils.command.StringStreamConsumer;

import java.io.File;
import java.io.IOException;
import java.util.List;

@BatchSide
public class RubocopExecutor {

    private static final Logger LOG = LoggerFactory.getLogger(RubocopExecutor.class);

    private boolean mustQuoteSpaceContainingPaths;
    private TempFolder tempFolder;

    public RubocopExecutor(System2 system, TempFolder tempFolder) {
        this.mustQuoteSpaceContainingPaths = system.isOsWindows();
        this.tempFolder = tempFolder;
    }

    public String execute(RubocopExecutorConfig config, List<String> files) {
        checkNotNull(config, "RubcopExecutorConfig must not be null");
        checkNotNull(files, "List of files must not be null");

        if (config.useExistingRubocopOutput()) {
            LOG.debug("Running with existing JSON file '{}' instead of calling rubocop", config.getPathToRubocopOutput());
            return getFileContent(new File(config.getPathToRubocopOutput()));
        }

        File rubocopOutputFile = tempFolder.newFile();
        String rubocopOutputFilePath = rubocopOutputFile.getAbsolutePath();
        Command baseCommand = getBaseCommand(config, rubocopOutputFilePath);

        StringStreamConsumer stdOutConsumer = new StringStreamConsumer();
        StringStreamConsumer stdErrConsumer = new StringStreamConsumer();
        return getCommandOutput(baseCommand, stdOutConsumer, stdErrConsumer, rubocopOutputFile, config.getTimeoutMs());
    }

    private String getFileContent(File rubocopOutputFile) {
        try {
            return Files.toString(rubocopOutputFile, Charsets.UTF_8);
        } catch (IOException e) {
            LOG.error("Failed to read Rubocop output", e);
        }

        return "";
    }

    private Command getBaseCommand(RubocopExecutorConfig config, String tempPath) {
        Command command =
            Command
                .create(config.getPathToRubocop())
                .addArgument(preparePath(config.getPathToRubocop()))
                .addArgument("--format")
                .addArgument("json");

        if (StringUtils.isNotBlank(tempPath)) {
            command
                .addArgument("--out")
                .addArgument(preparePath(tempPath));
        }

        command
            .addArgument("--config")
            .addArgument(preparePath(config.getConfigFile()));

        command.setNewShell(false);

        return command;
    }

    private String preparePath(String path) {
        if (path == null) {
            return "";
        } else if (path.contains(" ") && mustQuoteSpaceContainingPaths) {
            return '"' + path + '"';
        } else {
            return path;
        }
    }

    private String getCommandOutput(Command thisCommand, StreamConsumer stdOutConsumer, StreamConsumer stdErrConsumer, File rubocopOutputFile, Integer timeoutMs) {
        createExecutor().execute(thisCommand, stdOutConsumer, stdErrConsumer, timeoutMs);
        return getFileContent(rubocopOutputFile);
    }

    protected CommandExecutor createExecutor() {
        return CommandExecutor.create();
    }
}