package com.atlassian.bitbucket.mesh.test.cluster;

import com.atlassian.bitbucket.mesh.io.IoConsumer;
import org.slf4j.event.Level;

import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.util.Objects.requireNonNull;

/**
 * Consume stdout output from a Mesh process and forwards any lines logged at {@link #level} or higher to stdout.
 * This ensures that relevant logging from the Mesh nodes is included in the test output.
 */
public class ConsoleLogForwarder implements IoConsumer<ByteBuffer> {

    //Matches any line of format "YYYY-MM-DD HH:mm:ss,SSS LEVEL" where LEVEL is one of
    //"TRACE", "DEBUG", "INFO", "WARN", or "ERROR"
    private static final Pattern PATTERN_LOG_EVENT = Pattern.compile(
            "(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{3}) (TRACE|DEBUG|INFO|WARN|ERROR)\\s.+");

    private final String linePrefix;
    private final ByteArrayOutputStream partialLine;
    private final PrintStream writer;

    private Level currentLevel;
    private volatile Level level;

    public ConsoleLogForwarder(String linePrefix, Level level, PrintStream writer) {
        this.level = level;
        this.linePrefix = linePrefix;
        this.writer = writer;

        currentLevel = Level.TRACE;
        partialLine = new ByteArrayOutputStream();
    }

    @Override
    public void accept(ByteBuffer buffer) throws IOException {
        //Copy the data to the internal partial line
        Channels.newChannel(partialLine).write(buffer);

        //Convert to a string and emit full lines
        String data = partialLine.toString();
        int offset = 0;
        int index;
        while (offset < data.length() && (index = data.indexOf('\n', offset)) != -1) {
            String line = data.substring(offset, index + 1);
            if (getLevel(line).toInt() >= level.toInt()) {
                writer.print(linePrefix);
                writer.print(line);
            }
            offset = index + 1;
        }

        partialLine.reset();
        if (offset < data.length()) {
            //Put the final partial line back into partialLine
            partialLine.write(data.substring(offset).getBytes(StandardCharsets.UTF_8));
        }
    }

    public void setLevel(@Nonnull Level level) {
        this.level = requireNonNull(level, "level");
    }

    private Level getLevel(String line) {
        Matcher matcher = PATTERN_LOG_EVENT.matcher(line);
        if (matcher.find()) {
            currentLevel = Level.valueOf(matcher.group(2));
        }
        return currentLevel;
    }
}
