/*
 * This file is part of libbluray
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see
 * <http://www.gnu.org/licenses/>.
 */

package org.videolan;

import java.util.LinkedList;

public class BDJActionQueue implements Runnable {

    public static BDJActionQueue create(String name) {
        return create(null, name);
    }

    public static BDJActionQueue create(BDJThreadGroup threadGroup, String name) {
        BDJActionQueue aq = new BDJActionQueue(threadGroup, name);
        aq.startThread(threadGroup, name);
        return aq;
    }

    private BDJActionQueue(BDJThreadGroup threadGroup, String name) {
        if (threadGroup == null) {
            if (BDJXletContext.getCurrentContext() != null) {
                logger.error("BDJActionQueue " + name + " created from wrong context: " + Logger.dumpStack());
                // throw new SecurityException();
            }
        }
    }

    private void startThread(BDJThreadGroup threadGroup, String name) {
        /* run all actions in given thread group / xlet context */
        thread = new Thread(threadGroup, this, name + ".BDJActionQueue");
        thread.setDaemon(true);
        thread.start();

        watchdog = new Watchdog();
        watchdog.start(name);
    }

    public void shutdown() {

        synchronized (actions) {
            terminated = true;
            actions.addLast(null);
            actions.notifyAll();
        }
        watchdog.shutdown();
        try {
            thread.join();
        } catch (InterruptedException t) {
            logger.error("Error joining thread: " + t);
        }
    }

    public void run() {
        while (true) {
            Object action;
            synchronized (actions) {
                while (actions.isEmpty()) {
                    try {
                        actions.wait();
                    } catch (InterruptedException e) {
                    }
                }
                action = actions.removeFirst();
            }
            if (action == null)
                return;
            try {
                watchdog.startAction(action);

                ((BDJAction)action).process();

                watchdog.endAction();
            } catch (ThreadDeath d) {
                System.err.println("action failed: " + d + "\n");
                throw d;
            } catch (Throwable e) {
                System.err.println("action failed: " + e + "\n" + Logger.dumpStack(e));
            }
        }
    }

    public void put(BDJAction action) {
        if (action != null) {
            synchronized (actions) {
                if (!terminated) {
                    actions.addLast(action);
                    actions.notifyAll();
                } else {
                    logger.error("Action skipped (queue stopped): " + action);
                    action.abort();
                }
            }
        }
    }

    private boolean terminated = false;
    private Thread thread;
    private LinkedList actions = new LinkedList();

    private static final Logger logger = Logger.getLogger(BDJActionQueue.class.getName());

    /*
     *
     */

    private Watchdog watchdog = null;

    class Watchdog implements Runnable {

        private Object currentAction = null;
        private boolean terminate = false;

        Watchdog() {}

        synchronized void start(String name) {
            Thread t = new Thread(null, this, name + ".BDJActionQueue.Monitor");
            t.setDaemon(true);
            t.start();
        }

        public synchronized void shutdown() {
            terminate = true;
            notifyAll();
        }

        public synchronized void startAction(Object action) {
            currentAction = action;
            notifyAll();
        }

        public synchronized void endAction() {
            currentAction = null;
            notifyAll();
        }

        public void run() {
            Object loggedAction = null;
            synchronized (this) {
                while (!terminate) {

                    if (loggedAction != null && currentAction != loggedAction) {
                        loggedAction = null;
                        logger.info("Callback returned (" + thread + ")");
                    }

                    try {
                        if (currentAction == null) {
                            wait();
                        } else {
                            Object cachedAction = currentAction;
                            wait(5000);
                            if (currentAction == cachedAction && loggedAction != cachedAction) {
                                logger.error("Callback timeout in " + thread + ", callback=" + currentAction + "\n" +
                                             PortingHelper.dumpStack(thread));
                                loggedAction = cachedAction;
                            }
                        }
                    } catch (InterruptedException e) {
                    }
                }
            }
        }
    }
}
