/*
 * Copyright 2004-2023 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (https://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.message;

import java.text.MessageFormat;
import java.util.ArrayList;

import org.h2.expression.ParameterInterface;
import org.h2.util.StringUtils;

/**
 * This class represents a trace module.
 */
public final class Trace {

    /**
     * The trace module id for commands.
     */
    public static final int COMMAND = 0;

    /**
     * The trace module id for constraints.
     */
    public static final int CONSTRAINT = 1;

    /**
     * The trace module id for databases.
     */
    public static final int DATABASE = 2;

    /**
     * The trace module id for functions.
     */
    public static final int FUNCTION = 3;

    /**
     * The trace module id for file locks.
     */
    public static final int FILE_LOCK = 4;

    /**
     * The trace module id for indexes.
     */
    public static final int INDEX = 5;

    /**
     * The trace module id for the JDBC API.
     */
    public static final int JDBC = 6;

    /**
     * The trace module id for locks.
     */
    public static final int LOCK = 7;

    /**
     * The trace module id for schemas.
     */
    public static final int SCHEMA = 8;

    /**
     * The trace module id for sequences.
     */
    public static final int SEQUENCE = 9;

    /**
     * The trace module id for settings.
     */
    public static final int SETTING = 10;

    /**
     * The trace module id for tables.
     */
    public static final int TABLE = 11;

    /**
     * The trace module id for triggers.
     */
    public static final int TRIGGER = 12;

    /**
     * The trace module id for users.
     */
    public static final int USER = 13;

    /**
     * The trace module id for the JDBCX API
     */
    public static final int JDBCX = 14;

    /**
     * Module names by their ids as array indexes.
     */
    static final String[] MODULE_NAMES = {
        "command",
        "constraint",
        "database",
        "function",
        "fileLock",
        "index",
        "jdbc",
        "lock",
        "schema",
        "sequence",
        "setting",
        "table",
        "trigger",
        "user",
        "JDBCX"
    };

    private final TraceWriter traceWriter;
    private final String module;
    private final String lineSeparator;
    private int traceLevel = TraceSystem.PARENT;

    Trace(TraceWriter traceWriter, int moduleId) {
        this(traceWriter, MODULE_NAMES[moduleId]);
    }

    Trace(TraceWriter traceWriter, String module) {
        this.traceWriter = traceWriter;
        this.module = module;
        this.lineSeparator = System.lineSeparator();
    }

    /**
     * Set the trace level of this component. This setting overrides the parent
     * trace level.
     *
     * @param level the new level
     */
    public void setLevel(int level) {
        this.traceLevel = level;
    }

    private boolean isEnabled(int level) {
        if (this.traceLevel == TraceSystem.PARENT) {
            return traceWriter.isEnabled(level);
        }
        return level <= this.traceLevel;
    }

    /**
     * Check if the trace level is equal or higher than INFO.
     *
     * @return true if it is
     */
    public boolean isInfoEnabled() {
        return isEnabled(TraceSystem.INFO);
    }

    /**
     * Check if the trace level is equal or higher than DEBUG.
     *
     * @return true if it is
     */
    public boolean isDebugEnabled() {
        return isEnabled(TraceSystem.DEBUG);
    }

    /**
     * Write a message with trace level ERROR to the trace system.
     *
     * @param t the exception
     * @param s the message
     */
    public void error(Throwable t, String s) {
        if (isEnabled(TraceSystem.ERROR)) {
            traceWriter.write(TraceSystem.ERROR, module, s, t);
        }
    }

    /**
     * Write a message with trace level ERROR to the trace system.
     *
     * @param t the exception
     * @param s the message
     * @param params the parameters
     */
    public void error(Throwable t, String s, Object... params) {
        if (isEnabled(TraceSystem.ERROR)) {
            s = MessageFormat.format(s, params);
            traceWriter.write(TraceSystem.ERROR, module, s, t);
        }
    }

    /**
     * Write a message with trace level INFO to the trace system.
     *
     * @param s the message
     */
    public void info(String s) {
        if (isEnabled(TraceSystem.INFO)) {
            traceWriter.write(TraceSystem.INFO, module, s, null);
        }
    }

    /**
     * Write a message with trace level INFO to the trace system.
     *
     * @param s the message
     * @param params the parameters
     */
    public void info(String s, Object... params) {
        if (isEnabled(TraceSystem.INFO)) {
            s = MessageFormat.format(s, params);
            traceWriter.write(TraceSystem.INFO, module, s, null);
        }
    }

    /**
     * Write a message with trace level INFO to the trace system.
     *
     * @param t the exception
     * @param s the message
     */
    void info(Throwable t, String s) {
        if (isEnabled(TraceSystem.INFO)) {
            traceWriter.write(TraceSystem.INFO, module, s, t);
        }
    }

    /**
     * Format the parameter list.
     *
     * @param parameters the parameter list
     * @return the formatted text
     */
    public static String formatParams(ArrayList<? extends ParameterInterface> parameters) {
        if (parameters.isEmpty()) {
            return "";
        }
        StringBuilder builder = new StringBuilder();
        int i = 0;
        for (ParameterInterface p : parameters) {
            if (p.isValueSet()) {
                builder.append(i == 0 ? " {" : ", ") //
                        .append(++i).append(": ") //
                        .append(p.getParamValue().getTraceSQL());
            }
        }
        if (i != 0) {
            builder.append('}');
        }
        return builder.toString();
    }

    /**
     * Write a SQL statement with trace level INFO to the trace system.
     *
     * @param sql the SQL statement
     * @param params the parameters used, in the for {1:...}
     * @param count the update count
     * @param time the time it took to run the statement in ms
     */
    public void infoSQL(String sql, String params, long count, long time) {
        if (!isEnabled(TraceSystem.INFO)) {
            return;
        }
        StringBuilder buff = new StringBuilder(sql.length() + params.length() + 20);
        buff.append(lineSeparator).append("/*SQL");
        boolean space = false;
        if (params.length() > 0) {
            // This looks like a bug, but it is intentional:
            // If there are no parameters, the SQL statement is
            // the rest of the line. If there are parameters, they
            // are appended at the end of the line. Knowing the size
            // of the statement simplifies separating the SQL statement
            // from the parameters (no need to parse).
            space = true;
            buff.append(" l:").append(sql.length());
        }
        if (count > 0) {
            space = true;
            buff.append(" #:").append(count);
        }
        if (time > 0) {
            space = true;
            buff.append(" t:").append(time);
        }
        if (!space) {
            buff.append(' ');
        }
        buff.append("*/");
        StringUtils.javaEncode(sql, buff, false);
        StringUtils.javaEncode(params, buff, false);
        buff.append(';');
        sql = buff.toString();
        traceWriter.write(TraceSystem.INFO, module, sql, null);
    }

    /**
     * Write a message with trace level DEBUG to the trace system.
     *
     * @param s the message
     * @param params the parameters
     */
    public void debug(String s, Object... params) {
        if (isEnabled(TraceSystem.DEBUG)) {
            s = MessageFormat.format(s, params);
            traceWriter.write(TraceSystem.DEBUG, module, s, null);
        }
    }

    /**
     * Write a message with trace level DEBUG to the trace system.
     *
     * @param s the message
     */
    public void debug(String s) {
        if (isEnabled(TraceSystem.DEBUG)) {
            traceWriter.write(TraceSystem.DEBUG, module, s, null);
        }
    }

    /**
     * Write a message with trace level DEBUG to the trace system.
     * @param t the exception
     * @param s the message
     */
    public void debug(Throwable t, String s) {
        if (isEnabled(TraceSystem.DEBUG)) {
            traceWriter.write(TraceSystem.DEBUG, module, s, t);
        }
    }


    /**
     * Write Java source code with trace level INFO to the trace system.
     *
     * @param java the source code
     */
    public void infoCode(String java) {
        if (isEnabled(TraceSystem.INFO)) {
            traceWriter.write(TraceSystem.INFO, module, lineSeparator +
                    "/**/" + java, null);
        }
    }

    /**
     * Write Java source code with trace level DEBUG to the trace system.
     *
     * @param java the source code
     */
    void debugCode(String java) {
        if (isEnabled(TraceSystem.DEBUG)) {
            traceWriter.write(TraceSystem.DEBUG, module, lineSeparator +
                    "/**/" + java, null);
        }
    }

}
