/*
 * $Id: ExecutableChannels.java 4078 2012-07-28 12:40:12Z kredel $
 */

package edu.jas.util;


import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;


// import edu.unima.ky.parallel.ChannelFactory;
// import edu.unima.ky.parallel.SocketChannel;

/**
 * ExecutableChannels used to receive and execute classes.
 * @author Heinz Kredel
 */


public class ExecutableChannels {


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


    /**
     * default port.
     */
    protected final static int DEFAULT_PORT = 7114; //ChannelFactory.DEFAULT_PORT;


    /**
     * default machine file.
     */
    protected final static String DEFAULT_MFILE = "examples/machines.test";


    protected final ChannelFactory cf;


    protected SocketChannel[] channels = null;


    protected String[] servers = null;


    protected int[] ports = null;


    /**
     * Internal constructor.
     */
    protected ExecutableChannels() {
        cf = new ChannelFactory();
        cf.init();
    }


    /**
     * Constructor from array of server:port strings.
     * @param srvs A String array.
     */
    public ExecutableChannels(String[] srvs) {
        this();
        if (srvs == null) {
            return;
        }
        servers = new String[srvs.length];
        ports = new int[srvs.length];
        for (int i = 0; i < srvs.length; i++) {
            setServerPort(i, srvs[i]);
        }
    }


    /**
     * Constructor from machine file.
     * @param mfile
     * @throws FileNotFoundException.
     */
    public ExecutableChannels(String mfile) throws FileNotFoundException {
        this();
        if (mfile == null || mfile.length() == 0) {
            mfile = DEFAULT_MFILE;
        }
        InputStreamReader isr = new InputStreamReader(new FileInputStream(mfile),Charset.forName("UTF8"));
        BufferedReader in = new BufferedReader(isr);
        String line = null;
        List<String> list = new ArrayList<String>();
        int x;
        try {
            while (true) {
                if (!in.ready()) {
                    break;
                }
                line = in.readLine();
                if (line == null) {
                    break;
                }
                x = line.indexOf("#");
                if (x >= 0) {
                    line = line.substring(0, x);
                }
                line = line.trim();
                if (line.length() == 0) {
                    continue;
                }
                list.add(line);
            }
        } catch (IOException ignored) {
        } finally {
            try {
                in.close();
            } catch (IOException ignore) {
            }
        }
        logger.debug("list.size() in " + mfile + " = " + list.size());
        if (list.size() == 0) {
            return;
        }
        servers = new String[list.size()];
        ports = new int[list.size()];
        for (int i = 0; i < servers.length; i++) {
            setServerPort(i, list.get(i));
        }
    }


    /* 
     * internal method
     */
    protected void setServerPort(int i, String srv) {
        int x = srv.indexOf(":");
        ports[i] = DEFAULT_PORT;
        if (x < 0) {
            servers[i] = srv;
        } else {
            servers[i] = srv.substring(0, x);
            String p = srv.substring(x + 1, srv.length());
            try {
                ports[i] = Integer.parseInt(p);
            } catch (NumberFormatException ignored) {
            }
        }
    }


    /**
     * String representation.
     */
    @Override
    public String toString() {
        StringBuffer s = new StringBuffer("ExecutableChannels(");
        if (servers != null) {
            for (int i = 0; i < servers.length; i++) {
                s.append(servers[i] + ":" + ports[i]);
                if (i < servers.length - 1) {
                    s.append(" ");
                }
            }
        }
        if (channels != null) {
            s.append(" channels = ");
            for (int i = 0; i < channels.length; i++) {
                s.append(channels[i]);
                if (i < channels.length - 1) {
                    s.append(" ");
                }
            }
        }
        s.append(")");
        return s.toString();
    }


    /**
     * number of servers.
     */
    public int numServers() {
        if (servers != null) {
            return servers.length;
        }
        return -1;
    }


    /**
     * get master host.
     */
    public String getMasterHost() {
        if (servers != null && servers.length > 0) {
            return servers[0];
        }
        return null;
    }


    /**
     * get master port.
     */
    public int getMasterPort() {
        if (ports != null && ports.length > 0) {
            return ports[0];
        }
        return 0;
    }


    /**
     * number of channels.
     */
    public int numChannels() {
        if (channels != null) {
            return channels.length;
        }
        return -1;
    }


    /**
     * open, setup of SocketChannels.
     * @throws IOException.
     */
    public void open() throws IOException {
        logger.debug("opening " + servers.length + " channels");
        if (servers.length <= 1) {
            throw new IOException("to few servers");
        }
        channels = new SocketChannel[servers.length - 1];
        for (int i = 1; i < servers.length; i++) {
            channels[i - 1] = cf.getChannel(servers[i], ports[i]);
        }
    }


    /**
     * open, setup of SocketChannels. If nc &gt; servers.length open in round
     * robin fashion.
     * @param nc number of channels to open.
     * @throws IOException.
     */
    public void open(int nc) throws IOException {
        logger.debug("opening " + nc + " channels");
        if (servers.length <= 1) {
            throw new IOException("to few servers");
        }
        channels = new SocketChannel[nc];
        int j = 1; // 0 is master
        for (int i = 0; i < channels.length; i++) {
            if (j >= servers.length) { // modulo #servers
                j = 1;
            }
            channels[i] = cf.getChannel(servers[j], ports[j]);
            j++;
        }
    }


    /**
     * close all channels and ChannelFactory.
     */
    public void close() {
        logger.debug("closing ExecutableChannels");
        if (cf != null) {
            cf.terminate();
        }
        if (channels != null) {
            for (int i = 0; i < channels.length; i++) {
                if (channels[i] != null) {
                    try {
                        channels[i].send(ExecutableServer.STOP);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        channels[i].close();
                    }
                    channels[i] = null;
                }
            }
            channels = null;
        }
        logger.debug("ExecuteChannels closed");
    }


    /**
     * getChannel.
     * @param i channel number.
     */
    public SocketChannel getChannel(int i) {
        if (channels != null && 0 <= i && i < channels.length) {
            return channels[i];
        }
        return null;
    }


    /**
     * getChannels.
     */
    /*package*/SocketChannel[] getChannels() {
        return channels;
    }


    /**
     * send on channel i.
     * @param i channel number.
     * @param o object to send.
     */
    public void send(int i, Object o) throws IOException {
        if (channels != null && 0 <= i && i < channels.length) {
            channels[i].send(o);
        }
    }


    /**
     * recieve on channel i.
     * @param i channel number.
     * @return object recieved.
     */
    public Object receive(int i) throws IOException, ClassNotFoundException {
        if (channels != null && 0 <= i && i < channels.length) {
            return channels[i].receive();
        }
        return null;

    }

}
