/*
 * 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.openejb.util;

import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;

public class LogStreamAsync implements LogStream, Closeable {

    private static final LinkedBlockingQueue<Message> log = new LinkedBlockingQueue<>();
    private static final Thread t = new Thread(new Consumer(LogStreamAsync.log), "LogStreamAsync.Thread");
    private static final AtomicBoolean started = new AtomicBoolean(false);
    private final LogStream ls;

    private enum level {
        fatal,
        error,
        warn,
        info,
        debug,
        quit,
    }

    public LogStreamAsync(final LogStream ls) {
        this.ls = ls;

        if (!started.getAndSet(true)) {
            t.setDaemon(true);
            t.start();
        }
    }

    @Override
    public void close() throws IOException {
        LogStreamAsync.log.clear();
        try {
            LogStreamAsync.log.put(new Message(this.ls, level.quit, ""));
        } catch (final InterruptedException e) {
            //Ignore
        }
    }

    @Override
    public boolean isFatalEnabled() {
        return ls.isFatalEnabled();
    }

    @Override
    public void fatal(final String message) {
        this.log(level.fatal, message);
    }

    @Override
    public void fatal(final String message, final Throwable t) {
        this.log(level.fatal, message, t);
    }

    @Override
    public boolean isErrorEnabled() {
        return ls.isErrorEnabled();
    }

    @Override
    public void error(final String message) {
        this.log(level.error, message);
    }

    @Override
    public void error(final String message, final Throwable t) {
        this.log(level.error, message, t);
    }

    @Override
    public boolean isWarnEnabled() {
        return ls.isWarnEnabled();
    }

    @Override
    public void warn(final String message) {
        this.log(level.warn, message);
    }

    @Override
    public void warn(final String message, final Throwable t) {
        this.log(level.warn, message, t);
    }

    @Override
    public boolean isInfoEnabled() {
        return ls.isInfoEnabled();
    }

    @Override
    public void info(final String message) {
        this.log(level.info, message, null);
    }

    @Override
    public void info(final String message, final Throwable t) {
        this.log(level.info, message, null);
    }

    @Override
    public boolean isDebugEnabled() {
        return ls.isDebugEnabled();
    }

    @Override
    public void debug(final String message) {
        this.log(level.debug, message);
    }

    @Override
    public void debug(final String message, final Throwable t) {
        this.log(level.debug, message, t);
    }

    public void log(final level l, final String s) {
        this.log(l, s, null);
    }

    public void log(final level l, final String s, final Throwable t) {
        try {
            LogStreamAsync.log.put(new Message(this.ls, l, s, t));
        } catch (final InterruptedException e) {
            //Ignore
        }
    }

    private static final class Message {

        private final LogStream ls;
        private final level l;
        private final String s;
        private final Throwable t;

        private Message(final LogStream ls, final level l, final String s) {
            this(ls, l, s, null);
        }

        private Message(final LogStream ls, final level l, final String s, final Throwable t) {
            this.ls = ls;
            this.l = l;
            this.s = s;
            this.t = t;
        }
    }

    private static final class Consumer implements Runnable {

        private final BlockingQueue<Message> queue;

        private Consumer(final BlockingQueue<Message> queue) {
            this.queue = queue;
        }

        public void run() {

            try {
                Message msg;
                while (!level.quit.equals((msg = queue.take()).l)) {
                    final Throwable t = msg.t;

                    if (null != t) {
                        switch (msg.l) {
                            case fatal:
                                msg.ls.fatal(msg.s, t);
                                break;
                            case error:
                                msg.ls.error(msg.s, t);
                                break;
                            case warn:
                                msg.ls.warn(msg.s, t);
                                break;
                            case info:
                                msg.ls.info(msg.s, t);
                                break;
                            case debug:
                                msg.ls.debug(msg.s, t);
                                break;
                        }
                    } else {
                        switch (msg.l) {
                            case fatal:
                                msg.ls.fatal(msg.s);
                                break;
                            case error:
                                msg.ls.error(msg.s);
                                break;
                            case warn:
                                msg.ls.warn(msg.s);
                                break;
                            case info:
                                msg.ls.info(msg.s);
                                break;
                            case debug:
                                msg.ls.debug(msg.s);
                                break;
                        }
                    }
                }
            } catch (final InterruptedException e) {
                //Exit
            }
        }
    }
}