package com.elphel.imagej.common;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;

/**
 * Tee System.out/System.err to a per-scene log file while preserving console output.
 * Usage:
 *   LogTee.install(); // once at startup
 *   LogTee.setSceneLog(path); // enable per-scene logging (append)
 *   LogTee.clearSceneLog();   // stop file logging
 */
public final class LogTee {
    private static final Object LOCK = new Object();
    private static final PrintStream ORIGINAL_OUT = System.out;
    private static final PrintStream ORIGINAL_ERR = System.err;
    private static volatile PrintStream fileStream = null;
    private static volatile Path sceneLogPath = null;
    private static volatile boolean installed = false;
    // codex 2026-01-25: optional console/file noise filter
    private static volatile boolean filterEnabled = false;
    private static volatile String[] filterSubstrings = null;

    private LogTee() {
    }

    public static void install() {
        synchronized (LOCK) {
            if (installed) return;
            configureFilterFromProperty();
            System.setOut(new PrintStream(new TeeStream(ORIGINAL_OUT), true));
            System.setErr(new PrintStream(new TeeStream(ORIGINAL_ERR), true));
            installed = true;
        }
    }

    public static void setSceneLog(Path path) throws IOException {
        if (path == null) {
            clearSceneLog();
            return;
        }
        synchronized (LOCK) {
            closeFileStream();
            fileStream = new PrintStream(new FileOutputStream(path.toFile(), true), true);
            sceneLogPath = path;
        }
    }

    public static void clearSceneLog() {
        synchronized (LOCK) {
            closeFileStream();
            fileStream = null;
            sceneLogPath = null;
        }
    }

    public static Path getSceneLog() {
        return sceneLogPath;
    }

    // codex 2026-01-25: set filters via -Delphel.logtee.filter
    // Values:
    //   "ome"  -> built-in OME/Bio-Formats noise filters
    //   "none" -> disable filtering
    //   comma-separated substrings to drop lines containing any of them
    private static void configureFilterFromProperty() {
        String value = System.getProperty("elphel.logtee.filter");
        if (value == null || value.trim().isEmpty()) {
            filterEnabled = false;
            filterSubstrings = null;
            return;
        }
        String trimmed = value.trim();
        if ("none".equalsIgnoreCase(trimmed)) {
            filterEnabled = false;
            filterSubstrings = null;
            return;
        }
        if ("ome".equalsIgnoreCase(trimmed)) {
            filterEnabled = true;
            filterSubstrings = new String[] {
                    "Loaded properties from: services.properties",
                    "Added interface interface ome.codecs.services.JAIIIOService",
                    "Checking comment style",
                    "Expected positive value for PhysicalSize",
                    "Parsing TIFF EXIF data",
                    "Populating OME metadata",
                    "Reading IFDs"
            };
            return;
        }
        String[] parts = trimmed.split(",");
        filterEnabled = parts.length > 0;
        filterSubstrings = parts;
    }

    private static boolean shouldFilterLine(String line) {
        if (!filterEnabled || filterSubstrings == null || line == null) {
            return false;
        }
        for (String token : filterSubstrings) {
            if (token == null) continue;
            String t = token.trim();
            if (t.isEmpty()) continue;
            if (line.contains(t)) return true;
        }
        return false;
    }

    private static void closeFileStream() {
        if (fileStream != null) {
            fileStream.flush();
            fileStream.close();
        }
    }

    private static final class TeeStream extends OutputStream {
        private final PrintStream console;
        private final StringBuilder buffer = new StringBuilder();

        private TeeStream(PrintStream console) {
            this.console = console;
        }

        @Override
        public void write(int b) throws IOException {
            if (!filterEnabled) {
                console.write(b);
                PrintStream fs = fileStream;
                if (fs != null) fs.write(b);
                return;
            }
            buffer.append((char) b);
            if (b == '\n') {
                flushBuffer();
            }
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            if (!filterEnabled) {
                console.write(b, off, len);
                PrintStream fs = fileStream;
                if (fs != null) fs.write(b, off, len);
                return;
            }
            String chunk = new String(b, off, len, StandardCharsets.UTF_8);
            for (int i = 0; i < chunk.length(); i++) {
                char c = chunk.charAt(i);
                buffer.append(c);
                if (c == '\n') {
                    flushBuffer();
                }
            }
        }

        @Override
        public void flush() throws IOException {
            if (filterEnabled && buffer.length() > 0) {
                flushBuffer();
            }
            console.flush();
            PrintStream fs = fileStream;
            if (fs != null) fs.flush();
        }

        private void flushBuffer() {
            String line = buffer.toString();
            buffer.setLength(0);
            if (shouldFilterLine(line)) {
                return;
            }
            console.print(line);
            PrintStream fs = fileStream;
            if (fs != null) fs.print(line);
        }
    }
}
