// $Id: Configurator.java,v 1.16.6.2 2009/04/06 16:45:24 rachmatowicz Exp $

package org.jgroups.stack;


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jgroups.Event;
import org.jgroups.util.Util;

import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import java.io.IOException;
import java.io.PushbackReader;
import java.io.StringReader;
import java.io.Reader;


/**
 * The task if this class is to setup and configure the protocol stack. A string describing
 * the desired setup, which is both the layering and the configuration of each layer, is
 * given to the configurator which creates and configures the protocol stack and returns
 * a reference to the top layer (Protocol).<p>
 * Future functionality will include the capability to dynamically modify the layering
 * of the protocol stack and the properties of each layer.
 * @author Bela Ban
 */
public class Configurator {

     protected final Log log=LogFactory.getLog(getClass());


    /**
     * The configuration string has a number of entries, separated by a ':' (colon).
     * Each entry consists of the name of the protocol, followed by an optional configuration
     * of that protocol. The configuration is enclosed in parentheses, and contains entries
     * which are name/value pairs connected with an assignment sign (=) and separated by
     * a semicolon.
     * <pre>UDP(in_port=5555;out_port=4445):FRAG(frag_size=1024)</pre><p>
     * The <em>first</em> entry defines the <em>bottommost</em> layer, the string is parsed
     * left to right and the protocol stack constructed bottom up. Example: the string
     * "UDP(in_port=5555):FRAG(frag_size=32000):DEBUG" results is the following stack:<pre>
     *
     *   -----------------------
     *  | DEBUG                 |
     *  |-----------------------|
     *  | FRAG frag_size=32000  |
     *  |-----------------------|
     *  | UDP in_port=32000     |
     *   -----------------------
     * </pre>
     */
    public Protocol setupProtocolStack(String configuration, ProtocolStack st) throws Exception {
        Protocol protocol_stack=null;
        Vector protocol_configs;
        Vector protocols;

        protocol_configs=parseConfigurations(configuration);
        protocols=createProtocols(protocol_configs, st);
        if(protocols == null)
            return null;
        protocol_stack=connectProtocols(protocols);
        return protocol_stack;
    }


    public void initProtocolStack(Protocol bottom_prot) throws Exception {
        while(bottom_prot != null) {
            bottom_prot.init();
            bottom_prot=bottom_prot.getUpProtocol();
        }
    }


    public void startProtocolStack(Protocol bottom_prot) {
        while(bottom_prot != null) {
            bottom_prot.startDownHandler();
            bottom_prot.startUpHandler();
            bottom_prot=bottom_prot.getUpProtocol();
        }
    }


    public void stopProtocolStack(Protocol start_prot) {
        while(start_prot != null) {
            start_prot.stopInternal();
            start_prot.destroy();
            start_prot=start_prot.getDownProtocol();
        }
    }


    public Protocol findProtocol(Protocol prot_stack, String name) {
        String s;
        Protocol curr_prot=prot_stack;

        while(true) {
            s=curr_prot.getName();
            if(s == null)
                continue;
            if(s.equals(name))
                return curr_prot;
            curr_prot=curr_prot.getDownProtocol();
            if(curr_prot == null)
                break;
        }
        return null;
    }


    public Protocol getBottommostProtocol(Protocol prot_stack) {
        Protocol tmp=null, curr_prot=prot_stack;

        while(true) {
            if((tmp=curr_prot.getDownProtocol()) == null)
                break;
            curr_prot=tmp;
        }
        return curr_prot;
    }


    /**
     * Creates a new protocol given the protocol specification. Initializes the properties and starts the
     * up and down handler threads.
     * @param prot_spec The specification of the protocol. Same convention as for specifying a protocol stack.
     *                  An exception will be thrown if the class cannot be created. Example:
     *                  <pre>"VERIFY_SUSPECT(timeout=1500)"</pre> Note that no colons (:) have to be
     *                  specified
     * @param stack The protocol stack
     * @return Protocol The newly created protocol
     * @exception Exception Will be thrown when the new protocol cannot be created
     */
    public Protocol createProtocol(String prot_spec, ProtocolStack stack) throws Exception {
        ProtocolConfiguration config;
        Protocol prot;

        if(prot_spec == null) throw new Exception("Configurator.createProtocol(): prot_spec is null");

        // parse the configuration for this protocol
        config=new ProtocolConfiguration(prot_spec);

        // create an instance of the protocol class and configure it
        prot=config.createLayer(stack);
        prot.init();

        // start the handler threads (unless down_thread or up_thread are set to false)
        prot.startDownHandler();
        prot.startUpHandler();

        return prot;
    }


    /**
     * Inserts an already created (and initialized) protocol into the protocol list. Sets the links
     * to the protocols above and below correctly and adjusts the linked list of protocols accordingly.
     * @param prot  The protocol to be inserted. Before insertion, a sanity check will ensure that none
     *              of the existing protocols have the same name as the new protocol.
     * @param position Where to place the protocol with respect to the neighbor_prot (ABOVE, BELOW)
     * @param neighbor_prot The name of the neighbor protocol. An exception will be thrown if this name
     *                      is not found
     * @param stack The protocol stack
     * @exception Exception Will be thrown when the new protocol cannot be created, or inserted.
     */
    public void insertProtocol(Protocol prot, int position, String neighbor_prot, ProtocolStack stack) throws Exception {
        if(neighbor_prot == null) throw new Exception("Configurator.insertProtocol(): neighbor_prot is null");
        if(position != ProtocolStack.ABOVE && position != ProtocolStack.BELOW)
            throw new Exception("Configurator.insertProtocol(): position has to be ABOVE or BELOW");


        // find the neighbors below and above



        // connect to the protocol layer below and above


    }


    /**
     * Removes a protocol from the stack. Stops the protocol and readjusts the linked lists of
     * protocols.
     * @param prot_name The name of the protocol. Since all protocol names in a stack have to be unique
     *                  (otherwise the stack won't be created), the name refers to just 1 protocol.
     * @exception Exception Thrown if the protocol cannot be stopped correctly.
     */
    public void removeProtocol(String prot_name) throws Exception {
    }



    /* ------------------------------- Private Methods ------------------------------------- */


    /**
     * Creates a protocol stack by iterating through the protocol list and connecting
     * adjacent layers. The list starts with the topmost layer and has the bottommost
     * layer at the tail. When all layers are connected the algorithms traverses the list
     * once more to call startInternal() on each layer.
     * @param protocol_list List of Protocol elements (from top to bottom)
     * @return Protocol stack
     */
    private Protocol connectProtocols(Vector protocol_list) {
        Protocol current_layer=null, next_layer=null;

        for(int i=0; i < protocol_list.size(); i++) {
            current_layer=(Protocol)protocol_list.elementAt(i);
            if(i + 1 >= protocol_list.size())
                break;
            next_layer=(Protocol)protocol_list.elementAt(i + 1);
            current_layer.setUpProtocol(next_layer);
            next_layer.setDownProtocol(current_layer);
        }
        return current_layer;
    }


    /**
     * Get a string of the form "P1(config_str1):P2:P3(config_str3)" and return
     * ProtocolConfigurations for it. That means, parse "P1(config_str1)", "P2" and
     * "P3(config_str3)"
     * @param config_str Configuration string
     * @return Vector of ProtocolConfigurations
     */
    public Vector parseComponentStrings(String config_str, String delimiter) {
        Vector retval=new Vector();
        StringTokenizer tok;
        String token;

        /*tok=new StringTokenizer(config_str, delimiter, false);
        while(tok.hasMoreTokens()) {
            token=tok.nextToken();
            retval.addElement(token);
        }*/
        // change suggested by gwoolsey
        tok=new StringTokenizer(config_str, delimiter, false);
        while(tok.hasMoreTokens()) {
            token=tok.nextToken();
            while(token.endsWith("\\"))
                token=token.substring(0, token.length() - 1) + delimiter + tok.nextToken();
            retval.addElement(token);
        }

        return retval;
    }


    /**
     * Return a number of ProtocolConfigurations in a vector
     * @param configuration protocol-stack configuration string
     * @return Vector of ProtocolConfigurations
     */
    public Vector parseConfigurations(String configuration) throws Exception {
        Vector retval=new Vector();
        Vector component_strings=parseProtocols(configuration);
        String component_string;
        ProtocolConfiguration protocol_config;

        if(component_strings == null)
            return null;
        for(int i=0; i < component_strings.size(); i++) {
            component_string=(String)component_strings.elementAt(i);
            protocol_config=new ProtocolConfiguration(component_string);
            retval.addElement(protocol_config);
        }
        return retval;
    }



        /**
     * Get a string of the form "P1(config_str1):P2:P3(config_str3)" and return
     * ProtocolConfigurations for it. That means, parse "P1(config_str1)", "P2" and
     * "P3(config_str3)"
     * @param config_str Configuration string
     * @return Vector of strings
     */
    private Vector parseProtocols(String config_str) throws IOException {
        Vector retval=new Vector();
        PushbackReader reader=new PushbackReader(new StringReader(config_str));
        int ch;
        StringBuffer sb;
        boolean running=true;

        while(running) {
            String protocol_name=readWord(reader);
            sb=new StringBuffer();
            sb.append(protocol_name);

            ch=read(reader);
            if(ch == -1) {
                retval.add(sb.toString());
                break;
            }

            if(ch == ':') {  // no attrs defined
                retval.add(sb.toString());
                continue;
            }

            if(ch == '(') { // more attrs defined
                reader.unread(ch);
                String attrs=readUntil(reader, ')');
                sb.append(attrs);
                retval.add(sb.toString());
            }
            else {
                retval.add(sb.toString());
            }

            while(true) {
                ch=read(reader);
                if(ch == ':') {
                    break;
                }
                if(ch == -1) {
                    running=false;
                    break;
                }
            }
        }
        reader.close();

        return retval;
    }


    private static int read(Reader reader) throws IOException {
        int ch=-1;
        while((ch=reader.read()) != -1) {
            if(!Character.isWhitespace((char)ch))
                return ch;
        }
        return ch;
    }




    private static String readUntil(Reader reader, char c) throws IOException {
        StringBuffer sb=new StringBuffer();
        int ch;
        while((ch=read(reader)) != -1) {
            sb.append((char)ch);
            if(ch == c)
                break;
        }
        return sb.toString();
    }

    private static String readWord(PushbackReader reader) throws IOException {
        StringBuffer sb=new StringBuffer();
        int ch;

        while((ch=read(reader)) != -1) {
            if(Character.isLetterOrDigit((char)ch) || ch == '_' || ch == '.' || ch == '$') {
                sb.append((char)ch);
            }
            else {
                reader.unread(ch);
                break;
            }
        }

        return sb.toString();
    }



    /**
     * Takes vector of ProtocolConfigurations, iterates through it, creates Protocol for
     * each ProtocolConfiguration and returns all Protocols in a vector.
     * @param protocol_configs Vector of ProtocolConfigurations
     * @param stack The protocol stack
     * @return Vector of Protocols
     */
    private Vector createProtocols(Vector protocol_configs, ProtocolStack stack) throws Exception {
        Vector retval=new Vector();
        ProtocolConfiguration protocol_config;
        Protocol layer;

        for(int i=0; i < protocol_configs.size(); i++) {
            protocol_config=(ProtocolConfiguration)protocol_configs.elementAt(i);
            layer=protocol_config.createLayer(stack);
            if(layer == null)
                return null;
            retval.addElement(layer);
        }
        sanityCheck(retval);
        return retval;
    }


    /**
     Throws an exception if sanity check fails. Possible sanity check is uniqueness of all protocol
     names.
     */
    public void sanityCheck(Vector protocols) throws Exception {
        Vector names=new Vector();
        Protocol prot;
        String name;
        ProtocolReq req;
        Vector req_list=new Vector();
        int evt_type;

        // Checks for unique names
        for(int i=0; i < protocols.size(); i++) {
            prot=(Protocol)protocols.elementAt(i);
            name=prot.getName();
            for(int j=0; j < names.size(); j++) {
                if(name.equals(names.elementAt(j))) {
                    throw new Exception("Configurator.sanityCheck(): protocol name " + name +
                                        " has been used more than once; protocol names have to be unique !");
                }
            }
            names.addElement(name);
        }


        // Checks whether all requirements of all layers are met
        for(int i=0; i < protocols.size(); i++) {
            prot=(Protocol)protocols.elementAt(i);
            req=new ProtocolReq(prot.getName());
            req.up_reqs=prot.requiredUpServices();
            req.down_reqs=prot.requiredDownServices();
            req.up_provides=prot.providedUpServices();
            req.down_provides=prot.providedDownServices();
            req_list.addElement(req);
        }


        for(int i=0; i < req_list.size(); i++) {
            req=(ProtocolReq)req_list.elementAt(i);

            // check whether layers above this one provide corresponding down services
            if(req.up_reqs != null) {
                for(int j=0; j < req.up_reqs.size(); j++) {
                    evt_type=((Integer)req.up_reqs.elementAt(j)).intValue();

                    if(!providesDownServices(i, req_list, evt_type)) {
                        throw new Exception("Configurator.sanityCheck(): event " +
                                            Event.type2String(evt_type) + " is required by " +
                                            req.name + ", but not provided by any of the layers above");
                    }
                }
            }

            // check whether layers below this one provide corresponding up services
            if(req.down_reqs != null) {  // check whether layers above this one provide up_reqs
                for(int j=0; j < req.down_reqs.size(); j++) {
                    evt_type=((Integer)req.down_reqs.elementAt(j)).intValue();

                    if(!providesUpServices(i, req_list, evt_type)) {
                        throw new Exception("Configurator.sanityCheck(): event " +
                                            Event.type2String(evt_type) + " is required by " +
                                            req.name + ", but not provided by any of the layers below");
                    }
                }
            }

        }
    }


    /** Check whether any of the protocols 'below' end_index provide evt_type */
    boolean providesUpServices(int end_index, Vector req_list, int evt_type) {
        ProtocolReq req;

        for(int i=0; i < end_index; i++) {
            req=(ProtocolReq)req_list.elementAt(i);
            if(req.providesUpService(evt_type))
                return true;
        }
        return false;
    }


    /** Checks whether any of the protocols 'above' start_index provide evt_type */
    boolean providesDownServices(int start_index, Vector req_list, int evt_type) {
        ProtocolReq req;

        for(int i=start_index; i < req_list.size(); i++) {
            req=(ProtocolReq)req_list.elementAt(i);
            if(req.providesDownService(evt_type))
                return true;
        }
        return false;
    }



    /* --------------------------- End of Private Methods ---------------------------------- */





    private static class ProtocolReq {
        Vector up_reqs=null;
        Vector down_reqs=null;
        Vector up_provides=null;
        Vector down_provides=null;
        String name=null;

        ProtocolReq(String name) {
            this.name=name;
        }


        boolean providesUpService(int evt_type) {
            int type;

            if(up_provides != null) {
                for(int i=0; i < up_provides.size(); i++) {
                    type=((Integer)up_provides.elementAt(i)).intValue();
                    if(type == evt_type)
                        return true;
                }
            }
            return false;
        }

        boolean providesDownService(int evt_type) {
            int type;

            if(down_provides != null) {
                for(int i=0; i < down_provides.size(); i++) {
                    type=((Integer)down_provides.elementAt(i)).intValue();
                    if(type == evt_type)
                        return true;
                }
            }
            return false;
        }


        public String toString() {
            StringBuffer ret=new StringBuffer();
            ret.append('\n' + name + ':');
            if(up_reqs != null)
                ret.append("\nRequires from above: " + printUpReqs());

            if(down_reqs != null)
                ret.append("\nRequires from below: " + printDownReqs());

            if(up_provides != null)
                ret.append("\nProvides to above: " + printUpProvides());

            if(down_provides != null)
                ret.append("\nProvides to below: ").append(printDownProvides());
            return ret.toString();
        }


        String printUpReqs() {
            StringBuffer ret=new StringBuffer("[");
            if(up_reqs != null) {
                for(int i=0; i < up_reqs.size(); i++) {
                    ret.append(Event.type2String(((Integer)up_reqs.elementAt(i)).intValue()) + ' ');
                }
            }
            return ret.toString() + ']';
        }

        String printDownReqs() {
            StringBuffer ret=new StringBuffer("[");
            if(down_reqs != null) {
                for(int i=0; i < down_reqs.size(); i++) {
                    ret.append(Event.type2String(((Integer)down_reqs.elementAt(i)).intValue()) + ' ');
                }
            }
            return ret.toString() + ']';
        }


        String printUpProvides() {
            StringBuffer ret=new StringBuffer("[");
            if(up_provides != null) {
                for(int i=0; i < up_provides.size(); i++) {
                    ret.append(Event.type2String(((Integer)up_provides.elementAt(i)).intValue()) + ' ');
                }
            }
            return ret.toString() + ']';
        }

        String printDownProvides() {
            StringBuffer ret=new StringBuffer("[");
            if(down_provides != null) {
                for(int i=0; i < down_provides.size(); i++)
                    ret.append(Event.type2String(((Integer)down_provides.elementAt(i)).intValue()) +
                               ' ');
            }
            return ret.toString() + ']';
        }

    }


    /**
     * Parses and encapsulates the specification for 1 protocol of the protocol stack, e.g.
     * <code>UNICAST(timeout=5000)</code>
     */
    public class ProtocolConfiguration {
        private String protocol_name=null;
        private String properties_str=null;
        private final Properties properties=new Properties();
        private static final String protocol_prefix="org.jgroups.protocols";


        /**
         * Creates a new ProtocolConfiguration.
         * @param config_str The configuration specification for the protocol, e.g.
         *                   <pre>VERIFY_SUSPECT(timeout=1500)</pre>
         */
        public ProtocolConfiguration(String config_str) throws Exception {
            setContents(config_str);
        }

        public String getProtocolName() {
            return protocol_name;
        }

        public Properties getProperties() {
            return properties;
        }


     
        void setContents(String config_str) throws Exception {
            int index=config_str.indexOf('(');  // e.g. "UDP(in_port=3333)"
            int end_index=config_str.lastIndexOf(')');

            if(index == -1) {
                protocol_name=config_str;
                properties_str="";
            }
            else {
                if(end_index == -1) {
                    throw new Exception("Configurator.ProtocolConfiguration(): closing ')' " +
                            "not found in " + config_str + ": properties cannot be set !");
                }
                else {
                    properties_str=config_str.substring(index + 1, end_index);
                    protocol_name=config_str.substring(0, index);
                }
            }

            /* "in_port=5555;out_port=6666" */
            if(properties_str.length() > 0) {
                String[] components=properties_str.split(";");
                for(int i=0; i < components.length; i++) {
                // for(String property : components) {
                    String property=components[i];
                    String name, value;
                    index=property.indexOf('=');
                    if(index == -1) {
                        throw new Exception("Configurator.ProtocolConfiguration(): '=' not found in " + property
                                + " of "
                                + protocol_name);
                    }
                    name=property.substring(0, index);
                    value=property.substring(index + 1, property.length());
                    properties.put(name, value);
                }
            }
        }



        private Protocol createLayer(ProtocolStack prot_stack) throws Exception {
            Protocol retval=null;
            if(protocol_name == null)
                return null;

            String defaultProtocolName=protocol_prefix + '.' + protocol_name;
            Class clazz=null;

            try {
                clazz=Util.loadClass(defaultProtocolName, this.getClass());
            }
            catch(ClassNotFoundException e) {
            }

            if(clazz == null) {
                try {
                    clazz=Util.loadClass(protocol_name, this.getClass());
                }
                catch(ClassNotFoundException e) {
                }
                if(clazz == null) {
                    throw new Exception("unable to load class for protocol " + protocol_name +
                            " (either as an absolute - " + protocol_name + " - or relative - " +
                            defaultProtocolName + " - package name)!");
                }
            }

            try {
                retval=(Protocol)clazz.newInstance();

                if(retval == null)
                    throw new Exception("creation of instance for protocol " + protocol_name + "failed !");
                retval.setProtocolStack(prot_stack);
                if(properties != null)
                    if(!retval.setPropertiesInternal(properties))
                        return null;
                // retval.init(); // moved to after creation of *all* protocols
            }
            catch(InstantiationException inst_ex) {
                log.error("an instance of " + protocol_name + " could not be created. Please check that it implements" +
                        " interface Protocol and that is has a public empty constructor !");
                throw inst_ex;
            }
            return retval;
        }


        public String toString() {
            StringBuffer retval=new StringBuffer();
            retval.append("Protocol: ");
            if(protocol_name == null)
                retval.append("<unknown>");
            else
                retval.append(protocol_name);
            if(properties != null)
                retval.append("(" + properties + ')');
            return retval.toString();
        }
    }


    public static void main(String args[]) {
        if(args.length != 1) {
            System.err.println("Configurator <string>");
            System.exit(0);
        }
        String config_str=args[0];
        Configurator conf=new Configurator();
        Vector protocol_configs;
        Vector protocols=null;
        Protocol protocol_stack;


        try {
            protocol_configs=conf.parseConfigurations(config_str);
            protocols=conf.createProtocols(protocol_configs, null);
            if(protocols == null)
                return;
            protocol_stack=conf.connectProtocols(protocols);
            Thread.sleep(3000);
            conf.stopProtocolStack(protocol_stack);
            // conf.stopProtocolStackInternal(protocol_stack);
        }
        catch(Exception e) {
            System.err.println(e);
        }

        System.err.println(protocols);
    }


}


