package com.termux.shared.shell.command.result;

import androidx.annotation.NonNull;
import com.termux.shared.data.DataUtils;
import com.termux.shared.logger.Logger;
import com.termux.shared.markdown.MarkdownUtils;
import com.termux.shared.errors.Errno;
import com.termux.shared.errors.Error;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ResultData implements Serializable {

    /**
     * The stdout of command.
     */
    public final StringBuilder stdout = new StringBuilder();

    /**
     * The stderr of command.
     */
    public final StringBuilder stderr = new StringBuilder();

    /**
     * The exit code of command.
     */
    public Integer exitCode;

    /**
     * The internal errors list of command.
     */
    public List<Error> errorsList = new ArrayList<>();

    public ResultData() {
    }

    public void clearStdout() {
        stdout.setLength(0);
    }

    public StringBuilder prependStdout(String message) {
        return stdout.insert(0, message);
    }

    public StringBuilder prependStdoutLn(String message) {
        return stdout.insert(0, message + "\n");
    }

    public StringBuilder appendStdout(String message) {
        return stdout.append(message);
    }

    public StringBuilder appendStdoutLn(String message) {
        return stdout.append(message).append("\n");
    }

    public void clearStderr() {
        stderr.setLength(0);
    }

    public StringBuilder prependStderr(String message) {
        return stderr.insert(0, message);
    }

    public StringBuilder prependStderrLn(String message) {
        return stderr.insert(0, message + "\n");
    }

    public StringBuilder appendStderr(String message) {
        return stderr.append(message);
    }

    public StringBuilder appendStderrLn(String message) {
        return stderr.append(message).append("\n");
    }

    public synchronized boolean setStateFailed(@NonNull Error error) {
        return setStateFailed(error.getType(), error.getCode(), error.getMessage(), null);
    }

    public synchronized boolean setStateFailed(@NonNull Error error, Throwable throwable) {
        return setStateFailed(error.getType(), error.getCode(), error.getMessage(), Collections.singletonList(throwable));
    }

    public synchronized boolean setStateFailed(@NonNull Error error, List<Throwable> throwablesList) {
        return setStateFailed(error.getType(), error.getCode(), error.getMessage(), throwablesList);
    }

    public synchronized boolean setStateFailed(int code, String message) {
        return setStateFailed(null, code, message, null);
    }

    public synchronized boolean setStateFailed(int code, String message, Throwable throwable) {
        return setStateFailed(null, code, message, Collections.singletonList(throwable));
    }

    public synchronized boolean setStateFailed(int code, String message, List<Throwable> throwablesList) {
        return setStateFailed(null, code, message, throwablesList);
    }

    public synchronized boolean setStateFailed(String type, int code, String message, List<Throwable> throwablesList) {
        if (errorsList == null)
            errorsList = new ArrayList<>();
        Error error = new Error();
        errorsList.add(error);
        return error.setStateFailed(type, code, message, throwablesList);
    }

    public boolean isStateFailed() {
        if (errorsList != null) {
            for (Error error : errorsList) if (error.isStateFailed())
                return true;
        }
        return false;
    }

    public int getErrCode() {
        if (errorsList != null && errorsList.size() > 0)
            return errorsList.get(errorsList.size() - 1).getCode();
        else
            return Errno.ERRNO_SUCCESS.getCode();
    }

    @NonNull
    @Override
    public String toString() {
        return getResultDataLogString(this, true);
    }

    /**
     * Get a log friendly {@link String} for {@link ResultData} parameters.
     *
     * @param resultData The {@link ResultData} to convert.
     * @param logStdoutAndStderr Set to {@code true} if {@link #stdout} and {@link #stderr} should be logged.
     * @return Returns the log friendly {@link String}.
     */
    public static String getResultDataLogString(final ResultData resultData, boolean logStdoutAndStderr) {
        if (resultData == null)
            return "null";
        StringBuilder logString = new StringBuilder();
        if (logStdoutAndStderr) {
            logString.append("\n").append(resultData.getStdoutLogString());
            logString.append("\n").append(resultData.getStderrLogString());
        }
        logString.append("\n").append(resultData.getExitCodeLogString());
        logString.append("\n\n").append(getErrorsListLogString(resultData));
        return logString.toString();
    }

    public String getStdoutLogString() {
        if (stdout.toString().isEmpty())
            return Logger.getSingleLineLogStringEntry("Stdout", null, "-");
        else
            return Logger.getMultiLineLogStringEntry("Stdout", DataUtils.getTruncatedCommandOutput(stdout.toString(), Logger.LOGGER_ENTRY_MAX_SAFE_PAYLOAD / 5, false, false, true), "-");
    }

    public String getStderrLogString() {
        if (stderr.toString().isEmpty())
            return Logger.getSingleLineLogStringEntry("Stderr", null, "-");
        else
            return Logger.getMultiLineLogStringEntry("Stderr", DataUtils.getTruncatedCommandOutput(stderr.toString(), Logger.LOGGER_ENTRY_MAX_SAFE_PAYLOAD / 5, false, false, true), "-");
    }

    public String getExitCodeLogString() {
        return Logger.getSingleLineLogStringEntry("Exit Code", exitCode, "-");
    }

    public static String getErrorsListLogString(final ResultData resultData) {
        if (resultData == null)
            return "null";
        StringBuilder logString = new StringBuilder();
        if (resultData.errorsList != null) {
            for (Error error : resultData.errorsList) {
                if (error.isStateFailed()) {
                    if (!logString.toString().isEmpty())
                        logString.append("\n");
                    logString.append(Error.getErrorLogString(error));
                }
            }
        }
        return logString.toString();
    }

    /**
     * Get a markdown {@link String} for {@link ResultData}.
     *
     * @param resultData The {@link ResultData} to convert.
     * @return Returns the markdown {@link String}.
     */
    public static String getResultDataMarkdownString(final ResultData resultData) {
        if (resultData == null)
            return "null";
        StringBuilder markdownString = new StringBuilder();
        if (resultData.stdout.toString().isEmpty())
            markdownString.append(MarkdownUtils.getSingleLineMarkdownStringEntry("Stdout", null, "-"));
        else
            markdownString.append(MarkdownUtils.getMultiLineMarkdownStringEntry("Stdout", resultData.stdout.toString(), "-"));
        if (resultData.stderr.toString().isEmpty())
            markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Stderr", null, "-"));
        else
            markdownString.append("\n").append(MarkdownUtils.getMultiLineMarkdownStringEntry("Stderr", resultData.stderr.toString(), "-"));
        markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Exit Code", resultData.exitCode, "-"));
        markdownString.append("\n\n").append(getErrorsListMarkdownString(resultData));
        return markdownString.toString();
    }

    public static String getErrorsListMarkdownString(final ResultData resultData) {
        if (resultData == null)
            return "null";
        StringBuilder markdownString = new StringBuilder();
        if (resultData.errorsList != null) {
            for (Error error : resultData.errorsList) {
                if (error.isStateFailed()) {
                    if (!markdownString.toString().isEmpty())
                        markdownString.append("\n");
                    markdownString.append(Error.getErrorMarkdownString(error));
                }
            }
        }
        return markdownString.toString();
    }

    public static String getErrorsListMinimalString(final ResultData resultData) {
        if (resultData == null)
            return "null";
        StringBuilder minimalString = new StringBuilder();
        if (resultData.errorsList != null) {
            for (Error error : resultData.errorsList) {
                if (error.isStateFailed()) {
                    if (!minimalString.toString().isEmpty())
                        minimalString.append("\n");
                    minimalString.append(Error.getMinimalErrorString(error));
                }
            }
        }
        return minimalString.toString();
    }
}
