/**
 * Copyright (C) 2012 FuseSource, Inc.
 * http://fusesource.com
 *
 * Licensed 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.fusesource.hawtdispatch.internal;

import org.fusesource.hawtdispatch.DispatchQueue;
import org.fusesource.hawtdispatch.Metrics;
import org.fusesource.hawtdispatch.Task;
import org.fusesource.hawtdispatch.TaskWrapper;

import java.util.LinkedList;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 
 * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
 */
public class SerialDispatchQueue extends AbstractDispatchObject implements HawtDispatchQueue {

    protected volatile String label;

    protected final AtomicBoolean triggered = new AtomicBoolean();
    protected final ConcurrentLinkedQueue<Task> externalQueue = new ConcurrentLinkedQueue<Task>();
    private final LinkedList<Task> localQueue = new LinkedList<Task>();
    private final LinkedList<Task> sourceQueue= new LinkedList<Task>();
    private final ThreadLocal<Boolean> executing = new ThreadLocal<Boolean>();
    private MetricsCollector metricsCollector = InactiveMetricsCollector.INSTANCE;
    private boolean profile=false;

    public SerialDispatchQueue(String label) {
        this.label = label;
    }

    public void execute(Task task) {
        assert task != null;
        enqueue(metricsCollector.track(task));
    }

    @Deprecated
    public void execute(final Runnable runnable) {
        execute(new TaskWrapper(runnable));
    }

    @Deprecated()
    public void executeAfter(long delay, TimeUnit unit, Runnable runnable) {
        this.executeAfter(delay, unit, new TaskWrapper(runnable));
    }

    public LinkedList<Task> getSourceQueue() {
        return sourceQueue;
    }

    private void enqueue(Task runnable) {
        // We can take a shortcut...
        if( executing.get()!=null ) {
            localQueue.add(runnable);
        } else {
            externalQueue.add(runnable);
            triggerExecution();
        }
    }

    public void run() {

        checkCollector();

        HawtDispatchQueue original = HawtDispatcher.CURRENT_QUEUE.get();
        HawtDispatcher.CURRENT_QUEUE.set(this);
        executing.set(Boolean.TRUE);
        try {
            Task runnable;
            while( (runnable = externalQueue.poll())!=null ) {
                localQueue.add(runnable);
            }
            while(true) {
                if( isSuspended() ) {
                    return;
                }
                runnable = localQueue.poll();
                if( runnable==null ) {
                    return;
                }
                try {
                    runnable.run();
                } catch (Throwable e) {
                    Thread thread = Thread.currentThread();
                    thread.getUncaughtExceptionHandler().uncaughtException(thread, e);
                }
            }
        } finally {

            // Posts any deferred events.  This ensures
            // the next events generated by this dispatch
            // queue are received in order.
            for (Runnable runnable : sourceQueue) {
                runnable.run();
            }
            sourceQueue.clear();

            executing.remove();
            HawtDispatcher.CURRENT_QUEUE.set(original);
            triggered.set(false);
            boolean empty = externalQueue.isEmpty() && localQueue.isEmpty();
            if( !isSuspended() && !empty) {
                triggerExecution();
            }
        }
    }

    protected void triggerExecution() {
        if( triggered.compareAndSet(false, true) ) {
            getTargetQueue().execute(this);
        }
    }

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public boolean isExecuting() {
        return executing.get()!=null;
    }

    public void assertExecuting() {
        assert isExecuting() : getDispatcher().assertMessage(getLabel());
    }


    @Override
    protected void onStartup() {
        triggerExecution();
    }

    @Override
    protected void onResume() {
        triggerExecution();
    }

    public QueueType getQueueType() {
        return QueueType.SERIAL_QUEUE;
    }

    public void executeAfter(long delay, TimeUnit unit, Task task) {
        getDispatcher().timerThread.addRelative(task, this, delay, unit);
    }

    public DispatchQueue createQueue(String label) {
        DispatchQueue rc = getDispatcher().createQueue(label);
        rc.setTargetQueue(this);
        return rc;
    }

    public HawtDispatcher getDispatcher() {
        HawtDispatchQueue target = getTargetQueue();
        if (target ==null ) {
            throw new UnsupportedOperationException();
        }
        return target.getDispatcher();
    }

    public SerialDispatchQueue isSerialDispatchQueue() {
        return this;
    }

    public ThreadDispatchQueue isThreadDispatchQueue() {
        return null;
    }

    public GlobalDispatchQueue isGlobalDispatchQueue() {
        return null;
    }

    public void profile(boolean profile) {
        this.profile = profile;
        checkCollector();
    }

    public boolean profile() {
        return this.profile;
    }

    private void checkCollector() {
        if( profile() || getDispatcher().profile() ) {
            if(  metricsCollector == InactiveMetricsCollector.INSTANCE ) {
                metricsCollector = new ActiveMetricsCollector(this);
                getDispatcher().track(this);
            }
        } else {
            if(  metricsCollector != InactiveMetricsCollector.INSTANCE ) {
                metricsCollector = InactiveMetricsCollector.INSTANCE;
                getDispatcher().untrack(this);
            }
        }
    }

    public Metrics metrics() {
        return metricsCollector.metrics();
    }

    private int drains() {
        return getDispatcher().drains;
    }

    @Override
    public String toString() {
        if( label == null ) {
            return "serial queue";
        } else {
            return "serial queue { label: \""+label+"\" }";
        }
    }
}
