/* UtilLib - Copyright (c) 2012-2025 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package net.mapeadores.util.logging;

import java.io.IOException;
import java.io.StringReader;
import java.util.AbstractList;
import java.util.Collections;
import java.util.List;
import java.util.RandomAccess;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.SourceLocator;
import javax.xml.transform.TransformerException;
import net.mapeadores.util.exceptions.ShouldNotOccurException;
import net.mapeadores.util.html.HtmlPrinter;
import net.mapeadores.util.instruction.InstructionErrorHandler;
import net.mapeadores.util.localisation.Message;
import net.mapeadores.util.xml.AppendableXMLWriter;
import net.mapeadores.util.xml.DOMUtils;
import net.mapeadores.util.xml.DomMessages;
import net.mapeadores.util.xml.XMLUtils;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;


/**
 *
 * @author Vincent Calame
 */
public final class LogUtils {

    public final static SourceLogs EMPTY_SOURCELOGS = new EmptySourceLogs();
    public final static MessageHandler NULL_MESSAGEHANDLER = new NullMultiMessageHandler();
    public final static MultiMessageHandler NULL_MULTIMESSAGEHANDLER = new NullMultiMessageHandler();
    public final static LineMessageHandler NULL_LINEMESSAGEHANDLER = new NullLineMessageHandler();
    public final static List<Message> EMPTY_MESSAGELIST = Collections.emptyList();
    public final static LineLogs EMPTY_LINELOGS = new EmptyLineLogs();
    private final static Object[] EMPTY = new Object[0];

    private LogUtils() {
    }

    public static LineMessage toLineMessage(String category, int lineNumber, String messageKey, Object... messageValues) {
        if (messageKey == null) {
            throw new IllegalArgumentException("messageKey is null");
        }
        if (messageValues == null) {
            messageValues = EMPTY;
        }
        return new InternalLineMessage(category, lineNumber, messageKey, messageValues);
    }

    public static LineMessage toLineMessage(String category, int lineNumber, Message message) {
        return new InternalLineMessage(category, lineNumber, message.getMessageKey(), message.getMessageValues());
    }

    public static String toXmlString(SourceLogs logs) {
        StringBuilder buf = new StringBuilder();
        try {
            AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(buf);
            XMLUtils.appendXmlDeclaration(xmlWriter);
            SourceLogXMLPart messageXMLPart = new SourceLogXMLPart(xmlWriter);
            messageXMLPart.addLogs(logs);
        } catch (IOException ioe) {

        }
        return buf.toString();
    }

    public static String toXmlString(SourceLog log) {
        StringBuilder buf = new StringBuilder();
        try {
            AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(buf);
            XMLUtils.appendXmlDeclaration(xmlWriter);
            SourceLogXMLPart messageXMLPart = new SourceLogXMLPart(xmlWriter);
            messageXMLPart.addLog(log);
        } catch (IOException ioe) {

        }
        return buf.toString();
    }


    public static SourceLog readLog(String name, String xmlString) {
        SourceLogBuilder logBuilder = new SourceLogBuilder(name);
        DocumentBuilder docBuilder = DOMUtils.newDocumentBuilder();
        try {
            Document document = docBuilder.parse(new InputSource(new StringReader(xmlString)));
            SourceLogDOMReader reader = new SourceLogDOMReader(LogUtils.NULL_MESSAGEHANDLER);
            reader.readLog(logBuilder, document.getDocumentElement(), "");
            return logBuilder.toSourceLog();
        } catch (SAXException saxe) {
            DomMessages.saxException(logBuilder, saxe);
            return logBuilder.toSourceLog();
        } catch (IOException ioe) {
            throw new ShouldNotOccurException(ioe);
        }
    }

    public static SourceLogs readLogs(String xmlString) {
        SourceLogsBuilder logBuilder = new SourceLogsBuilder();
        DocumentBuilder docBuilder = DOMUtils.newDocumentBuilder();
        try {
            Document document = docBuilder.parse(new InputSource(new StringReader(xmlString)));
            SourceLogDOMReader reader = new SourceLogDOMReader(LogUtils.NULL_MESSAGEHANDLER);
            reader.readLogs(logBuilder, document.getDocumentElement(), "");
        } catch (SAXException saxe) {
            logBuilder.setCurrentSource("logfile");
            DomMessages.saxException(logBuilder, saxe);
        } catch (IOException ioe) {
            throw new ShouldNotOccurException(ioe);
        }
        return logBuilder.toSourceLogs();
    }

    public static boolean printHtml(HtmlPrinter hp, SourceLogs logs, String listCssClasses) {
        if (logs.isEmpty()) {
            return false;
        }
        hp
                .UL(listCssClasses);
        for (SourceLog log : logs) {
            List<SourceMessage> messageList = log.getSourceMessageList();
            if (!messageList.isEmpty()) {
                hp
                        .LI();
                hp
                        .P()
                        .CODE("cm-link")
                        .__escape(log.getName())
                        ._CODE()
                        ._P();
                hp
                        .UL();
                for (SourceMessage sourceMessage : messageList) {
                    hp
                            .LI()
                            .P()
                            .CODE("cm-qualifier")
                            .__escape('[')
                            .__escape(sourceMessage.getCategory())
                            .__escape(']')
                            ._CODE()
                            .__space()
                            .SPAN("cm-quote")
                            .__localize(sourceMessage)
                            ._SPAN()
                            ._P()
                            ._LI();
                }
                hp
                        ._UL();
                hp
                        ._LI();
            }
        }
        hp
                ._UL();
        return true;
    }

    public static String getClassName(String category) {
        if (category.startsWith("severe.")) {
            return "global-ErrorMessage";
        }
        if (category.startsWith("warning.")) {
            return "global-WarningMessage";
        }
        return "";
    }

    public static InstructionErrorHandler encapsulate(LineMessageHandler lineMessageHandler, String category, int lineNumber) {
        return new LineInstructionErrorHandler(lineMessageHandler, category, lineNumber);
    }

    public static List<SourceMessage> wrap(SourceMessage[] array) {
        return new SourceMessageList(array);
    }

    public static List<LineMessage> wrap(LineMessage[] array) {
        return new LineMessageList(array);
    }

    public static void handleSAXException(String category, SAXException saxException, LineMessageHandler lineMessageHandler) {
        if (saxException instanceof SAXParseException) {
            SAXParseException spe = (SAXParseException) saxException;
            lineMessageHandler.addMessage(category, spe.getLineNumber(), "_ error.exception.xml.sax_parse", saxException.getMessage(), String.valueOf(spe.getLineNumber()), String.valueOf(spe.getColumnNumber()));
        } else {
            lineMessageHandler.addMessage(category, 0, "_ error.exception.xml.sax", saxException.getMessage());
        }
    }

    /**
     * Traitement particulier car sourceLocator n'est pas toujours défini par
     * Xalan
     *
     * @param category
     * @param transformerException
     * @param systemId
     * @param lineMessageHandler
     */
    public static void handleTransformerException(String category, TransformerException transformerException, String systemId, LineMessageHandler lineMessageHandler) {
        SourceLocator sourceLocator = transformerException.getLocator();
        boolean done = false;
        if (sourceLocator != null) {
            String sourceSystemId = sourceLocator.getSystemId();
            int lineNumber = sourceLocator.getLineNumber();
            if ((lineNumber != -1) && ((sourceSystemId == null) || (sourceSystemId.equals(systemId)))) {
                lineMessageHandler.addMessage(category, lineNumber, "_ error.exception.xml.xslt", transformerException.getMessage());
                done = true;
            }
        }
        if (done) {
            return;
        }
        String message = transformerException.getMessage().trim();
        int idx = message.indexOf("line ");
        if (idx > - 1) {
            String end = message.substring(idx + 5).trim();
            String number = end;
            int spaceIdx = end.indexOf(":");
            if (spaceIdx > -1) {
                number = end.substring(0, spaceIdx);
                end = end.substring(spaceIdx + 1).trim();
            }
            try {
                int lineNumber = Integer.parseInt(number);
                String currentSystemId = message.substring(0, idx).trim();
                if (currentSystemId.endsWith(":")) {
                    currentSystemId = currentSystemId.substring(0, currentSystemId.length() - 1).trim();
                }
                if ((currentSystemId.isEmpty()) || (currentSystemId.equals(systemId))) {
                    lineMessageHandler.addMessage(category, lineNumber, "_ error.exception.xml.xslt", end);
                    done = true;
                }
            } catch (NumberFormatException nfe) {

            }
        }
        if (!done) {
            lineMessageHandler.addMessage(category, 0, "_ error.exception.xml.xslt", transformerException.getMessageAndLocation());
        }
    }

    public static CommandMessage error(String messageKey, Object... messageValues) {
        if (messageKey == null) {
            throw new IllegalArgumentException("messageKey is null");
        }
        if (messageValues == null) {
            messageValues = EMPTY;
        }
        return new SimpleCommandMessage(messageKey, messageValues, true);
    }

    public static CommandMessage error(Message message) {
        Object[] messageValues = message.getMessageValues();
        if (messageValues == null) {
            messageValues = EMPTY;
        }
        return new SimpleCommandMessage(message.getMessageKey(), messageValues, true);
    }

    public static CommandMessage done(String messageKey, Object... messageValues) {
        if (messageKey == null) {
            throw new IllegalArgumentException("messageKey is null");
        }
        if (messageValues == null) {
            messageValues = EMPTY;
        }
        return new SimpleCommandMessage(messageKey, messageValues, false);
    }

    public static CommandMessage done(Message message) {
        Object[] messageValues = message.getMessageValues();
        if (messageValues == null) {
            messageValues = EMPTY;
        }
        return new SimpleCommandMessage(message.getMessageKey(), messageValues, false);
    }


    public static class ShouldNotOccurMessageHandler implements MessageHandler {

        public ShouldNotOccurMessageHandler() {
        }

        @Override
        public void addMessage(String category, Message message) {
            StringBuilder buf = new StringBuilder();
            buf.append(category);
            buf.append(" / ");
            buf.append(message.getMessageKey());
            Object[] values = message.getMessageValues();
            for (Object value : values) {
                buf.append(" / ");
                buf.append(value);
            }
            throw new ShouldNotOccurException(buf.toString());
        }

    }


    private static class InternalLineMessage implements LineMessage {

        private final String category;
        private final String messageKey;
        private final Object[] messageValues;
        private final int lineNumber;

        private InternalLineMessage(String category, int lineNumber, String messageKey, Object[] messageValues) {
            this.category = category;
            this.lineNumber = lineNumber;
            this.messageKey = messageKey;
            this.messageValues = messageValues;
        }

        @Override
        public String getCategory() {
            return category;
        }

        @Override
        public int getLineNumber() {
            return lineNumber;
        }

        @Override
        public String getMessageKey() {
            return messageKey;
        }

        @Override
        public Object[] getMessageValues() {
            return messageValues;
        }

    }


    private static class EmptySourceLogs extends AbstractList<SourceLog> implements SourceLogs {

        private EmptySourceLogs() {
        }

        @Override
        public int size() {
            return 0;
        }

        @Override
        public SourceLog get(int i) {
            throw new IndexOutOfBoundsException("size = 0");
        }

    }


    private static class NullMultiMessageHandler implements MultiMessageHandler {

        private NullMultiMessageHandler() {
        }

        @Override
        public void addMessage(String category, Message message) {
        }

        @Override
        public void addMessage(String category, String messageKey, Object... messageValues) {

        }

        @Override
        public void setCurrentSource(String uri) {
        }

    }


    private static class NullLineMessageHandler implements LineMessageHandler {

        private NullLineMessageHandler() {
        }

        @Override
        public void addMessage(LineMessage lineMessage) {
        }

        @Override
        public void addMessage(String category, int lineNumber, Message message) {
        }

        @Override
        public void addMessage(String category, int lineNumber, String messageKey, Object... messageValues) {

        }

        @Override
        public void addMessage(LineMessageException lme) {

        }

    }


    private static class LineInstructionErrorHandler implements InstructionErrorHandler {

        private final String category;
        private final LineMessageHandler lineMessageHandler;
        private final int lineNumber;

        private LineInstructionErrorHandler(LineMessageHandler lineMessageHandler, String category, int lineNumber) {
            this.lineMessageHandler = lineMessageHandler;
            this.category = category;
            this.lineNumber = lineNumber;
        }

        @Override
        public void invalidAsciiCharacterError(String part, int row, int col) {
            addMessage("_ error.wrong.character_notascii", part, row, col);
        }

        @Override
        public void invalidEndCharacterError(String part, int row, int col) {
            addMessage("_ error.wrong.character_end", part, row, col);
        }

        @Override
        public void invalidSeparatorCharacterError(String part, int row, int col) {
            addMessage("_ error.wrong.character_separator", part, row, col);
        }

        private void addMessage(String messageKey, String part, int row, int col) {
            lineMessageHandler.addMessage(category, lineNumber, messageKey, part, row, col);
        }

    }


    private static class SourceMessageList extends AbstractList<SourceMessage> implements RandomAccess {

        private final SourceMessage[] array;

        private SourceMessageList(SourceMessage[] array) {
            this.array = array;
        }

        @Override
        public int size() {
            return array.length;
        }

        @Override
        public SourceMessage get(int index) {
            return array[index];
        }

    }


    private static class LineMessageList extends AbstractList<LineMessage> implements RandomAccess {

        private final LineMessage[] array;

        private LineMessageList(LineMessage[] array) {
            this.array = array;
        }

        @Override
        public int size() {
            return array.length;
        }

        @Override
        public LineMessage get(int index) {
            return array[index];
        }

    }


    private static class SimpleCommandMessage implements CommandMessage {

        private final String messageKey;
        private final Object[] messageValues;
        private final boolean isError;

        private SimpleCommandMessage(String messageKey, Object[] messageValues, boolean isError) {
            this.messageKey = messageKey;
            this.messageValues = messageValues;
            this.isError = isError;
        }

        @Override
        public String getMessageKey() {
            return messageKey;
        }

        @Override
        public Object[] getMessageValues() {
            return messageValues;
        }

        @Override
        public boolean isErrorMessage() {
            return isError;
        }

        @Override
        public List<Message> getMultiErrorList() {
            return EMPTY_MESSAGELIST;
        }


        @Override
        public SourceLogs getCommandLogs() {
            return null;
        }

    }


    private static class EmptyLineLogs extends AbstractList<LineLog> implements LineLogs {

        private EmptyLineLogs() {

        }

        @Override
        public int size() {
            return 0;
        }

        @Override
        public LineLog get(int i) {
            throw new IndexOutOfBoundsException("size = 0");
        }

    }


}
