/*******************************************************************************
* Copyright (c)                                                                *
*           2005   Tristan Allwood,                                            *
*           2005   Daniel Burke,                                               *
*           2005   Marc Hull,                                                  *
*           2005   Ekaterina Itskova,                                          *
*           2005   Steve Zymler                                                *
*                                                                              *
* Permission is hereby granted, free of charge, to any person obtaining a      *
* copy of this software and associated documentation files (the                *
* "Software"), to deal in the Software without restriction, including          *
* without limitation the rights to use, copy, modify, merge, publish,          *
* distribute, sublicense, and/or sell copies of the Software, and to           *
* permit persons to whom the Software is furnished to do so, subject to        *
* the following conditions:                                                    *
*                                                                              *
* The above copyright notice and this permission notice shall be included      *
* in all copies or substantial portions of the Software.                       *
*                                                                              *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS      *
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF                   *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.       *
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY         *
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,         *
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE            *
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                       *
*******************************************************************************/
package uk.ac.ic.doc.kevlar.publicInterfaces;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import uk.ac.ic.doc.kevlar.api.programs.arguments.IArgumentInstance;
import uk.ac.ic.doc.kevlar.api.programs.framework.internal.PipeManagerProvider;
import uk.ac.ic.doc.kevlar.api.programs.framework.internal.execute.ClientSidePipe;
import uk.ac.ic.doc.kevlar.api.programs.framework.internal.execute.ISetupInformation;

/**
 * Programs which wish to be called from our system need to extend this abstract
 * class.
 * 
 * This class will provide a default setup for you.
 * 
 * You need to provide a 0-point (preferably empty) constructor.
 * 
 * System.err lets you print out normal text, do not use System.in or
 * System.out, they are used for IPC with the master framework.
 * 
 * @author tora
 */

public abstract class AbstractProgram {

    protected AbstractProgram() {
        /* The constructor */
    }

    /**
     * This is the entry point for the program. You should implement your
     * program stating from here.
     * 
     * @param args
     *            The arguments provided to this program
     * @param inputs
     *            The input pipes that are tied to this program.
     * @param outputs
     *            The output pipes tied to this program.
     */
    public abstract void run(IArgumentInstance args,
            Map<String, IInputPipe> inputs, Map<String, IOutputPipe> outputs);

    /**
     * This gets executed as its 'main'
     * 
     * @param args
     *            The string arguments to the program that are used to configure
     *            the IO function and other things.
     * 
     * args 0 is the class to load.
     * @throws ClassNotFoundException
     *             if the class isn't found
     * @throws IllegalArgumentException
     *             reflection error if things go wrong
     * @throws IOException
     * @throws ArrayIndexOutOfBoundsException
     *             if args are not set properly
     */
    public static final void main(final String[] args)
            throws ClassNotFoundException, IllegalArgumentException,
            IOException {

        final ISetupInformation isi;
        final String classToLoad = args[0];

        // next up: communicate some pipey stuff

        //ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(
        //        System.in));
        ObjectInputStream ois = new ObjectInputStream((
                System.in));
        // Get the setup information
        Object o = ois.readObject();
        if (ISetupInformation.class.isAssignableFrom(o.getClass())) {
            isi = (ISetupInformation) o;
        } else {
            throw new AssertionError("Precondition failed on rx");
        }

        // set up the pipes:
        Map<String, ClientSidePipe> inputPipes = getPipes(isi
                .getInputPipeNames());
        Map<String, ClientSidePipe> outputPipes = getPipes(isi
                .getOutputPipeNames());

        Map<String, IInputPipe> ins = getInputPipes(inputPipes);
        Map<String, IOutputPipe> outs = getOutputPipes(outputPipes);

        Map<String, IInputPipe> ins2 = getInputPipes(outputPipes);
        Map<String, IOutputPipe> outs2 = getOutputPipes(inputPipes);

        AbstractProgram.inputPipes = ins;
        AbstractProgram.outputPipes = outs;

        PipeManagerProvider.getPipeManager().managePipes(ois, System.out, ins2,
                outs2, null, null, null, null);

        Class prog = AbstractProgram.class.getClassLoader().loadClass(
                classToLoad);

        Constructor[] consts = prog.getConstructors();

        for (Constructor c : consts) {
            if (c.getParameterTypes().length == 0) {

                try {
                    // So now we have the instance of the thing to run
                    ((AbstractProgram) c.newInstance(new Object[] {})).run(isi
                            .getArguments(), ins, outs);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.exit(-1);
                } catch (Error e) {
                    e.printStackTrace();
                    System.exit(-2);
                }

                exit(0);
            }
        }

    }

    private static Map<String, IInputPipe> inputPipes;

    private static Map<String, IOutputPipe> outputPipes;

    private static AtomicBoolean exiting = new AtomicBoolean(false);

    /**
     * If programs wish to exit they should use this method in preference to
     * System.exit as it will wait until all unwritten data has propogated
     * across pipes. This method does return - to stop deadlocks if called from
     * within the pipe handling threads.
     * 
     * @param code
     */
    public final static void exit(final int code) {

        if (exiting.getAndSet(true)) {
            return;
        }

        new Thread() {
            public void run() {
                for (String key : new HashSet<String>(inputPipes.keySet())) {
                    inputPipes.get(key).close();
                }

                for (String key : new HashSet<String>(outputPipes.keySet())) {
                    outputPipes.get(key).close();
                }

                PipeManagerProvider.getPipeManager().waitTillAllDone();

                System.exit(code);
            }
        }.start();
    }

    private static Map<String, ClientSidePipe> getPipes(final Set<String> names) {

        final Map<String, ClientSidePipe> map = new HashMap<String, ClientSidePipe>();

        for (String s : names) {
            map.put(s, new ClientSidePipe());
        }

        return map;
    }

    /*
     * Convert ClientSidePipe round to IInputPipe in the map
     */
    private static Map<String, IInputPipe> getInputPipes(
            final Map<String, ClientSidePipe> inputs) {
        final Map<String, IInputPipe> out = new HashMap<String, IInputPipe>();

        for (Map.Entry<String, ClientSidePipe> me : inputs.entrySet()) {
            out.put(me.getKey(), me.getValue().getInputPipe());
        }

        return out;

    }

    /*
     * Convert CSP into IOP
     */
    private static Map<String, IOutputPipe> getOutputPipes(
            final Map<String, ClientSidePipe> outputs) {

        final Map<String, IOutputPipe> out = new HashMap<String, IOutputPipe>();

        for (Map.Entry<String, ClientSidePipe> me : outputs.entrySet()) {
            out.put(me.getKey(), me.getValue().getOutputPipe());
        }

        return out;

    }
}
