/*
Copyright 1990-2001 Sun Microsystems, Inc. All Rights Reserved.

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 OPEN GROUP OR SUN MICROSYSTEMS, INC. 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 EVEN IF
ADVISED IN ADVANCE OF THE POSSIBILITY OF SUCH DAMAGES.


Except as contained in this notice, the names of The Open Group and/or
Sun Microsystems, Inc. shall not be used in advertising or otherwise to
promote the sale, use or other dealings in this Software without prior
written authorization from The Open Group and/or Sun Microsystems,
Inc., as applicable.


X Window System is a trademark of The Open Group

OSF/1, OSF/Motif and Motif are registered trademarks, and OSF, the OSF
logo, LBX, X Window System, and Xinerama are trademarks of the Open
Group. All other trademarks and registered trademarks mentioned herein
are the property of their respective owners. No right, title or
interest in or to any trademark, service mark, logo or trade name of
Sun Microsystems, Inc. or its licensors is granted.

*/

 
package sun.awt.im.iiimp;

import java.net.*;
import java.util.*;
import java.util.jar.*;
import java.io.*;
import java.text.*;
import java.awt.*;
import java.awt.im.*;
import java.awt.event.*;
import java.awt.font.*;
import java.security.*;
import java.text.AttributedCharacterIterator.Attribute;
import com.sun.iiim.*;

class ProtocolDriver extends IIIMComponent implements IIIMProtocol,
				IIIMActionListener, IMProvider {

    private IMServer imServer = null;
    private InputContext inputContext = null;
    
    private static ProtocolDriver pd = null;
    private boolean connected = false;
    
    private String userName = null;
    
    private URL url = null;
    
    private IIIMPURLConnection uc;
    
    private IIIMPInputStream in;
    
    private IIIMPOutputStream out;
 
    private ODClassLoader odLoader = null;

    private int auxIMID = 0;
    private int auxICID = 0;
    private HashMap auxMap;

    private HashSet doSet;
    private HashSet classNameSet;
    private boolean download = true;
    private HashSet aliveIC;
    private int ccdefID = NOID;
    private int manageRuleID = NOID;
    private Locale prevLocale = null;

    static ProtocolDriver getInstance() {
        if (pd == null) {
	    pd = (ProtocolDriver)AccessController.doPrivileged(new PrivilegedAction() {
		public Object run() {
		    try {
			return new ProtocolDriver();
		    } catch(Exception e) {
			return null;
		    }
		}
	    });
        }
        return pd;
    }
    
    //private 
    ProtocolDriver() {
        imServer = new IMServer();
	doSet = new HashSet();
	classNameSet = new HashSet();
	aliveIC = new HashSet();
	if (System.getSecurityManager() == null) {
	    download = false;
	}
	String dl = Manager.getProperty("iiimf.object.download");
	if (dl != null && dl.equals("true")) {
	    download = true;
	}
	if (download) {
	    odLoader = new ODClassLoader();
	    Manager.setLoader(odLoader);
	}
	Manager.setPD(this);
	auxMap = new HashMap();
	
        // First try to connect with iiim server
        try {
            connectIM();
        } catch(IOException e) {
	    if (Manager.DEBUG) {
		e.printStackTrace();
	    }
        }
        pd = this;
    }
    
    boolean isConnected() {
	return connected;
    }

    boolean connect() {
        try {
            uc = (IIIMPURLConnection) url.openConnection();
            
            if(uc.isConnected() == false) {
		try {
		    AccessController.doPrivileged
			(new PrivilegedExceptionAction() {
			    public Object run() throws IOException {
				uc.connect();
				return null;
			    }
			});
		} catch (PrivilegedActionException pe) {
		    throw (IOException)pe.getException();
		}

                if(uc.isConnected() == false) {
                    throw new IOException();
                }
            }
            in = new IIIMPInputStream(this, uc.getInputStream());
            out = new IIIMPOutputStream(uc.getOutputStream());
	} catch(IOException e) {
	    if (Manager.DEBUG) {
		e.printStackTrace();
	    }
            return false;
        }
        return true;
    }
    
  
    synchronized void lostConnect() {
        if (connected == false) {
            return;
        }
	try {
            uc.disconnect();
        } catch(IOException e) {
            connected = false;
        }
        connected = false;
        done();
        uc = null;
        in = null;
        out = null;
        inputContext = null;
        imServer = new IMServer();
    } 
    
    synchronized void send(Protocol protocol) throws IOException {
        protocol.write(out);
        out.flush();
    }
    
    public void dispatchEvent(IIIMEvent e) {
        AWTEvent event = e.getAWTEvent();

        if(event instanceof KeyEvent && inputContext != null) {
            inputContext.dispatchKeyEvent((KeyEvent)event);      
	}
    }

    synchronized void regularReply(int imID, int icID, int opCode) {
        ProtocolData data;
        Protocol reply;
        
        data = new ProtocolData();
        try {
            data.write2(imID);
            data.write2(icID);
        } catch(IOException e) {
	    if (Manager.DEBUG) {
		e.printStackTrace();
	    }
	}
                
        reply = new Protocol(opCode, data);
        try {
            send(reply);                 
        } catch(IOException e) {
	    if (Manager.DEBUG) {
		e.printStackTrace();
	    }
	}
    }
    
    void getReply(int protocolID) throws IOException {
        getReply(0, protocolID);
    }
    
    int nestLevel = 0;
    boolean catchedInNest = false;
    boolean outSideNestExit = false;
    Vector nestVector = new Vector();
    int catchedProtocolInNest = IM_NO_PROTOCOL;

    void getReply(int proto1, int proto2) throws IOException {

	nestLevel++;
	Integer processProto = new Integer(proto2);
	nestVector.add(processProto);
        while(true) {
	    if (outSideNestExit) {
		if (catchedProtocolInNest == proto2) {
		    catchedInNest = false;
		    outSideNestExit = false;
		    break;
		}
	    }

            Protocol protocol1 = new Protocol();
            protocol1.read(in);

            // If it matches one of the expected reply protocol, return
            if((protocol1.getOpCode() == proto1) ) {
                Protocol protocol2 = new Protocol();
                protocol2.read(in);
            
                if(protocol2.getOpCode() != proto2) {
                    // Received an unexpected protocol
                    lostConnect();
                    throw new IIIMProtocolException("Unexpected protocol received: "
                        + protocol1 + " against: " + proto1);            
                }
                driveProtocol(protocol1, protocol2); 
                break;
            }
            else if((protocol1.getOpCode() == proto2)) {
                driveProtocol(protocol1);
                break;
            } else {
                //debug("Addtional protocol = " + protocol1.getOpCode());
                driveProtocol(protocol1);
		if (nestLevel > 1) {
		    int p = protocol1.getOpCode();
		    for (int i = 0; i < nestLevel - 1; i++) { 
			if (p == ((Integer)nestVector.get(i)).intValue()) {
			    catchedProtocolInNest = p; 
			    catchedInNest = true;
			}
		    }
		}
            }
	}
	if (catchedInNest) {
	    outSideNestExit = true;
	}
	nestLevel--;
	nestVector.remove(processProto);
    }

    void driveProtocol(Protocol protocol1, Protocol protocol2 ) throws IOException {
        int op1 = protocol1.getOpCode();
        int op2 = protocol2.getOpCode();
        int imServerID = 0;

        ProtocolData data1 = protocol1.getData();
        ProtocolData data2 = protocol2.getData();
        
        if(op1 == IM_REGISTER_TRIGGER_KEYS && op2 == IM_CONNECT_REPLY) {
  	    IIIMPIMValues ret = new IIIMPIMValues();

            data1.read2(); // skip the input method id
            data1.skipBytes(2);
	    int num_on_keys = (data1.read4() / 16); // on keys
	    
	    if (num_on_keys > 0) {
                ret.onKey = new IIIMPKey[num_on_keys];
                for (int i = 0; i < num_on_keys; i++) {
                    int keycode = data1.read4();
                    char keychar = (char) data1.read4();
                    int modifier = data1.read4();
                    int timestamp = data1.read4();
                    ret.onKey[i] =
                        new IIIMPKey(keycode, keychar, modifier);
                }
            }

            int num_off_keys = (data1.read4() / 16); // off keys
            if (num_off_keys > 0) {
                ret.offKey = new IIIMPKey[num_off_keys];
                for (int i = 0; i < num_off_keys; i++) {
                    int keycode = data1.read4();
                    char keychar = (char) data1.read4();
                    int modifier = data1.read4();
                    int timestamp = data1.read4();
                    ret.offKey[i] = new IIIMPKey(keycode, keychar,modifier);
                }
	    }

            ret.dynamic_event_flow = true;     

            ret.id = data2.read2(); // input method id
            String[] localeNameList = null;
            try {
                StringData d = new StringData((ProtocolData) data2);
                localeNameList = d.toStringArray();
            } catch (Exception e) {
		if (Manager.DEBUG) {
		    e.printStackTrace();
		}
            }

            if (localeNameList != null) {
                int max = localeNameList.length;
                ret.localeList = new Locale[max];
                for (int i = 0; i < max; i++) {
	            ret.localeList[i] =
	            toLocale(localeNameList[i]);
	        }
	    }

            imServer.setData(ret);
        }
    }
    
    void driveProtocol(Protocol protocol) throws IOException {
        int opCode = protocol.getOpCode();
        ProtocolData data = protocol.getData();
        
        ProtocolData replyData = new ProtocolData();
        Protocol reply;        
        int imServerID = data.read2();
        int inputContextID = 0;

        AttributedCharacterIterator iterator = null;                
        FeedbackText text = null;
        int type;
        
        switch(opCode) {
	  case IM_PREEDIT_START:
	    debug("IM_PREEDIT_START");
	      
	    inputContextID = data.read2();
	    replyData.write2(imServerID);
	    replyData.write2(inputContextID);
	    replyData.write4(1024);
                
	    dispatchPreeditEvent(new IIIMPreeditEvent(IIIMPreeditEvent.START));
	    reply = new Protocol(opCode, replyData);
            send(new Protocol(IM_PREEDIT_START_REPLY, replyData));
	    break;
	  case IM_PREEDIT_DRAW:
	    {
		debug("IM_PREEDIT_DRAW");
		
		inputContextID = data.read2();
		int caret_position = data.read4();
		int first_changed = data.read4();
		int length_changed = data.read4();
		type = data.read4();
                
		InputContext temp = imServer.getInputContext(inputContextID);
		Manager manager = Manager.getInstance();
		manager.setIIIMPreeditListener(temp.getPreeditListener());
		if (type == STRING) {
		    //iterator =
		    //    (new AttributedString(data.readString())).getIterator();                
		} else if (type == TEXT) {
		    text = FeedbackText.toFeedbackText(data);
		}
                
		if(text == null) {
		    for (int i = (first_changed + length_changed - 1);
			 i >= first_changed; i--) {
			temp.removeContentAt(i);
		    }
		} else {
		    // composedText replace
		    if (length_changed > 0) {
			for (int i = (first_changed + length_changed - 1);
			     i >= first_changed; i--) {
			    temp.removeContentAt(i);
			}
		    }
		    int len = text.count;
		    for (int i = first_changed, j = 0;
			 i < (first_changed+len); i++, j++) {
			FeedbackChar c = text.value[j];
			temp.insertContentAt(c, i);
		    }
		}
		
		iterator = temp.getAttributedString().getIterator();
		
		// IIIMPreeditEvent don't include change first, change length
		// and FeedbackType information   
		IIIMPreeditEvent event =
		    new IIIMPreeditEvent(IIIMPreeditEvent.DRAW,
					 iterator, caret_position);
		dispatchPreeditEvent(event);
		regularReply(imServerID, inputContextID, IM_PREEDIT_DRAW_REPLY);
		break;
	    }
	  case IM_PREEDIT_DONE:
	    debug("IM_PREEDIT_DONE");
            
	    // Send event to remove preeditlistener
	    inputContextID = data.read2();
	    // For event isn't synchronized, but it have no effect
	    dispatchPreeditEvent(new IIIMPreeditEvent(IIIMPreeditEvent.DONE));
	    regularReply(imServerID, inputContextID, IM_PREEDIT_DONE_REPLY);
	    break;

	  case IM_STATUS_START:
	    debug("IM_STATUS_START");
            
	    inputContextID = data.read2();
	    
	    dispatchStatusEvent(new IIIMStatusEvent(IIIMStatusEvent.START));
	    regularReply(imServerID, inputContextID, IM_STATUS_START_REPLY);
	    break;

	  case IM_STATUS_DRAW:
	    debug("IM_STATUS_DRAW");
            
	    inputContextID = data.read2();
	    type = data.read4();
                
	    IIIMStatusEvent statusEvent = null;
	    if(type == STRING) {
		iterator =
		    (new AttributedString(data.readString())).getIterator();
	    } else if(type == TEXT) {
		text = FeedbackText.toFeedbackText(data);

		iterator = FeedbackText.toACIterator(text);
	    }
	    statusEvent = 
		new IIIMStatusEvent(IIIMStatusEvent.DRAW, iterator);                
	    dispatchStatusEvent(statusEvent);
	    
	    regularReply(imServerID, inputContextID, IM_STATUS_DRAW_REPLY);
	    break;

	  case IM_STATUS_DONE:
	    debug("IM_STATUS_DONE");
            
	    inputContextID = data.read2();
	    dispatchStatusEvent(new IIIMStatusEvent(IIIMStatusEvent.DONE));
	    
	    regularReply(imServerID, inputContextID, IM_STATUS_DONE_REPLY);
	    break;

	  case IM_FORWARD_EVENT:
	    {
		debug("IM_FORWARD_EVENT");

		inputContextID = data.read2();
		type = data.read4();
		switch(type) {
		  case STRING:
		    break;
		  case TEXT:
		    break;
		  case KEYEVENT:
		    int len = data.read4();
		    imServer.getInputContext(inputContextID).setForward();
		    if(len > 0) {
			byte[] buf = new byte[len];
			data.read(buf, 0, len);
			KeyData kd = new KeyData(buf, len);
			IIIMPKey[] kev = kd.toKey();
			break;                        
		    }
		    break;
		}
		regularReply(imServerID, inputContextID, IM_FORWARD_EVENT_REPLY);
		break;
	    }

	  case IM_FORWARD_EVENT_REPLY:
	    debug("IM_FORWARD_EVENT_REPLY");
            
	    inputContextID = data.read2();
	    break;

	  case IM_FORWARD_EVENT_WITH_OPERATIONS:
	  case IM_FORWARD_EVENT_WITH_OPERATIONS_REPLY:
	    debug("IM_FORWARD_EVENT_WITH_OPERATIONS_REPLY");

	    // unprocessed operation list processing will be done later

	    break;

	  case IM_COMMIT_STRING:
	    debug("IM_COMMIT_STRING");
            
	    inputContextID = data.read2();
	    type = data.read4();
	    IIIMCommittedEvent commitedEvent = null;

	    {
		InputContext temp = imServer.getInputContext(inputContextID);
		Manager manager = Manager.getInstance();
		manager.setIIIMCommittedListener(temp.getCommittedListener());
	    }

	    if(type == STRING) {
		String committed = data.readString();
		commitedEvent = new IIIMCommittedEvent(committed);
	    } else if(type == TEXT) {
		text = FeedbackText.toFeedbackText(data);
		commitedEvent = new IIIMCommittedEvent(text.toString());                
	    }
	    dispatchCommittedEvent(commitedEvent);
	    // No reply protocol
	    break;           

	  case IM_LOOKUP_CHOICE_START:
	    {
		debug("IM_LOOKUP_CHOICE_START");
            
		inputContextID = data.read2();
	    
		type = data.read2();
                
		int size = data.read2();
		int rows = data.read2();
		int columns = data.read2();
                
		int dir = data.read2();
		int labeled = data.read2();
                
		IIIMLookupEvent event =
		    new IIIMLookupEvent(IIIMLookupEvent.START,
					type, size,
					new Dimension(rows, columns),
					dir, labeled);
		dispatchLookupEvent(event);
		regularReply(imServerID, inputContextID,
			     IM_LOOKUP_CHOICE_START_REPLY);   
		break;             
	    }
	  case IM_LOOKUP_CHOICE_DRAW:
	    {
		debug("IM_LOOKUP_CHOICE_DRAW");
            
		inputContextID = data.read2();
		int ret_first_index = data.read4();
		int ret_last_index = data.read4();
		int ret_current_index = data.read4();

		// List of candidates
		FeedbackText[] candidates = null;
		int len = data.read4();

		byte[] buf;

		if (len > 0) {
		    buf = new byte[len];
		    data.read(buf, 0, len);
		    candidates = FeedbackText.
			toListOfFeedbackText(new ProtocolData(buf, len));
		}

		// List of labels
		FeedbackText[] labels = null;
		len = data.read4();
                if (len > 0) {
                    buf =new byte[len];
                    data.read(buf, 0, len);
                    labels = FeedbackText.
                        toListOfFeedbackText(new ProtocolData(buf, len));
                }
                FeedbackText title =
		    FeedbackText.toFeedbackText(data);


		IIIMLookupEvent lookupEvent = null;
		if (Manager.COLOR_SUPPORT) {
		    AttributedCharacterIterator[] can =
			new AttributedCharacterIterator[candidates.length];
		    for (int i = 0; i < can.length; i++) {
			can[i] = FeedbackText.toACIterator(candidates[i]);
		    }

		    AttributedCharacterIterator[] lab =
			new AttributedCharacterIterator[labels.length];
		    if (labels != null) {
			for (int i = 0; i < lab.length; i++) {
			    lab[i] = FeedbackText.toACIterator(labels[i]);
			}
		    } else {
			for (int i = 0; i < lab.length; i++) {
			    lab[i] = new AttributedString("").getIterator();
			}
		    }
		    AttributedCharacterIterator titleIterator =
			title == null ?
			new AttributedString("Lookup Window").getIterator() :
		    FeedbackText.toACIterator(title);

		    lookupEvent = new IIIMLookupEvent(IIIMLookupEvent.DRAW,
						      ret_first_index,
						      ret_last_index,
						      ret_current_index,
						      can, lab, titleIterator);
		} else {
		    String[] can = new String[candidates.length];
		    for (int i = 0; i < can.length; i++) {
			can[i] = candidates[i].toString();
		    }

		    String[] lab = new String[labels.length];
		    if (labels != null) {
			for (int i = 0; i < lab.length; i++) {
			    lab[i] = labels[i].toString();                
			}
		    } else {
			// In this case, user must select candidate
			// with mouse click.
			for (int i = 0; i < candidates.length; i++) {
			    lab[i] = " ";
			}
		    }
		    String titleString =
			title == null ? "Lookup Window" : title.toString();

		    lookupEvent = new IIIMLookupEvent(IIIMLookupEvent.DRAW,
						      ret_first_index,
						      ret_last_index,
						      ret_current_index,
						      can, lab, titleString);
		}

		dispatchLookupEvent(lookupEvent);
                regularReply(imServerID, inputContextID,
			     IM_LOOKUP_CHOICE_DRAW_REPLY);
                break;
            }
	  case IM_LOOKUP_CHOICE_PROCESS:
            {
                debug("IM_LOOKUP_CHOICE_PROCESS");
            
                inputContextID = data.read2();
                
                type = data.read2();
                
                int val = data.read2();
                
                if(type == 0) {
                    // INDEX type, val is selected index
                } else if(type ==  1) {
                    // PAGE type, val is page index
                }
                IIIMLookupEvent event = new IIIMLookupEvent(
                                        IIIMLookupEvent.PROCESS,
                                        type, val
                                        );
		dispatchLookupEvent(event);
                regularReply(imServerID, inputContextID,
			     IM_LOOKUP_CHOICE_PROCESS_REPLY);
                break;
            }
	  case IM_LOOKUP_CHOICE_DONE:
            {
                debug("IM_LOOKUP_CHOICE_DONE");
            
                inputContextID = data.read2();
		dispatchLookupEvent(new IIIMLookupEvent(IIIMLookupEvent.DONE));
                regularReply(imServerID, inputContextID,
			     IM_LOOKUP_CHOICE_DONE_REPLY);
                break;
            }

	  case IM_AUX_START:
            {
                debug("IM_AUX_START");
                inputContextID = data.read2();
                auxIMID = imServerID;
		auxICID = inputContextID;

                int index = data.read4();
                String name = data.readString();

		if (odLoader == null) {
                  replyData.write2(imServerID);
                  replyData.write2(inputContextID);
                  replyData.write4(index);
                  replyData.writeString(name);
                  send(new Protocol(IM_AUX_START_REPLY, replyData));
		  break;
		}

		try {
		    IIIMAuxListener aux =
			(IIIMAuxListener)odLoader.loadClass(name).newInstance();
		    auxMap.put(name, aux);
		    aux.auxStart(new IIIMAuxEvent(IIIMAuxEvent.START,
						  index, name));
		} catch(Exception e) {
		    if (Manager.DEBUG) {
			e.printStackTrace();
		    }
		}

                replyData.write2(imServerID);
                replyData.write2(inputContextID);
                replyData.write4(index);
                replyData.writeString(name);
                send(new Protocol(IM_AUX_START_REPLY, replyData));
                break;
            }
	  case IM_AUX_DRAW:
            {
                debug("IM_AUX_DRAW");
            
                inputContextID = data.read2();
                StringData data1 = new StringData(data);
                int index = data1.read4();
                String name = data1.readString();
                int len = data1.read4() / 4;
                int[] idata = new int[len];
                for(int i = 0; i < len; i++) {
                    idata[i] = data1.read4();
                }
                
                String[] sdata = data1.toStringArray2();

		IIIMAuxListener aux = (IIIMAuxListener)auxMap.get(name);
		if (aux == null) {
		  debug(" Unknown aux : " + name);
                  replyData.write2(imServerID);
                  replyData.write2(inputContextID);
                  replyData.write4(index);
                  replyData.writeString(name);
                  send(new Protocol(IM_AUX_DRAW_REPLY, replyData));                
		  break;
		}
		aux.auxDraw(new IIIMAuxEvent(IIIMAuxEvent.DRAW, index,
					     name, idata, sdata));
                replyData.write2(imServerID);
                replyData.write2(inputContextID);
                replyData.write4(index);
                replyData.writeString(name);
                send(new Protocol(IM_AUX_DRAW_REPLY, replyData));                
                break;
            }   
	  case IM_AUX_DONE:
            {
                debug("IM_AUX_DONE");
            
                inputContextID = data.read2();
                
                int index = data.read4();
                String name = data.readString();

		IIIMAuxListener aux = (IIIMAuxListener)auxMap.get(name);
		if (aux == null) {
		    debug(" Unknown aux : " + name);
		    break;
		}

		aux.auxDone(new IIIMAuxEvent(IIIMAuxEvent.DONE, index, name));
                
                replyData.write2(imServerID);
                replyData.write2(inputContextID);
                replyData.write4(index);
                replyData.writeString(name);
                send(new Protocol(IM_AUX_DONE_REPLY, replyData));                 
                break;
            }
	  case IM_CONNECT_REPLY:
	    {
                debug("IM_CONNECT_REPLY");
                IIIMPIMValues ret = new IIIMPIMValues();

                String[] localeNameList = null;
	        try {
	            StringData d = new StringData((ProtocolData) data);
	            localeNameList = d.toStringArray();
	        } catch (Exception e) {
		    if (Manager.DEBUG) {
			e.printStackTrace();
		    }
	        }
	        Locale[] localeList = null;
	        if (localeNameList != null) {
	            int max = localeNameList.length;
	            localeList = new Locale[max];
	            for (int i = 0; i < max; i++) {
		        localeList[i] = toLocale(localeNameList[i]);
	            }
	        }
	        ret.id = imServerID;
	        ret.dynamic_event_flow = false; 
	        
	        imServer.setData(ret);	        
	        break;
	    }
	  case IM_DISCONNECT_REPLY:
	    {
                debug("IM_DISCONNECT_REPLY");
	    
	        //Initialize imServer
	        imServer = new IMServer();

	        break;           	 
	    }
	  case IM_CREATEIC_REPLY:
	    {
                debug("IM_CREATEIC_REPLY");
	    
	        inputContextID = data.read2();
		aliveIC.add(new Integer(inputContextID));
	        if(inputContext != null) // It shouldn't happen
	            inputContext.setID(inputContextID);
        
	        // getCurrentInputContext().setID(inputContextID);         
	        break;
	    }
	  case IM_TRIGGER_NOTIFY:
	    {
		debug("IM_TRIGGER_NOTIFY");

		inputContextID = data.read2();
		int onOff = data.read2();
		data.read2(); // padding

		InputContext ic = imServer.getInputContext(inputContextID);
		if (onOff == 1) {
		    // this method do not send IM_TRIGGER_NOTIFY
		    ic.setConversionModeOff();
		}
		regularReply(imServerID, inputContextID, IM_TRIGGER_NOTIFY_REPLY);
		break;
	    }
	  case IM_TRIGGER_NOTIFY_REPLY:
	    {
                debug("IM_TRIGGER_NOTIFY_REPLY");
	    
	        inputContextID = data.read2();
	        break;        
	    }
	  case IM_DESTROYIC_REPLY:
	    {
                debug("IM_DESTROYIC_REPLY");
	    
	        inputContextID = data.read2();
	        imServer.removeInputContextHandler(
	            imServer.getInputContext(inputContextID));
	        break;
	    }
	  case IM_SETICVALUES_REPLY:
	    {
                debug("IM_SETICVALUES_REPLY");
	    
	        inputContextID = data.read2();
	        break;
	    }
	  case IM_GETICVALUES_REPLY:
	    {
                debug("IM_GETICVALUES_REPLY");
	    
	        inputContextID = data.read2();
	        int len = data.read2(); // byte length
	        byte[] buf = new byte[len];
	        data.read(buf, 0 , len);
	        data.read2(); //pad
	        
	        IIIMPICValues value = new IIIMPICValues(buf);
	        // getInputContext() and set ICValues
	        break;
	    }
	  case IM_SETICFOCUS_REPLY: 
	  case IM_UNSETICFOCUS_REPLY:
	    {
                //debug("IM_SETICFOCUS_REPLY or IM_UNSETICFOCUS_REPLY");
	    
	        inputContextID = data.read2();
	        break;
	    }
	  case IM_RESETIC_REPLY:
	    {
	        debug("IM_RESETIC_REPLY");
	        
	        inputContextID = data.read2();
	        // Reset ic's context
	        imServer.getInputContext(inputContextID).done();
	        //done();
	        break;
	    }
	  case IM_AUX_SETVALUES_REPLY:
	    {
	        debug("IM_AUX_SETVALUES_REPLY");
	        inputContextID = data.read2();
	        int index = data.read4();
	        String name = data.readString();
	        
		// dispatchAuxEvent(new IIIMAuxEvent(IIIMAuxEvent.SETVALUES, index, 
		//				  name, null, null));
	        break;
	    }
	    
	  case IM_SETIMVALUES:
	    {
		data.read2(); //padding
		int length = data.read4();

		if (length > 0) {
		    byte[] buf = new byte[length];
		    data.read(buf, 0, length);

		    ProtocolData attrs = new ProtocolData(buf, length);

		    while(attrs.available() > 0) {
			type = attrs.read2();
			attrs.read2(); // pad
			int size = attrs.read4();
			ProtocolData adata =
			    new ProtocolData(attrs.buf, attrs.pos, size);
			attrs.skipBytes(size);
			switch(type) {
			  case INPUTMETHOD_LIST:
			    while(adata.available() > 0) {
				String imHRN = adata.readString();
				String imID = adata.readString();
				debug("im = " + imHRN + ":" + imID);
				int len = adata.read4();
				ProtocolData ldata =
				    new ProtocolData(adata.buf, adata.pos, len);
				adata.skipBytes(len);
				while(ldata.available() > 0) {
				    String langHRN = ldata.readString();
				    String langID = ldata.readString();
				    debug(" im lang = " +
					  langHRN + "/" + langID);
				}
			    }
			    break;
			  case OBJECT_DESCRIPTER_LIST:
			    while(adata.available() > 0) {
				int objCategory = adata.read2();
				adata.read2(); // pad 
				int objSize = adata.read4();
				int attribID = adata.read2();
				int dattribID = adata.read2();
				if (objCategory == SYNTAX_RULE_CATEGORY) {
				    ccdefID = dattribID;
				} else if (objCategory == MANAGE_RULE_CATEGORY) {
				    manageRuleID = dattribID;
				}
				String objName = adata.readString();
				String objHRN = adata.readString();
				String objSignature = adata.readString();
				String objUser = adata.readString();
				IIIMPObjectDescripter objDesc =
				    new IIIMPObjectDescripter(objCategory,
							      objSize,
							      attribID,
							      dattribID,
							      objName,
							      objHRN,
							      objSignature,
							      objUser);
				doSet.add(objDesc);
			    }
			    break;
			  default:
			    debug(" unknown attribID");
			    break;
			}
		    }
		}
		replyData.write2(imServerID);
		replyData.write2(0); // pad
		send(new Protocol(IM_SETIMVALUES_REPLY, replyData));
		break;
	    }
	  case IM_SETIMVALUES_REPLY:
	    debug("IM_SETIMVALUES_REPLY");
	    
	    data.read2(); // for pad
	    break;
	  
	  case IM_GETIMVALUES_REPLY:
	    {
	        debug("IM_GETIMVALUES_REPLY");
	    
	        data.read2(); // for pad
		data.read4(); // size , not used
	        
		while(data.available() > 0) {
		    debug(" data.available() = " + data.available());
		    int attrID = data.read2();
		    debug(" attrID = " + attrID);
		    
		    data.read2(); // for pad
		    int size = data.read4();
		    debug("attrsize = " + size);

		    // CCDEF & ManageRule object downloading
		    if (attrID == ccdefID) {
			String ccdef = data.readString();
			Manager manager = Manager.getInstance();
			manager.setCCDEF(ccdef);
			continue;
		    } else if (attrID == manageRuleID) {
			String mr = data.readString();
			Manager manager = Manager.getInstance();
			manager.setManageRule(new ManageRule(mr));
			continue;
		    }

		    if (size > 0) {
			int classNameListSize = data.read4();
			debug(" cnls = " + classNameListSize);
			ProtocolData classNameData =
			    new ProtocolData(data.buf, data.pos, classNameListSize);
			data.skipBytes(classNameListSize);
			while(classNameData.available() > 0) {
			    String className = classNameData.readString();
			    classNameSet.add(className);
			    debug(" class = " + className);
			}
			int jarSize = data.read4();
			debug(" jar size = " + jarSize);
			byte[] attrval = new byte[jarSize];
			data.read(attrval, 0, jarSize);

			// FileOutputStream out = new FileOutputStream("/tmp/out");
			// out.write(attrval);
			// out.flush();

			if (odLoader != null) {
			    JarInputStream jis =
				new JarInputStream(new ByteArrayInputStream(attrval));
			    odLoader.addJar(jis);
			}

			int pad = ProtocolData.paddings(size);
			debug(" paddings = " + pad);
			for (int i = 0; i < pad; i++) {
			    data.read();
			}
		    }
		}

		if (classNameSet.size() > 0) {
		    // check Preedit/Lookup/Status override object
		    Iterator it = classNameSet.iterator();
		    try {
			while (it.hasNext()) {
			    String name = (String)it.next();
			    debug(" name = " + name);
			    if (odLoader != null) {
				Class cl = odLoader.findClass(name);
				Object o = cl.newInstance();
				Manager manager = Manager.getInstance();

				if (o instanceof IIIMLookupListener) {
				    manager.setIIIMLookupListener
					((IIIMLookupListener)o);
				} else if (o instanceof IIIMPreeditListener) {
				    manager.setIIIMPreeditListener
					((IIIMPreeditListener)o);
				} else if (o instanceof IIIMStatusListener) {
				    manager.setIIIMStatusListener
					((IIIMStatusListener)o);
				}
			    }
			}
		    } catch(Exception e) {
			if (Manager.DEBUG) {
			    e.printStackTrace();
			}
		    }
		}

                break;
	    }
            default:
                break;
        }    
    }
    
    synchronized void connectIM() throws IOException {
	// for reconnection...
	if (url != null) {
	    connected = connectIMImpl(url);
	}

	String urlAddr = Manager.getProperty("iiimp.server");
	if (urlAddr == null) {
		urlAddr = "iiimp://localhost:9010";
	}
	URL tmp = new URL(urlAddr);
        url = tmp;
	connected = connectIMImpl(tmp);
	
	if (download) {
	    // Object Downloading setup
	    setClientType();
	    download = false;

	    // Object Downloading
	    downloadObject();
	    download = false;
	}
    }
    
    boolean connectIMImpl(URL url) {
	try {
	    uc = (IIIMPURLConnection) url.openConnection();

	    if (uc.isConnected() == false) {
		uc.connect();
		
		if (uc.isConnected() == false) {
		    throw new IOException();
		}
	    }
	    
	    in = new IIIMPInputStream(this, uc.getInputStream());
	    out = new IIIMPOutputStream(uc.getOutputStream());
	    openIM();
	    return true;
	} catch (IOException e) {
	    debug("IOException = " + e);
	    return false;
	}
    }
    
    synchronized void openIM() throws IOException {
	if (userName == null) {
	    userName = System.getProperty("user.name", null);
	    String hostName = getHostName();
	    if (hostName != null) {
		userName += "@" + hostName;
	    }
	    String passwd = getPasswd();
	    if (passwd != null) {
	        userName += "#" + passwd;
	    }
	}
	if (userName == null) {
	    throw new IOException();
	} 
	
	// creates protocol for IM_CONNECT
	ProtocolData data = new ProtocolData();
	data.write(BIGENDIAN); // endian
	data.write(VERSION); //protocol version
	data.writeString(userName); // user name and host name
	data.write2(0);  //client auth protocol names

	Protocol protocol = new Protocol(IM_CONNECT, data);
	send(protocol);
	
	getReply(IM_REGISTER_TRIGGER_KEYS, IM_CONNECT_REPLY);
    }
    
    void closeIM(int imID) throws IOException {
	if (connected == false) {
	    return;
	}
	
	ProtocolData data = new ProtocolData();
	data.write2(imID); // input method id
	data.write2(0); //padding
	
	Protocol protocol = new Protocol(IM_DISCONNECT, data);
	
	send(protocol);
	
	getReply(IM_DISCONNECT_REPLY);
    }                         
    
    void createIC(int imID, Locale locale) throws IOException {
	if (connected == false) {
	    return ;
	}

	// send IM_SETIMVALUES

	ProtocolData data = new ProtocolData();
        data.write2(imID);
        IIIMPICValues val = new IIIMPICValues(locale);
        byte[] buf = val.getListOfICAttr();
        
        data.write2(buf.length);
        data.writeBytes(buf);    
        data.write2(0);
        
        send(new Protocol(IM_CREATEIC, data));
        
        getReply(IM_CREATEIC_REPLY);
    }
    
    static ProtocolData getCreateICData(int imID) throws IOException {
	ProtocolData data = new ProtocolData();
	data.write2(imID);
	IIIMPICValues val = new IIIMPICValues(Locale.getDefault());
	byte[] buf = val.getListOfICAttr();

	data.write2(buf.length);
	data.writeBytes(buf);
	data.write2(0);

	return data;
    }

    void destroyIC(int imID, int icID) throws IOException {
	if (connected == false) {
	    return;
	}

	Object[] intArray = aliveIC.toArray();
	for (int i = 0; i < intArray.length; i++) {
	    if (icID == ((Integer)intArray[i]).intValue()) {
		ProtocolData data = new ProtocolData();
		data.write2(imID);
		data.write2(icID);
        
		send(new Protocol(IM_DESTROYIC, data));
		getReply(IM_DESTROYIC_REPLY);

		aliveIC.remove(intArray[i]);
		break;
	    }
	}
    }

    void setFocus(int imID, int icID, boolean mode) throws IOException {
	if (connected == false) {
	    return;
	}

	ProtocolData data = new ProtocolData();
	data.write2(imID);
	data.write2(icID);

        send(new Protocol(mode ? IM_SETICFOCUS : IM_UNSETICFOCUS, data));
        
        getReply(mode ? IM_SETICFOCUS_REPLY : IM_UNSETICFOCUS_REPLY);
    }

    void resetIC(int imID, int icID) throws IOException {
	if (connected == false) {
	    return;
	}
        
        ProtocolData data = new ProtocolData();
        data.write2(imID);
        data.write2(icID);
        
        send(new Protocol(IM_RESETIC, data));
        getReply(IM_RESETIC_REPLY);
    }
                
    void notifyTrigger(int imID, int icID, boolean mode) throws IOException {
        if(connected == false) {
            return;
        }
        
        ProtocolData ps = new ProtocolData();
        ps.write2(imID);
        ps.write2(icID);
        ps.write2( (mode == true) ? 0 : 1);
        
        send(new Protocol(IM_TRIGGER_NOTIFY, ps));
        getReply(IM_TRIGGER_NOTIFY_REPLY);
    }
        
    void sendAuxData(IIIMAuxEvent ev) throws IOException {
	sendAuxData(auxIMID, auxICID, ev.getIndex(),
		    ev.getName(), ev.getIntValues(), ev.getStringValues());
    }

    void sendAuxData(int imID, int icID, 
                     int index, String name, int[] idata, String[] sdata) 
                     throws IOException {
        if (connected == false) {
            return;
        }

        ProtocolData data = new ProtocolData();
        data.write2(imID);
        data.write2(icID);
        data.write4(index); // auxiliary window class index
        data.writeString(name); // engine name

	if (idata == null) {
	    data.write4(0);
	} else {
	    ProtocolData d = new ProtocolData();
	    for (int i = 0; i < idata.length; i++) {
		d.write4(idata[i]);
	    }
	    data.write4(d.count); // byte length of integer value list
	    data.writeBytes(d.buf, d.count); // interger value list
	}

	if (sdata == null) {
	    data.write4(0);
	} else {
	    ProtocolData d = new ProtocolData();
	    for (int i = 0; i < sdata.length; i++) {
		d.writeString(sdata[i]);
	    }
	    data.write4(d.count); // byte length of string value list
	    data.writeBytes(d.buf, d.count); // string value list
	}

        send(new Protocol(IM_AUX_SETVALUES, data));
        getReply(IM_AUX_SETVALUES_REPLY);        
    }

    /**
     * This will receive object donwloading candidate
     * before get IM_SETVALUES_REPLY
     */
    void setClientType() {
	debug(" setClientType");
	try {
	    ProtocolData data = new ProtocolData();
	    int imID = imServer.getID();
	    data.write2(imID);
	    data.write2(0); // pad

	    IIIMPClientDescripter desc = new IIIMPClientDescripter();
	    ProtocolData d = desc.getData();
	    data.write4(d.count);
	    data.writeBytes(d.buf, d.count);

	    send(new Protocol(IM_SETIMVALUES, data));
	    getReply(IM_SETIMVALUES_REPLY);
	} catch(Exception e) {
	    if (Manager.DEBUG) {
		e.printStackTrace();
	    }
	}
    }

    /**
     * Download object
     */
    void downloadObject() {
	if (doSet.size() == 0) {
	    return;
	}
	try {
	    ProtocolData data = new ProtocolData();
	    int imID = imServer.getID();
	    data.write2(imID);
	    data.write2(0); // pad
	    data.write4(doSet.size() * 2);

	    Iterator it = doSet.iterator();
	    while (it.hasNext()) { 
		IIIMPObjectDescripter desc =
		    (IIIMPObjectDescripter)it.next();
		data.write2(desc.getDAttribID());
		debug(desc.toString());
	    }
		
	    data.pad();
	    send(new Protocol(IM_GETIMVALUES, data));
	    getReply(IM_GETIMVALUES_REPLY);
	} catch(Exception e) {
	    if (Manager.DEBUG) {
		e.printStackTrace();
	    }
	}
    }

    void processKeyEvent(int imID, int icID, KeyEvent e) throws IOException {
	if (connected == false) {
	    return;
	}

        ProtocolData data = new ProtocolData();
        data.write2(imID);
        data.write2(icID);
        data.write4(2); // keyevent type is 2
        
        KeyData kd = new KeyData(e);
        data.writeBytes(kd.toByteStream());

        send(new Protocol(IM_FORWARD_EVENT, data));
        
        getReply(IM_FORWARD_EVENT_REPLY);
    }
    
    private String getHostName() {
	try {
	    java.net.InetAddress hostAddr =
		java.net.InetAddress.getLocalHost();
	    return hostAddr.getHostName();
	} catch (Exception e) {}
	return null;
    }     
    
    private String getPasswd() {
	return (String)AccessController.doPrivileged(new PrivilegedAction() {
	    public Object run() {
		String userHome = System.getProperty("user.home");
		File f = new File(userHome + File.separator + ".iiim" +
			          File.separator + "auth" + File.separator +
			          "passwd");
		try {
		    if (f != null && f.canRead()) {
		        byte[] ba = new byte[32];
			DataInputStream in =
			  new DataInputStream(new FileInputStream(f.getPath()));
			in.readFully(ba);
			in.close();
			return new String(ba);
		    }
		    return createPasswdFile(f);
		} catch(Exception e) {
		    return createPasswdFile(f);
		}
	    }
	});
    }

    private static final String CHMOD_CMD = "/usr/bin/chmod";

    private String createPasswdFile(File f) {
	try {
	    // generate random string and passwd file
	    File fdir = f.getParentFile();
	    fdir.mkdirs();
            DataOutputStream dos =
		new DataOutputStream(new FileOutputStream(f.getPath()));
	    byte[] ba = genPasswd();
	    dos.write(ba, 0, 32);
	    dos.flush();
	    dos.close();
	    // protect file so that only owner of passwd file can read
	    // (Solaris only solution)
	    String osName = System.getProperty("os.name");
	    if (osName.startsWith("SunOS")) {
	      Runtime rt = Runtime.getRuntime();
	      // $HOME/.iiim/auth
	      rt.exec(CHMOD_CMD + " 700 " + fdir.getPath());
	      // $HOME/.iiim/auth/passwd
	      rt.exec(CHMOD_CMD + " 600 " + f.getPath());
	    }
	    return new String(ba);
	} catch(Exception e) {	
	    return null;
	}  
    }

    /*
     * Generate 32 byte random string.
     */
    private byte[] genPasswd() {
	SecureRandom sr;
	try {
	    sr = SecureRandom.getInstance("SHA1PRNG");
	} catch(NoSuchAlgorithmException e) {
	    sr = new SecureRandom();
	}
    
	sr.setSeed(System.currentTimeMillis());
	
	byte[] ba = new byte[32];
	sr.nextBytes(ba);
	for (int i = 0; i < 32; i++) {
	    ba[i] = aa[Math.abs(ba[i] % 62)];
	}

	return ba;
    }

    static byte[] aa = {
	(byte)'0', (byte)'1', (byte)'2', (byte)'3',
	(byte)'4', (byte)'5', (byte)'6', (byte)'7',
	(byte)'8', (byte)'9', (byte)'A', (byte)'B',
	(byte)'C', (byte)'D', (byte)'E', (byte)'F',
	(byte)'G', (byte)'H', (byte)'I', (byte)'J',
	(byte)'K', (byte)'L', (byte)'M', (byte)'N',
	(byte)'O', (byte)'P', (byte)'Q', (byte)'R',
	(byte)'S', (byte)'T', (byte)'U', (byte)'V',
	(byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
	(byte)'a', (byte)'b', (byte)'c', (byte)'d',
	(byte)'e', (byte)'f', (byte)'g', (byte)'h',
	(byte)'i', (byte)'j', (byte)'k', (byte)'l',
	(byte)'m', (byte)'n', (byte)'o', (byte)'p',
	(byte)'q', (byte)'r', (byte)'s', (byte)'t',
	(byte)'u', (byte)'v', (byte)'w', (byte)'x',
	(byte)'y', (byte)'z',
    };
    
    public void activate() {
        debug("Enter activate");
        try {
            if(connected == false) connectIM();
            if(connected == false) return;
            
            java.awt.im.InputContext tmp = getInputContext();

            if(tmp == null) return;
            
            int icID = imServer.getInputContextID(tmp);
            Manager manager = Manager.getInstance();

            if(icID == -1) {
                inputContext = new InputContext(tmp, this);
                imServer.addInputContextHandler(inputContext);
                prevLocale = manager.getCurrentLocale();
                inputContext.create(imServer, manager.getCurrentLocale());
		inputContext.setPreeditListener(manager.getIIIMPreeditListener());
		inputContext.setCommittedListener(manager.getIIIMCommittedListener());
            } else {
               if (prevLocale.equals(manager.getCurrentLocale())){
                 debug("prevLocale and CurrentLocale are the same");
               } else {
                 prevLocale = manager.getCurrentLocale();
                 inputContext.create(imServer, manager.getCurrentLocale());
	         inputContext.setPreeditListener(manager.getIIIMPreeditListener());
	         inputContext.setCommittedListener(manager.getIIIMCommittedListener());
               }
            }
            if (inputContext != null) {
              inputContext.activate();
            }
        } catch(IOException e) {
	    if (Manager.DEBUG) {
		e.printStackTrace();
	    }
        }
        
    }
    
    public void deactivate(boolean isTemporary) {
        debug("Enter deactivate");
        
        if(inputContext != null)
            inputContext.deactivate();
    }
    
    public void removeNotify() {
	//dispose();
    }
    
    public void endComposition() {
        debug("Enter endComposition");
        
        synchronized(this) {
        if(inputContext != null) 
            inputContext.reset(false);
        }
    }
    
    public void dispose() {
        debug("Enter dispose");
        if(inputContext != null) 
            inputContext.dispose();
        done();
    }

    private void done() {
        dispatchPreeditEvent(new IIIMPreeditEvent(IIIMPreeditEvent.DONE));
        dispatchCommittedEvent(new IIIMCommittedEvent(""));
        dispatchStatusEvent(new IIIMStatusEvent(IIIMStatusEvent.DONE));
        dispatchLookupEvent(new IIIMLookupEvent(IIIMLookupEvent.DONE));
    }
        
    public void actionPerformed(IIIMActionEvent e) {
	if (e.getType() == IIIMActionEvent.FORWARD_STRING) {
	    String[] args = (String[])e.getArg();
	    if (args.length != 2) {
		return;
	    }

	    String action = args[0];
	    String content = args[1];

	    try {
		inputContext.deliverStringEvent(action, content);
	    } catch(Exception ex) {
		if (Manager.DEBUG) {
		    ex.printStackTrace();
		}
	    }
	}
    }
		
    void forwardEventWithOperation(int imID, int icID, String action, String content)
	throws IOException {

	ProtocolData data = new ProtocolData();
	data.write2(imID);
	data.write2(icID);
	// PD supports only "convert"/Sting type of operation now.
	data.write4(0); // STRING type
	data.writeString(content);

	ProtocolData operation = new ProtocolData();
	operation.writeString(action);
	operation.write2(0); // there is no value associated with "convert" action
	operation.pad();

	data.write4(operation.count);
	data.writeBytes(operation.buf, operation.count);

	send(new Protocol(IM_FORWARD_EVENT_WITH_OPERATIONS, data));

	getReply(IM_FORWARD_EVENT_WITH_OPERATIONS_REPLY);
    }

    public static void main(String[] args) {
        try {
            ProtocolDriver pd = new ProtocolDriver();
            pd.connectIM();
        } catch(IOException e) {
	    if (Manager.DEBUG) {
		e.printStackTrace();
	    }
        }

    }

    private static final String IIIMP_PGK_PREFIX = "sun.awt.im";
    private static final String JAVA_PROTOCOL_HANDLER_PKGS = 
                                          "java.protocol.handler.pkgs";

    static {
        // Add sun.awt.im to the java.protocol.handler.pkgs property
        // if it is not defined yet.
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                Properties defaults = System.getProperties();
                synchronized (defaults) {
                    String pkgs = defaults.getProperty(
                                    JAVA_PROTOCOL_HANDLER_PKGS);
                    if (pkgs == null) {
                        pkgs = IIIMP_PGK_PREFIX;
                    } else {
                        StringTokenizer pkgPrefixIter = new StringTokenizer(pkgs, "|");
                        while (pkgPrefixIter.hasMoreTokens()) {
                            if (pkgPrefixIter.nextToken().equals(IIIMP_PGK_PREFIX)) {
                                return null;
                            }
                        }
                        pkgs += "|" + IIIMP_PGK_PREFIX;
                    }
                    defaults.put(JAVA_PROTOCOL_HANDLER_PKGS, pkgs);
                }
                return null;
            }
        });
    }
    
    void debug(String string) {
        if (Manager.DEBUG) {
	    System.err.println(string);
	}
    }

    public String getName() {
	return "IIIM ProtocolDriver";
    }

    public String[] getEngineScript() {
	return null;
    }

    public IIIMEvent[] getTriggerEvent() {
	return null;
    }

    public IIIMEvent[] getTriggerOffEvent() {
	return null;
    }

    public void setTriggerEvent() {
	return;
    }

    public void setTriggerOffEvent() {
	return;
    }

    public Locale[] getSupportLocales() {
	return imServer.getAvailableLocales();
    }

    static Locale toLocale(String str) {
	if (str == null || str.length() == 0) {
	    return null;
	}

	// if str is a locale name that is defined in the spec...
	if (str.equals("ja")) {
	    return Locale.JAPAN;
        } else if (str.equals("ko")) {
	    return Locale.KOREAN;
        } else if (str.equals("zh_TW")) {
	    return Locale.TRADITIONAL_CHINESE;
        } else if (str.equals("zh_CN")) {
	    return Locale.SIMPLIFIED_CHINESE;
        } 

	// if str is not in the spec...
	int index = str.indexOf('_');
	if (index > 0) {
	    String language = str.substring(0, index);
	    String country = str.substring(index + 1);
	    return new Locale(language, country);
	}
	return new Locale(str, "");
    }

    static String toLocaleString(Locale locale) {
	if (locale == null) {
	    return null;
	}

        String loc = locale.getLanguage();
        String region = locale.getCountry();
        String locale_string = "";

	if (loc.equals("ja")) {
	    return "ja";
        } else if (loc.equals("ko")) {
	    return "ko";
        } else if (loc.equals("zh")) {
            if (region.equals("TW")) {
		return "zh_TW";
            } else if (region.equals("CN")){
		return "zh_CN";
            } else
                return "zh_HK";
        } else {
           if (region == null || region.length() == 0)
             locale_string = loc;
           else
             locale_string = loc + "_" + region;
        }
	return locale_string;
    }
}


class IMServer {
    private Vector ichList = new Vector();

    private Locale locale = null;

    private int id = -1;

    private Locale localeList[] = null;

    private IIIMPKey onKey[] = null;

    private IIIMPKey offKey[] = null;

    private boolean dynamic_event_flow = false;


    IMServer() {
    }

    IMServer(int id) {
        this.id = id;
    }

    boolean open(Locale locale) {
	if (isLocaleSupported(locale)) {
	    this.locale = locale;
	    return true;
	}
	return false;
    }

    boolean reopen(Locale locale) {
        if (isLocaleSupported(locale)) {
	    this.locale = locale;
	    return true;
	}
	return false;
    }

    boolean setData(IIIMPIMValues d) {
	if (d == null) {
	    return false;
	}

	// copy IiimpIMValues
	id = d.id;
	dynamic_event_flow = d.dynamic_event_flow;
	onKey = d.onKey;
	offKey = d.offKey;
	localeList = d.localeList;

	return true;
    }

    public Locale[] getAvailableLocales() {
	if (localeList == null) {
	    return null;
	}
        Locale[] tmp = new Locale[localeList.length];
	System.arraycopy(localeList, 0, tmp, 0, localeList.length);
	return tmp;
    }

    void addInputContextHandler(InputContext ich) {
	ichList.addElement(ich);
    }

    void removeInputContextHandler(InputContext ich) {
	ichList.removeElement(ich);
    }

    InputContext getInputContext(int id) {
	for (int i = 0; i < ichList.size(); i++) {
	    InputContext ic = (InputContext) ichList.elementAt(i);
	    if (ic.getID() == id) {
		return ic;
	    }
	}
	return (InputContext) null;
    }

    public InputContext[] getInputContext() {
	if (ichList.size() == 0) {
	    return null;
	}
	InputContext[] ich = new InputContext[ichList.size()];
	ichList.copyInto(ich);
	return ich;
    }

    int getID() {
	return id;
    }

    int getInputContextID(java.awt.im.InputContext ic) {
        InputContext[] inputContext = getInputContext();
        
        if(inputContext == null) {
            debug("InputContext is null, return -1");
            return -1; // I don't know -1 is exact
        }
        for(int i=0; i < inputContext.length; i++) {
            if(inputContext[i].isIn(ic)) return inputContext[i].getID();
        }
        
        debug("There are no ic in it, return -1");
        return -1;
    }
    
    private void debug(String str) {
	if (Manager.DEBUG) {
	    System.err.println(str);
	}
    }

    boolean isDynamicEventFlow() {
	return dynamic_event_flow;
    }

    boolean isConversionOnKey(KeyEvent e) {
	int mod = e.getModifiers();
	int code = e.getKeyCode();
	if (onKey != null) {
	    for (int i = 0; i < onKey.length; i++) {
		if ((mod & onKey[i].modifier) != 0) {
		    if (code == onKey[i].keycode) {
			return true;
		    }
		}
	    }
	}
	return false;
    }

    boolean isConversionOffKey(KeyEvent e) {
	int mod = e.getModifiers();
	int code = e.getKeyCode();
	if (offKey != null) {
	    for (int i = 0; i < offKey.length; i++) {
		if ((mod & offKey[i].modifier) != 0) {
		    if (code == offKey[i].keycode) {
			return true;
		    }
		}
	    }
	}
	return false;
    }

    boolean isLocaleSupported(Locale locale) { 
        if (localeList != null && localeList.length > 0) {
            for (int i = 0; i < localeList.length; i++) {
                if (localeEquals(locale, localeList[i]) == true) {
                    return true;
                }
            }
        }        
        return false;
    }

    static boolean localeEquals(java.util.Locale loc1, java.util.Locale loc2) {
	if (loc1 == null || loc2 == null) {
	    return false;
	}

	String lang = loc1.getLanguage();
        if (lang.equals("en") ||
	    lang.equals("fr") ||
	    lang.equals("de") ||
	    lang.equals("it") ||
	    lang.equals("sv") ||
	    lang.equals("es") ||
	    lang.equals("ja") ||
	    lang.equals("ko")) {
	    return lang.equals(loc2.getLanguage());
	}

	if (lang.equals("zh")) {
	    if (lang.equals(loc2.getLanguage())) {
		String region = loc1.getCountry();
		if (region != null && region.length() > 0) {
		    return region.equals(loc2.getCountry());
		}
		return true;
	    }
	    return false;
        }
	
	return lang.equals(loc2.getLanguage());
    }

}

class InputContext {

    private int id = 0;

    private Locale locale = null;

    private boolean active = false;

    private boolean connected = false;
    
    private boolean convMode = false;

    private boolean isForwardEvent = false;

    private IMServer im = null;
    
    private ProtocolDriver client = ProtocolDriver.getInstance();
    
    private java.awt.im.InputContext ic;
    
    private IIIMPreeditListener preeditListener;

    private IIIMCommittedListener committedListener;

    // Maybe it should be AttrbutedString
    // private StringBuffer preeditContent = new StringBuffer();
    private class ComposedChar {
	char c;
	java.awt.im.InputMethodHighlight attr;
    }
    private Vector composedText = new Vector();
    
    // It maybe changed
    InputContext(java.awt.im.InputContext ic, ProtocolDriver client) {
        this.ic = ic;
        this.client = client;
    }

    void setPreeditListener(IIIMPreeditListener listener) {
	preeditListener = listener;
    }

    void setCommittedListener(IIIMCommittedListener listener) {
	committedListener = listener;
    }

    IIIMPreeditListener getPreeditListener() {
	return preeditListener;
    }

    IIIMCommittedListener getCommittedListener() {
	return committedListener;
    }

    public IMServer getIMServer() {
	return im;
    }

    void create(IMServer im, Locale lc) {
        this.im = im;
	locale = lc;
        active = false;

	try {
	    client.createIC(im.getID(), locale);
            if (isConversionMode() == true)
              setConversionMode(true); 
	} catch (Exception e) {
	    debug("InputContext createIC: " + e);
	}
    }

    public void activate() {
        if (active == true) {
            return;
        }
        active = true;
        setFocus(true);
    }

    public void dispose() {
        try {
            client.destroyIC(im.getID(), id);
        } catch (Exception e) {
	    if (Manager.DEBUG) {
		e.printStackTrace();
	    }
	}
        im.removeInputContextHandler(this);
    }

    public void deactivate() {
        if (active == false) {
            return;
        }
        active = false;
        setFocus(false);
    }

    private boolean beingProcessed = false;

    public void dispatchKeyEvent(KeyEvent kev) {
	if (beingProcessed && (kev.getID() == KeyEvent.KEY_TYPED ||
			  kev.getID() == KeyEvent.KEY_RELEASED)) {
	    kev.consume();
	    return;
	}
	beingProcessed  = dispatchKeyEventImpl(kev);
	if (beingProcessed == true) {
	    kev.consume();
	}
    }
    
    private boolean isUninterestingModifiers(KeyEvent kev) {
        switch (kev.getKeyCode()) {
        case KeyEvent.VK_CONTROL:
        case KeyEvent.VK_CAPS_LOCK:
        case KeyEvent.VK_ALT:
        case KeyEvent.VK_META:
        case KeyEvent.VK_SHIFT:
            return true;
        }
        return false;
    }

    private boolean dispatchKeyEventImpl(KeyEvent kev) {
        if (isConversionMode() == true) {
            if (isUninterestingModifiers(kev) == true) {
                return true;
            }
            if (kev.getID() == KeyEvent.KEY_TYPED) {
                return (isForwardEvent == true) ? false : true;
            }
            if (kev.getID() == KeyEvent.KEY_RELEASED) {
                return (isForwardEvent == true) ? false : true;
            }
        } else {
            if (isUninterestingModifiers(kev) == true) {
                return false;
            }
        }

        if (connected == false) {
            if (im.isConversionOnKey(kev) == true) {
                if (im.reopen(locale) == true) {
                    if (reconnect() == true) {
                        activate();
                        setConversionMode(true);
                    }
                } 
                return true;
            }
            return false;
        }
        if (isForwardEvent == true) {
            isForwardEvent = false;
        }

        if (im.isDynamicEventFlow() == true) {
            // conversion on
            if (isConversionMode() == true) {
                // turn off the conversion if the conversion mode is on
                if (im.isConversionOffKey(kev) == true) {
                    setConversionMode(false);
                    return true;
                }
                return deliverKeyEvent(kev);
            }
            // conversion off
            else {
                // turn on the conversion if the conversion mode is off
                if (im.isConversionOnKey(kev) == true) {
                    setConversionMode(true);
                    return true;
                }
            }
            return false;
        }

        return deliverKeyEvent(kev);
    }

    void setForward() {
	isForwardEvent = true;
    }

    private boolean deliverKeyEvent(KeyEvent kev) {
        try {
            client.processKeyEvent(im.getID(), id, kev);
            if (isForwardEvent == true) {
                return false;
            }
            return true;
        } catch (Exception e) {
	    if (Manager.DEBUG) {
		e.printStackTrace();
	    }
        }
        return false;
    }

    void deliverStringEvent(String action, String content) throws IOException {
	client.forwardEventWithOperation(im.getID(), id, action, content);
    }

    public void reset(boolean state_is_preserved) {
        try {
            client.resetIC(im.getID(), id);
        } catch (Exception e) {
	    if (Manager.DEBUG) { 
		e.printStackTrace();
	    }
	}
        if (state_is_preserved == false) {
            setConversionMode(false);
        }
    }

    void setID(int id) {
        this.id = id;
        connected = true;
    }
    
    int getID() {
        return id;
    }

    private void setConversionMode(boolean mode) {
        try {
            client.notifyTrigger(im.getID(), id, mode);
        } catch (Exception e) {
	    if (Manager.DEBUG) {
		e.printStackTrace();
	    }
	}
        convMode = mode;
    }    

    // This method will be called when IM_TRIGGER_NOTIRY comes from server side
    void setConversionModeOff() {
	convMode = false;
    }

    private boolean reconnect() {
        if (connected == true) {
            return true;
        }
        try {
            client.createIC(im.getID(), locale);
        } catch (Exception e) {
	    if (Manager.DEBUG) {
		e.printStackTrace();
	    }
            return false;
        }
        connected = true;
        return true;
    }
    
    private void setFocus(boolean mode) {
        try {
            client.setFocus(im.getID(), id, mode);
        } catch (Exception e) {
	    if (Manager.DEBUG) {
		e.printStackTrace();
	    }
	}
    }

    private boolean isConversionMode() {
        return (connected == false) ? false : convMode;
    
        //return convMode;
    }
    
    public boolean isIn(java.awt.im.InputContext ic) {
        if(this.ic == ic) return true;
        else return false;
    }
    
    public void removeContentAt(int index) {
	if (composedText.size() > index) {
	    composedText.removeElementAt(index);
	}
    }
    
    public void insertContentAt(FeedbackChar c, int index) {
        ComposedChar comp = new ComposedChar();
        comp.c = c.c;
        comp.attr = convertVisualFeedbackToHighlight(c.fd[0].value);
        composedText.insertElementAt(comp, index);
    }
    
    AttributedString getAttributedString() {
	if (composedText.isEmpty() == true) {
	    return new AttributedString("");
	}

	ComposedChar[] c = new ComposedChar[composedText.size()];
	composedText.copyInto(c);

	StringBuffer buf = new StringBuffer();
	for (int i = 0; i < c.length; i++) {
	    buf.append(c[i].c);
	}
	AttributedString attrstr = new AttributedString(buf.toString());
	    
	int start = 0;
	int i = 0;
	InputMethodHighlight cur = c[0].attr;
	for (; i < c.length; i++) {
	    InputMethodHighlight next = c[i].attr;
	    if (next != cur) {
		if (cur != null) {
		    attrstr.
		    addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
				 cur, start, i);
		}
		cur = next;
		start = i;
	    }
	}
	if (cur != null) {
	    attrstr.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
			     cur, start, i);
	}

	return attrstr;
    }
    
    
    private InputMethodHighlight convertVisualFeedbackToHighlight(int
								  feedback) {
	InputMethodHighlight h;
	switch (feedback) {
	case sun.awt.im.iiimp.FeedbackType.UNDERLINE:
	    h = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT;
	    break;
	case sun.awt.im.iiimp.FeedbackType.REVERSE:
	    h = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT;
	    break;
	case sun.awt.im.iiimp.FeedbackType.HIGHLIGHT:
	    h = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
	    break;
	case sun.awt.im.iiimp.FeedbackType.PRIMARY:
	    h = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT;
	    break;
	case sun.awt.im.iiimp.FeedbackType.SECONDARY:
	    h = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT;
	    break;
	case sun.awt.im.iiimp.FeedbackType.TERTIARY:
	default:
	    h = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
	    break;
	}
	return h;
    }    
    
    public void done() {
        active = false;
        convMode = false;
        isForwardEvent = false;    
        composedText = new Vector();
    }

    private void debug(String str) {
	if (Manager.DEBUG) {
	    System.err.println(str);
	}
    }
}


class FeedbackChar {

    char c;

    FeedbackType fd[];

    FeedbackChar() {
        c = 0;
        fd = null;
    }

    void addFeedbackType(int i, int j) {
        if(fd == null) {
            fd = new FeedbackType[1];
            fd[0] = new FeedbackType(i, j);
            return;
        }

        FeedbackType inputmethodfeedback = null;
        for(int l = 0; l < fd.length; l++) {
            if(fd[l].id != i)
                continue;
            inputmethodfeedback = fd[l];
            break;
        }

        if(inputmethodfeedback != null) {
            inputmethodfeedback.value &= j;
            return;
        } else {
            int k = fd.length;
            FeedbackType ainputmethodfeedback[] =
		new FeedbackType[k + 1];
            System.arraycopy(fd, 0, ainputmethodfeedback, 0, k);
            fd = ainputmethodfeedback;
            fd[k] = new FeedbackType(i, j);
            return;
        }
    }
}

class FeedbackText implements IIIMProtocol {

    int count;
    FeedbackChar value[];

    public String toString() {
	if (count == 0) {
	    return null;
	}
	StringBuffer sb = new StringBuffer();
	for (int i = 0; i < count; i++) {
	    sb.append(value[i].c);
	}
	return sb.toString();
    }

    FeedbackText() {
	super();
	count = 0;
	value = new FeedbackChar[8];
    }

    void append(FeedbackChar c) {
	ensureCapacity(count + 1);
	value[count++] = c;
    }

    void ensureCapacity(int minimumNum) {
	int maxNum = value.length;
	if (minimumNum > maxNum) {
	    int newNum = (maxNum + 1) * 2;
	    if (minimumNum > newNum) {
		newNum = minimumNum;
	    }
	    FeedbackChar newValue[] = new FeedbackChar[newNum];
	    System.arraycopy(value, 0, newValue, 0, count);
	    value = newValue;
	}
    }

    static FeedbackText toFeedbackText(ProtocolData d) throws IOException {
	int len = d.read4();
	if (len == 0) {
	    return (FeedbackText) null;
	}
	return toFeedbackTextImpl(d, len);
    }

    static AttributedCharacterIterator toACIterator(FeedbackText text) {
	AttributedString as = new AttributedString(text.toString());

	if (Manager.COLOR_SUPPORT) {
	    for (int i = 0; i < text.count; i++) {
		for (int j = 0; j < text.value[i].fd.length; j++) {
		    switch(text.value[i].fd[j].id) {
		      case DECORATION_FEEDBACK:
			switch(text.value[i].fd[j].value) {
			  case FeedbackType.REVERSE:
			    as.addAttribute(TextAttribute.SWAP_COLORS,
					    TextAttribute.SWAP_COLORS_ON,
					    i, i + 1);
			    break;
			  case FeedbackType.UNDERLINE:
			    as.addAttribute(TextAttribute.UNDERLINE,
					    TextAttribute.UNDERLINE_ON,
					    i, i + 1);
			  case FeedbackType.NORMAL:
			  default:
			}
			break;
		      case FOREGROUND_RGB_FEEDBACK:
			as.addAttribute(TextAttribute.FOREGROUND,
					new Color(text.value[i].fd[j].value),
					i, i + 1);
			break;
		      case BACKGROUND_RGB_FEEDBACK:
			as.addAttribute(TextAttribute.BACKGROUND,
					new Color(text.value[i].fd[j].value),
					i, i + 1);
			break;
		      case UNDERLINE_RGB_FEEDBACK:
			as.addAttribute(TextAttribute.FOREGROUND,
					new Color(text.value[i].fd[j].value),
					i, i + 1);
			break;
		      default:
		    }
		}
	    }
	}

	AnnotationValue[] ava = text.getAnnotation();
	if (ava != null) {
	    for (int i = 0; i < ava.length; i++) {
		as.addAttribute(ava[i].getAttribute(),
				ava[i].getValue(),
				ava[i].getStart(),
				ava[i].getEnd());
	    }
	}

	return as.getIterator();
    }

    static private FeedbackText toFeedbackTextImpl(
	ProtocolData d, int len) throws IOException {
	FeedbackText im = new FeedbackText();

	int upto = d.pos + len;
	while (d.pos < upto) {
	    FeedbackChar ic = new FeedbackChar();
	    ic.c = (char) d.read2();
	    int fb_len = d.read2();
	    for (int i = 0; i < fb_len; i += 8) {
		int id = d.read4();
		int val = d.read4();
		ic.addFeedbackType(id, val);
	    }

	    im.append(ic);
	}

	// read Annotation information and set to FeedbackText
	//
	// Now only READING/AttributedCharacterIterator.Attribute.READING
	// and CLAUSE/AttributedCharacterIterator.Attribute.INPUT_METHOD_SEGMENGT
	// annotation (is implemented as referennce for
	// future implementation. Other annotation will be implemented
	// when server side will implement these features.
	//
	int atLen = d.read4();
	if (atLen != 0) {
	    while (d.available() > 0) {
		int attrID = d.read4();
		int size = 0;
		AnnotationValue[] ava = null;
		switch (attrID) {
		  case INPUT_STRING:
		    size = d.read4();
		    d.skipBytes(size);
		    break;
		  case READING:
		    ava = d.readTextAnnotationValues(Attribute.READING);
		    im.setAnnotation(ava);
		    break;
		  case PART_OF_SPEECH:
		    size = d.read4();
		    d.skipBytes(size);
		    break;
		  case CLAUSE:
		    ava = 
			d.readStringAnnotationValues(Attribute.INPUT_METHOD_SEGMENT);
		    im.setAnnotation(ava);
		    d.skipBytes(size);
		    break;
		  default:
		}
	    }
	}

	return (FeedbackText) im;
    }

    static FeedbackText[] toListOfFeedbackText(ProtocolData d)
	throws IOException {
	final int thread = 26; // number of alphabets

	int num_ret = thread;
	FeedbackText[] ret = new FeedbackText[thread];

	int num = 0;
	for (;; num++) {
	    if (d.available() == 0) {
		break;
	    }
	    int len = d.read4();
	    if (len == 0) {
		break;
	    }
	    if (num == num_ret) {
		FeedbackText[] tmp =
		    new FeedbackText[num_ret + thread];
		System.arraycopy(ret, 0, tmp, 0, num_ret);
		ret = tmp;
		num_ret += thread;
	    }
	    ret[num] = toFeedbackTextImpl(d, len);
	}

	if (num > 0) {
	    FeedbackText[] tmp = new FeedbackText[num];
	    System.arraycopy(ret, 0, tmp, 0, num);
	    return tmp;
	}

	return (FeedbackText[]) null;
    }

    private AnnotationValue[] annotations = null;

    void setAnnotation(AnnotationValue[] annotations) {
	this.annotations = annotations;
    }
    
    AnnotationValue[] getAnnotation() {
	return annotations;
    }
}

class FeedbackType {

    public final static int NORMAL 		= 0;
    public final static int REVERSE		= (1 << 0);
    public final static int UNDERLINE		= (1 << 1);
    public final static int HIGHLIGHT		= (1 << 2);
    public final static int PRIMARY		= (1 << 5);
    public final static int SECONDARY		= (1 << 6);
    public final static int TERTIARY		= (1 << 7);

    /**
     * The id of the FeedbackType.
     */
    public int id;

    /**
     * The value of the FeedbackType.
     */
    public int value;

    public FeedbackType() {
	this(0, 0);
    }

    public FeedbackType(int id, int value) {
	this.id = id;
	this.value = value;
    }
}

////////////////////////////////////////////

class IIIMPIMValues {
    int id;
    IIIMPKey onKey[];
    IIIMPKey offKey[];
    Locale localeList[];
    boolean dynamic_event_flow;
}

/**
 * Only the locale attribute in IC values is currently supported.
 * If another attributes are added, then this IiimpICValues needs
 * to be updated to support the attributes.
 */
class IIIMPICValues implements IIIMProtocol {

    Locale locale;

    IIIMPICValues(Locale locale) {
	this.locale = locale;
    }

    IIIMPICValues(byte[] buf) throws IOException {
	ProtocolData d = new ProtocolData(buf, buf.length);

	while (d.available() > 0) {
	    int id = d.read2();

	    int len = d.read2(); // byte length of attr

	    byte[] attr = new byte[len]; // attribute array
	    d.read(attr, 0, len);

	    int pad = d.paddings(len); // number of pad
	    d.skipBytes(pad); // skip pad

	    /*
	     * Currently only InputLanguage is supported.
	     */
	    if (id == IC_INPUT_LANGUAGE) {
		setInputLanguage(attr);
	    }

	    /*
	     * switch (id) {
	     * case IC_INPUT_LANGUAGE:
	     * getInputLocale(attr);
	     * break;
	     * }
	     */
	}
    }

    byte[] getListOfICAttrId() throws IOException {
	ProtocolData d = new ProtocolData();

	// byte length of attr id
	int len = 1 * 2;
	d.write2(len);

	// IC_INPUT_LANGUAGE
	d.write2(IC_INPUT_LANGUAGE);

	// pad it
	d.pad();

	byte[] ret = new byte[d.count];
	System.arraycopy(d.buf, 0, ret, 0, d.count);

	return ret;
    }

    byte[] getListOfICAttr() throws IOException {
	byte[] ret = getInputLanguage();
	return ret;
    }

    void setInputLanguage(byte[] buf) throws IOException {
	ProtocolData d = new ProtocolData(buf, buf.length);

	// The locale names are defined in the IIIMP spec.
	String str = d.readString();
	locale = ProtocolDriver.toLocale(str);
    }

    byte[] getInputLanguage() throws IOException {
	ProtocolData d = new ProtocolData();

	// attribute id
	d.write2(IC_INPUT_LANGUAGE);

	// input langauge value.
	ProtocolData val = new ProtocolData();
	String lang = ProtocolDriver.toLocaleString(locale);
	val.writeString(lang);
	
	// value length
	d.write2(val.count);
	// value
	d.writeBytes(val.buf, val.count);
	// pad it
	d.pad();

	byte[] ret = new byte[d.count];
	System.arraycopy(d.buf, 0, ret, 0, d.count);
	return ret;
    }
}

class StringData extends ProtocolData {

    StringData(byte b[], int len) {
	super(b, len);
    }

    StringData(String str) throws IOException {
	super();
	writeString(str);
    }

    StringData(ProtocolData d) {
	super(d);
    }

    String[] toStringArray() throws IOException {
	final int MAX = 8;

	// reads the length of string to follow
	int len = read2();
	if (len == 0) {
	    return (String[]) null;
	}

	int upto = pos + len;

	int num = 0;
	int num_array = MAX;
	String[] array = new String[num_array];

	for (; pos < upto; num++) {
	    if (num == num_array) {
		num_array += MAX;
		String[] tmp = new String[num_array];
		System.arraycopy(array, 0, tmp, 0, num);
		array = tmp;
	    }
	    array[num] = readString();
	}

	// Shrink array to make the right size
	if (array.length != num) {
	    String tmp[] = new String[num];
	    System.arraycopy(array, 0, tmp, 0, num);
	    array = tmp;
	}

	return array;
    }

    String[] toStringArray2() throws IOException {
	final int MAX = 8;

	// reads the length of string to follow
	int len = read4();
	if (len == 0) {
	    return (String[]) null;
	}

	int upto = pos + len;

	int num = 0;
	int num_array = MAX;
	String[] array = new String[num_array];

	for (; pos < upto; num++) {
	    if (num == num_array) {
		num_array += MAX;
		String[] tmp = new String[num_array];
		System.arraycopy(array, 0, tmp, 0, num);
		array = tmp;
	    }
	    array[num] = readString();
	}

	// Shrink array to make the right size
	if (array.length != num) {
	    String tmp[] = new String[num];
	    System.arraycopy(array, 0, tmp, 0, num);
	    array = tmp;
	}

	return array;
    }
}

class KeyData extends ProtocolData {

    KeyData(KeyEvent kev) throws IOException {
	super();
	int code = kev.getKeyCode();
	char c = kev.getKeyChar();
	int mod = kev.getModifiers();
	long when = kev.getWhen();

	write4(code);
	write4((int) c);
	write4(mod);
	write4((int) when);
    }

    KeyData(byte b[], int len) {
	super(b, len);
    }

    KeyData(ProtocolData d) {
	super(d);
    }

    IIIMPKey[] toKey() throws IOException {
	IIIMPKey[] kev = null;

	int num_kev = (count / 16);
	if (num_kev > 0) {
	    kev = new IIIMPKey[num_kev];
	    for (int i = 0; i < num_kev; i++) {
		int keycode = read4();
		char keychar = (char) read4();
		int modifier = read4();
		int timestamp = read4();
		kev[i] = new IIIMPKey(keycode, keychar, modifier);
	    }
	}

	return kev;
    }

    /**
     * Converts buf to the byte stream, which, if necessary, is padded.
     */
    byte[] toByteStream() throws IOException {
	// reserved 2 bytes space at the head of the stream.
	final int RESERVED = 4;

	if (count == 0) {
	    return null;
	}

	int pad = paddings(count);
	int total = count + pad;
	int real_total = RESERVED + count + pad;

	byte newBuf[] = new byte[real_total];
	System.arraycopy(buf, 0, newBuf, RESERVED, count);

	newBuf[0] = (byte) ((count >>> 24) & 0xFF);
	newBuf[1] = (byte) ((count >>> 16) & 0xFF);
	newBuf[2] = (byte) ((count >>> 8) & 0xFF);
	newBuf[3] = (byte) ((count >>> 0) & 0xFF);

	// padd to 0 just in case
	for (int i = (RESERVED + count); i < real_total; i++) {
	    newBuf[i] = 0;
	}

	// proceed the position to the end
	pos = count;

	return newBuf;
    }
}

class IIIMPKey {
    int keycode;
    char keychar;
    int modifier;

    IIIMPKey(int keycode, char keychar, int modifier) {
	this.keycode = keycode;
	this.keychar = keychar;
	this.modifier = modifier;
    }

    public String toString() {
	return "code = " + keycode + ", char = " + keychar + 
	    ", modifier = " + modifier;
    }
}

