/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.openjpa.trader.service;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.lib.conf.Configurable;
import org.apache.openjpa.lib.conf.Configuration;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.log.LogFactory;
import org.apache.openjpa.trader.domain.LogStatement;

/**
 * Specialized log to consolidate multiple logs into a single one.
 * Selects only query related messages.
 * <br>
 * Designed to capture multiple logs used by different slices. It would have
 * been more useful to capture the slice thread that executed the query, but
 * that is not possible in all cases as the log statement is emitted from the
 * kernel's main thread.
 *
 * @author Pinaki Poddar
 *
 */
public class BufferedLog implements LogFactory, Configurable  {
    private int _history    = 100;
    private String _diagCtx;
    private Configuration _conf;
    static String[] SQL_MARKERS  = {"INSERT INTO", "SELECT", "UPDATE", "DELETE"};
    static String[] JPQL_MARKERS = {"Executing query: ["};
    static List<String> CHANNELS = Arrays.asList(OpenJPAConfiguration.LOG_QUERY, JDBCConfiguration.LOG_SQL);

    private static LinkedList<LogStatement> _messageModel;
    static {
        _messageModel = new LinkedList<LogStatement>();
    }

    public void setConfiguration(Configuration conf) {
        _conf = conf;
    }

    public void endConfiguration() {
    }

    public void startConfiguration() {
    }


    public BufferedLog() {
        super();
    }

    public void setDiagnosticContext(String ctx) {
        System.err.println(ctx);
        _diagCtx = ctx;
    }


    public Log getLog(String channel) {
        return new ChannelLog(channel);
    }

    public void setHistory(int i) {
        _history = Math.max(i, 1);
    }

    public int getHistory() {
        return _history;
    }

    String getContext() {
        if (_diagCtx != null)
            return _diagCtx;
        if (_conf == null || isEmpty(_conf.getId()))
            return "";
        else {
            _diagCtx = _conf.getId();
            return _conf.getId();
        }
    }

    void addStatement(LogStatement stmt) {
        _messageModel.addLast(stmt);
        if (_messageModel.size() > _history) {
            _messageModel.removeFirst();
        }
    }

    boolean isEmpty(String s) {
        return s == null || s.trim().length() == 0;
    }

    public List<LogStatement> get() {
        List<LogStatement> result = new ArrayList<LogStatement>(_messageModel);
        _messageModel.clear();
        return result;
    }


    public class ChannelLog implements Log {
        final String _channel;
        final String _thread;
        public ChannelLog(String channel) {
            _channel = channel;
            _thread = Thread.currentThread().getName();
        }

        public void error(Object o) {
            createLogStatement("ERROR", o, null);
        }

        public void error(Object o, Throwable t) {
            createLogStatement("ERROR", o, t);
        }


        public void fatal(Object o) {
            createLogStatement("FATAL", o, null);
        }


        public void fatal(Object o, Throwable t) {
            createLogStatement("FATAL", o, t);
        }


        public void info(Object o) {
            createLogStatement("INFO", o, null);
        }


        public void info(Object o, Throwable t) {
            createLogStatement("INFO", o, t);
        }


        public boolean isErrorEnabled() {
            return true;
        }

        public boolean isFatalEnabled() {
            return true;
        }

        public boolean isInfoEnabled() {
            return CHANNELS.contains(_channel);
        }

        public boolean isTraceEnabled() {
            return CHANNELS.contains(_channel);
        }

        public boolean isWarnEnabled() {
            return true;
        }


        public void trace(Object o) {
            createLogStatement("TRACE", o, null);
        }

        public void trace(Object o, Throwable t) {
            createLogStatement("TRACE", o, t);
        }

        public void warn(Object o) {
            createLogStatement("WARN", o, null);
        }

        public void warn(Object o, Throwable t) {
            createLogStatement("WARN", o, t);
        }

        protected void createLogStatement(String level, Object message, Throwable t) {
            String msg = message == null ? null : message.toString();
            msg = extractQuery(msg);
            if (msg == null) {
                return;
            }
            addStatement(new LogStatement(level, getContext(),
                    _thread, _channel, msg));
            if (t != null) {
                StringWriter buffer = new StringWriter();
                t.printStackTrace(new PrintWriter(buffer));
                addStatement(new LogStatement(
                        level, getContext(),
                        Thread.currentThread().getName(), _channel,
                        buffer.toString()));
            }
        }

        public String extractQuery(String msg) {
            if (msg == null)
                return null;
            if ("openjpa.jdbc.SQL".equals(_channel)) {
                return getQuery(msg, SQL_MARKERS, true);
            } else if ("openjpa.Query".equals(_channel)) {
                return getQuery(msg, JPQL_MARKERS, false);
            }
            return null;
        }

        private String getQuery(String message, String[] markers, boolean sql) {
            int k = -1;
            for (int i = 0; i < markers.length; i++) {
                k = message.indexOf(markers[i]);
                if (k != -1) {
                    int m = sql ? 0 : markers[i].length();
                    return message.substring(k+m).trim();
                }
            }
            return null;
        }

    }


}
