package com.hypirion.beckon;

import java.util.Map;
import java.util.HashMap;

import clojure.lang.Atom;
import clojure.lang.PersistentHashMap;
import clojure.lang.Keyword;
import clojure.lang.AFn;
import clojure.lang.IFn;
import clojure.lang.Seqable;
import clojure.lang.ISeq;

public class SignalAtoms {
    private static final Map<String, Atom> atoms = new HashMap<String, Atom>();

    /**
     * The keyword <code>:signal</code>.
     */
    public static final Keyword SIGNAL = Keyword.intern("signal");

    /**
     * A standard Atom validator function which tests whether the new value is a
     * Seqable, where each element in the Seqable implements Runnable.
     */
    public static final IFn SIGNAL_ATOM_VALIDATOR = new SignalAtomValidator();

    /**
     * Returns a Clojure Atom containing a Seqable. The Seqable represents a
     * list of functions where the functions are called in order whenever a
     * Signal by the type <code>signame</code> is received by this process.
     *
     * @exception SignalHandlerNotFoundException if this code is unable to
     * detect a SignalHandler and/or a Signal class.
     */
    public static final synchronized Atom getSignalAtom(String signame)
        throws SignalHandlerNotFoundException{
        if (!atoms.containsKey(signame)) {
            Object list;
            try {
                list = SignalRegistererHelper.getHandlerSeq(signame);
            }
            catch (LinkageError le) {
                throw new SignalHandlerNotFoundException();
            }
            PersistentHashMap metadata = PersistentHashMap.create(SIGNAL, signame);
            Atom atm = new Atom(list, metadata);
            atm.setValidator(SIGNAL_ATOM_VALIDATOR);
            SignalAtomWatch saw = new SignalAtomWatch(signame);
            atm.addWatch(signame, saw);
            atoms.put(signame, atm);
        }
        return atoms.get(signame);
    }

    private static class SignalAtomValidator extends AFn {
        @Override
        public Object invoke(Object newVal) {
            if (newVal instanceof Seqable) {
                ISeq seq = ((Seqable) newVal).seq();
                // An empty seqable returns null.
                if (seq == null) {
                    return true;
                }
                int count = seq.count();
                while (count --> 0) {
                    Object o = seq.first();
                    seq = seq.next();
                    if (!(o instanceof Runnable)) {
                        return false;
                    }
                }
                return true;
            }
            else if (newVal == null) {
                return true;
            }
            else {
                return false;
            }
        }
    }

    private static class SignalAtomWatch extends AFn {
        public final String signame;

        public SignalAtomWatch(String signame) {
            this.signame = signame;
        }

        @Override
        public Object invoke(Object key, Object ref, Object oldState, Object newState) {
            SignalRegistererHelper.register(signame, (Seqable) newState);
            return null;
        }
    }
}
