package org.xbib.logging.ext.formatters;

import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import org.xbib.logging.ExtLogRecord;
import org.xbib.logging.Level;
import org.xbib.logging.handlers.ConsoleHandler;
import org.xbib.logging.ext.util.formatter.ColorPrintf;
import org.xbib.logging.ext.util.formatter.ColorUtil;
import org.xbib.logging.ext.util.formatter.Printf;
import static java.lang.Math.abs;

/**
 * A pattern formatter that colorizes the pattern in a fixed manner.
 */
public class ColorPatternFormatter extends PatternFormatter {

    private final Printf printf;
    private final int darken;

    public ColorPatternFormatter() {
        this(0);
    }

    public ColorPatternFormatter(final String pattern) {
        this(0, pattern);
    }

    public ColorPatternFormatter(int darken) {
        this.darken = darken;
        this.printf = new ColorPrintf(darken);
    }

    public ColorPatternFormatter(int darken, final String pattern) {
        this(darken);
        setPattern(pattern);
    }

    public void setSteps(final FormatStep[] steps) {
        FormatStep[] colorSteps = new FormatStep[steps.length];
        for (int i = 0; i < steps.length; i++) {
            colorSteps[i] = colorize(steps[i]);
        }
        super.setSteps(colorSteps);
    }

    private FormatStep colorize(final FormatStep step) {
        return switch (step.getItemType()) {
            case LEVEL -> new LevelColorStep(step, darken);
            case SOURCE_CLASS_NAME -> new ColorStep(step, 0xff, 0xff, 0x44, darken);
            case DATE -> new ColorStep(step, 0xc0, 0xc0, 0xc0, darken);
            case SOURCE_FILE_NAME -> new ColorStep(step, 0xff, 0xff, 0x44, darken);
            case HOST_NAME -> new ColorStep(step, 0x44, 0xff, 0x44, darken);
            case SOURCE_LINE_NUMBER -> new ColorStep(step, 0xff, 0xff, 0x44, darken);
            case LINE_SEPARATOR -> step;
            case CATEGORY -> new ColorStep(step, 0x44, 0x88, 0xff, darken);
            case MDC -> new ColorStep(step, 0x44, 0xff, 0xaa, darken);
            case MESSAGE -> new ColorStep(step, 0xff, 0xff, 0xff, darken);
            case EXCEPTION_TRACE -> new ColorStep(step, 0xff, 0x44, 0x44, darken);
            case SOURCE_METHOD_NAME -> new ColorStep(step, 0xff, 0xff, 0x44, darken);
            case SOURCE_MODULE_NAME -> new ColorStep(step, 0x88, 0xff, 0x44, darken);
            case SOURCE_MODULE_VERSION -> new ColorStep(step, 0x44, 0xff, 0x44, darken);
            case NDC -> new ColorStep(step, 0x44, 0xff, 0xaa, darken);
            case PROCESS_ID -> new ColorStep(step, 0xdd, 0xbb, 0x77, darken);
            case PROCESS_NAME -> new ColorStep(step, 0xdd, 0xdd, 0x77, darken);
            case RELATIVE_TIME -> new ColorStep(step, 0xc0, 0xc0, 0xc0, darken);
            case RESOURCE_KEY -> new ColorStep(step, 0x44, 0xff, 0x44, darken);
            case SYSTEM_PROPERTY -> new ColorStep(step, 0x88, 0x88, 0x00, darken);
            case TEXT -> new ColorStep(step, 0xd0, 0xd0, 0xd0, darken);
            case THREAD_ID -> new ColorStep(step, 0x44, 0xaa, 0x44, darken);
            case THREAD_NAME -> new ColorStep(step, 0x44, 0xaa, 0x44, darken);
            default -> new ColorStep(step, 0xb0, 0xd0, 0xb0, darken);
        };
    }

    private String colorizePlain(final String str) {
        return str;
    }

    public String formatMessage(final LogRecord logRecord) {
        if (logRecord instanceof ExtLogRecord record) {
            if (record.getFormatStyle() != ExtLogRecord.FormatStyle.PRINTF || record.getParameters() == null
                    || record.getParameters().length == 0) {
                return colorizePlain(super.formatMessage(record));
            }
            return printf.format(record.getMessage(), record.getParameters());
        } else {
            return colorizePlain(super.formatMessage(logRecord));
        }
    }

    static final class ColorStep implements FormatStep {
        private final int r, g, b;
        private final FormatStep delegate;
        private final boolean trueColor = ConsoleHandler.isTrueColor();

        ColorStep(final FormatStep delegate, final int r, final int g, final int b, final int darken) {
            this.r = r >>> darken;
            this.g = g >>> darken;
            this.b = b >>> darken;
            this.delegate = delegate;
        }

        public void render(final Formatter formatter, final StringBuilder builder, final ExtLogRecord record) {
            ColorUtil.startFgColor(builder, trueColor, r, g, b);
            delegate.render(formatter, builder, record);
            ColorUtil.endFgColor(builder);
        }

        public void render(final StringBuilder builder, final ExtLogRecord record) {
            render(null, builder, record);
        }

        public int estimateLength() {
            return delegate.estimateLength() + 30;
        }

        public boolean isCallerInformationRequired() {
            return delegate.isCallerInformationRequired();
        }

        public ItemType getItemType() {
            return delegate.getItemType();
        }
    }

    static final class LevelColorStep implements FormatStep {
        private static final int LARGEST_LEVEL = Level.ERROR.intValue();
        private static final int SMALLEST_LEVEL = Level.TRACE.intValue();
        private static final int SATURATION = 66;
        private final FormatStep delegate;
        private final int darken;
        // capture current console state
        private final boolean trueColor = ConsoleHandler.isTrueColor();

        LevelColorStep(final FormatStep delegate, final int darken) {
            this.delegate = delegate;
            this.darken = darken;
        }

        public void render(final Formatter formatter, final StringBuilder builder, final ExtLogRecord record) {
            final int level = Math.max(Math.min(record.getLevel().intValue(), LARGEST_LEVEL), SMALLEST_LEVEL) - SMALLEST_LEVEL;
            // really crappy linear interpolation
            int r = ((level < 300 ? 0 : (level - 300) * (255 - SATURATION) / 300) + SATURATION) >>> darken;
            int g = ((300 - abs(level - 300)) * (255 - SATURATION) / 300 + SATURATION) >>> darken;
            int b = ((level > 300 ? 0 : level * (255 - SATURATION) / 300) + SATURATION) >>> darken;
            ColorUtil.startFgColor(builder, trueColor, r, g, b);
            delegate.render(formatter, builder, record);
            ColorUtil.endFgColor(builder);
        }

        public void render(final StringBuilder builder, final ExtLogRecord record) {
            render(null, builder, record);
        }

        public int estimateLength() {
            return delegate.estimateLength() + 30;
        }
    }
}
