package org.jgroups.debug;

import org.jgroups.Address;
import org.jgroups.ChannelException;
import org.jgroups.Event;
import org.jgroups.Message;
import org.jgroups.View;
import org.jgroups.protocols.TP;
import org.jgroups.stack.Protocol;
import org.jgroups.stack.ProtocolStack;
import org.jgroups.util.Queue;
import org.jgroups.util.QueueClosedException;
import org.jgroups.util.TimeScheduler;

import java.util.HashMap;
import java.util.Iterator;

/**
 * Tests one or more protocols independently. Look at org.jgroups.tests.FCTest for an example of how to use it.
 * @author Bela Ban
 * @version $Id: Simulator.java,v 1.11.2.1 2008/05/22 13:23:10 belaban Exp $
 */
public class Simulator {
    private Protocol[] protStack=null;
    private ProtocolAdapter ad=new ProtocolAdapter();
    ProtocolStack prot_stack=null;
    private Receiver r=null;
    private Protocol top=null, bottom=null;
    private Queue send_queue=new Queue();
    private Thread send_thread;
    private Queue recv_queue=new Queue();
    private Thread recv_thread;


    /** HashMap from Address to Simulator. */
    private final HashMap addrTable=new HashMap();
    private Address local_addr=null;
    private View view;

    public interface Receiver {
        void receive(Event evt);
    }


    public void setProtocolStack(Protocol[] stack) {
        this.protStack=stack;
        this.protStack[0].setUpProtocol(ad);
        this.protStack[this.protStack.length-1].setDownProtocol(ad);
        top=protStack[0];
        bottom=this.protStack[this.protStack.length-1];

        try {
            prot_stack=new ProtocolStack();
        } catch (ChannelException e) {           
            e.printStackTrace();
        }

        if(protStack.length > 1) {
            for(int i=0; i < protStack.length; i++) {
                Protocol p1=protStack[i];
                p1.setProtocolStack(prot_stack);
                Protocol p2=i+1 >= protStack.length? null : protStack[i+1];
                if(p2 != null) {
                    p1.setDownProtocol(p2);
                    p2.setUpProtocol(p1);
                }
            }
        }
    }

    public String dumpStats() {
        StringBuilder sb=new StringBuilder();
        for(int i=0; i < protStack.length; i++) {
            Protocol p1=protStack[i];
            sb.append(p1.getName()).append(":\n").append(p1.dumpStats()).append("\n");
        }
        return sb.toString();
    }

    public void addMember(Address addr) {
        addMember(addr, this);
    }

    public void addMember(Address addr, Simulator s) {
        addrTable.put(addr, s);
    }

    public void setLocalAddress(Address addr) {
        this.local_addr=addr;
    }

    public void setView(View v) {
        this.view=v;
    }

    public void setReceiver(Receiver r) {
        this.r=r;
    }

    public Object send(Event evt) {
        return top.down(evt);
    }

    public void receive(Event evt) {
        try {
            Event copy;
            if(evt.getType() == Event.MSG && evt.getArg() != null) {
                copy=new Event(Event.MSG, ((Message)evt.getArg()).copy());
            }
            else
                copy=evt;

            recv_queue.add(copy);
        }
        catch(QueueClosedException e) {
        }
    }

    public void start() throws Exception {
       if(local_addr == null)
            throw new Exception("local_addr has to be non-null");
        if(protStack == null)
            throw new Exception("protocol stack is null");

        bottom.up(new Event(Event.SET_LOCAL_ADDRESS, local_addr));
        if(view != null) {
            Event view_evt=new Event(Event.VIEW_CHANGE, view);
            bottom.up(view_evt);
            top.down(view_evt);
        }

        for(int i=0; i < protStack.length; i++) {
            Protocol p=protStack[i];
            p.setProtocolStack(prot_stack);
        }

        for(int i=0; i < protStack.length; i++) {
            Protocol p=protStack[i];
            p.init();
        }

        for(int i=0; i < protStack.length; i++) {
            Protocol p=protStack[i];
            p.start();
        }


        send_thread=new Thread() {
            public void run() {
                Event evt;
                while(send_thread != null) {
                    try {
                        evt=(Event)send_queue.remove();
                        if(evt.getType() == Event.MSG) {
                            Message msg=(Message)evt.getArg();
                            Address dst=msg.getDest();
                            if(msg.getSrc() == null)
                                ((Message)evt.getArg()).setSrc(local_addr);
                            Simulator s;
                            if(dst == null) {
                                for(Iterator it=addrTable.values().iterator(); it.hasNext();) {
                                    s=(Simulator)it.next();
                                    s.receive(evt);
                                }
                            }
                            else {
                                s=(Simulator)addrTable.get(dst);
                                if(s != null)
                                    s.receive(evt);
                            }
                        }
                    }
                    catch(QueueClosedException e) {
                        send_thread=null;
                        break;
                    }
                }
            }
        };
        send_thread.start();


        recv_thread=new Thread() {
            public void run() {
                Event evt;
                while(recv_thread != null) {
                    try {
                        evt=(Event)recv_queue.remove();
                        bottom.up(evt);
                    }
                    catch(QueueClosedException e) {
                        recv_thread=null;
                        break;
                    }
                }
            }
        };
        recv_thread.start();
    }

    public void stop() {
        recv_thread=null;
        recv_queue.close(false);
        send_thread=null;
        send_queue.close(false);
        if(ad != null) {
            try {
                ad.getTimer().stop();
    }
            catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }




    class ProtocolAdapter extends TP {

        ProtocolAdapter() {
            timer=new TimeScheduler();
        }

        public TimeScheduler getTimer() {
            return timer;
        }

        public void setTimer(TimeScheduler timer) {
            this.timer=timer;
        }

        public String getName() {
            return "ProtocolAdapter";
        }

        public void sendToAllMembers(byte[] data, int offset, int length) throws Exception {
        }

        public void sendToSingleMember(Address dest, byte[] data, int offset, int length) throws Exception {
        }

        public String getInfo() {
            return null;
        }

        public void postUnmarshalling(Message msg, Address dest, Address src, boolean multicast) {
        }

        public void postUnmarshallingList(Message msg, Address dest, boolean multicast) {
        }

        public void init() throws Exception {
            super.init();
        }

        public Object up(Event evt) {
            if(r != null)
                r.receive(evt);
            return null;
        }

        /** send to unicast or multicast destination */
        public Object down(Event evt) {
            try {
                send_queue.add(evt);
            }
            catch(QueueClosedException e) {
            }
            return null;
        }
    }



}
