// NonBlockingServer.java
// Set up a nonblocking chatServer that will receive a connection from a client and echo client's message to all connected clients.
package org.dyndns.fishyu.messenger.sockets.server;

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.channels.spi.*;
import java.nio.charset.*;
import java.net.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.*; 


public class NonBlockingServer implements MouseListener {
	
	User user[] = new User[ 21 ];
	Controller controller = new Controller();
	
	private String waitToKill = "";
	
	private final String s = " ";
    JFrame f = new JFrame( "Messneger-Server" );
    
   private String command = "";
   
   private UserRecord userRecord= new UserRecord();
   
   private ChannelUser channelUser[] = new ChannelUser[ 21 ];
   
   private JPanel userRecordPanel = new JPanel();
   private JPanel channelsPanel = new JPanel();
   private JPanel channelUsersPanel = new JPanel();
   
   private JTextField userRecordField[] = new JTextField[ 21 ];
   private JTextField channelsField[] = new JTextField[ 21 ];
   private JTextField channelUsersField[] = new JTextField[ 21 ];
   
   private JTextArea commandArea = new JTextArea( "[kill fishyu] or [0 kill fishyu], 0 is channel NO." );
   private JButton commandButton = new JButton("送出");
   
   private ServerSocketChannel serverSocketChannel;
   private Selector selector;
   private Vector sockets = new Vector();
   private int counter = 0;
   private JTextArea displayArea;
   private Charset charSet; 
   private ByteBuffer writeBuffer;
   private ByteBuffer readBuffer = ByteBuffer.allocate( 512 );
   
   //private String users = "";

   public NonBlockingServer()
   {
      
      Dimension screen=Toolkit.getDefaultToolkit().getScreenSize();
		int width = 330;
		int height = 280;
		int x=(screen.width-width)/2;
		int y=(screen.height-height)/2;
		f.setBounds(x,y,width,height);
		f.getContentPane().setLayout( null );
		
	  for( int i = 0; i < user.length; i++ )
      {
      	  user[ i ] = new User();	
      }
      
      for( int i = 0; i < userRecordField.length; i++ )
      {
          userRecordField[ i ] = new JTextField();
          userRecordField[ i ].addMouseListener( this );
          userRecordField[ i ].setEditable( false );
          userRecordField[ i ].setBackground( Color.white );
          userRecordPanel.add( userRecordField[ i ] );
      }
          //userRecordField[ i ].setText( userRecord.getName( i ) );
          
      for( int j = 0; j < channelsField.length; j++ )
      {
          channelsField[ j ] = new JTextField( "頻道" + j );
          channelsField[ j ].addMouseListener( this );
          channelsField[ j ].setEditable( false );
          channelsField[ j ].setBackground( Color.white );
          channelsPanel.add( channelsField[ j ] );
      }
          //channelsField[ j ].setText( "頻道" + j );
          
      for( int k = 0; k < channelUsersField.length; k++ ) 
      {
          channelUsersField[ k ] = new JTextField();
          channelUsersField[ k ].addMouseListener( this );
          channelUsersField[ k ].setEditable( false );
          channelUsersField[ k ].setBackground( Color.white );
          channelUsersPanel.add( channelUsersField[ k ] );
      }
          //channelUsersField[ k ].setText( channels[ k ].getNames(
             
      userRecordPanel.setLayout( new GridLayout( 21, 1 ) );
      channelsPanel.setLayout( new GridLayout( 21, 1 ) );
      channelUsersPanel.setLayout( new GridLayout( 21, 1 ) );

      displayArea = new JTextArea();
      displayArea.setEditable( false );
      
      JScrollPane displayAreaPane = new JScrollPane( displayArea );
      JScrollPane userRecordPanelPane = new JScrollPane( userRecordPanel );
      JScrollPane channelsPanelPane  = new JScrollPane( channelsPanel );
      JScrollPane channelUsersPanelPane = new JScrollPane( channelUsersPanel );
      JScrollPane commandAreaPane = new JScrollPane( commandArea );
      
      displayAreaPane.setBounds( 0, 0, 80, 200 );
      userRecordPanelPane.setBounds( 80, 0, 80, 200 );
      channelsPanelPane.setBounds( 160, 0, 80, 200 );
      channelUsersPanelPane.setBounds( 240, 0, 80, 200 );  
      
      commandButton.setBounds( 0, 200, 80, 40 );
      commandAreaPane.setBounds( 80, 200, 240, 40 ); 
      
      CommandListener commandListener= new CommandListener();
      commandButton.addActionListener( commandListener );
      
      
      f.getContentPane().add(  displayAreaPane  );
      f.getContentPane().add(  userRecordPanelPane  );
      f.getContentPane().add(  channelsPanelPane );
      f.getContentPane().add(  channelUsersPanelPane );
      f.getContentPane().add(  commandButton );
      f.getContentPane().add(  commandAreaPane );

      
      
      
      for( int i = 0; i < channelUser.length; i++ )
          channelUser[ i ] = new ChannelUser();
          
          f.show();
          
      // close server socket channel and selector when closing window
      f.addWindowListener( 
		
         new WindowAdapter() {

            public void windowClosing( WindowEvent windowEvent )
            {
               // close server socket channel and selector
               try {
                  serverSocketChannel.close();
	              selector.close();
               }
               catch( IOException ioException ) {
                  ioException.printStackTrace();
               }
               finally {
                  System.exit( 0 );
               }
            }

         } // end inner class WindowAdapter

      ); // end addWindowListener

   } // end constructor
   
   // set up and run server 
   public void runServer()
   {
      // set up server to receive connections; process connections
      try {

         // specify the char set used to encode/decode messages
         charSet = Charset.forName( "UTF-8" );

         // create a ServerSocketChannel.
         serverSocketChannel = ServerSocketChannel.open();
	     serverSocketChannel.socket().bind( 
            new InetSocketAddress( 12345 ) );
         serverSocketChannel.configureBlocking( false );

         // wait for a connection.
         getConnection();

      } // end try

      // process problems with I/O
      catch ( Exception ioException ) {
         ioException.printStackTrace();
      }

   } // end method runServer

   // wait for connection to arrive, then display connection info
   private void getConnection() throws Exception
   {
      // Selector for incoming requests
	  selector = SelectorProvider.provider().openSelector();
      serverSocketChannel.register( 
         selector, SelectionKey.OP_ACCEPT, null );

      // process incoming requests
      while ( selector.select() > 0 ) {

         // get channels ready for i/o
         Set readyKeys = selector.selectedKeys();
         Iterator iterator = readyKeys.iterator();

         // for each ready channel, process request
         while ( iterator.hasNext() ) {
		    SelectionKey key = ( SelectionKey )iterator.next();
		    iterator.remove();

            if ( key.isAcceptable() ) {  // ready for connection

               // create connection
               ServerSocketChannel nextReady =
                  ( ServerSocketChannel ) key.channel();
               SocketChannel socketChannel = nextReady.accept();

               if ( socketChannel != null ) {
                  socketChannel.configureBlocking( false );
                  sockets.add( socketChannel.socket() );
                  counter++;

                  SwingUtilities.invokeLater( 

                     new Runnable() {

                        public void run() 
                        {
                           displayArea.append(
                              "\nConnection with Client " + counter );
                        }
                     }
			      );

                  // register read operation to socketChannel
                  SelectionKey readKey = socketChannel.register( 
                     selector, SelectionKey.OP_READ, null );

               } // end if socketChannel != null

            } // end if key.isAcceptable

            else if ( key.isReadable() ) {  // ready for read

               // get socketChannel ready for read
               SocketChannel socketChannel = 
                  ( SocketChannel ) key.channel();

               readMessage( socketChannel );
            }

         } // end processing each channel

      } // end processing incoming requests 

   } // end method getConnection
   
   public void mouseClicked( MouseEvent event )
        {}
      
        public void mouseReleased( MouseEvent event )
        {}
      
        public void mouseEntered( MouseEvent event )
        {}
      
        public void mouseExited( MouseEvent event )
        {}
      // process textfield events
        public void mousePressed( MouseEvent event )
        {
        	for( int i = 0; i < channelsField.length; i++ )
        	{
        	    channelsField[ i ].setBackground( Color.white );
        	    channelsField[ i ].setForeground( Color.black );
        	}
        	int target = 0;
        	for( int i = 0; i < channelsField.length; i++ )
        	    if( event.getSource() ==  channelsField[ i ] )
	        	{
	        		 channelsField[ i ].setBackground( Color.blue );
        	         channelsField[ i ].setForeground( Color.white );
	        	     target = i;
	        	     for( int k = 0; k < channelUsersField.length; k++ ) 
	                 {
	                     channelUsersField[ k ].setText( channelUser[ target ].getName( k ) );
	                 }
	                 break;
	        	}
        	    
        }

   // send message to client
   private void writeMessage( String message ) throws IOException
   {
      Socket socket;
      SocketChannel socketChannel;
      
      // echo message back to all connected clients
      for ( int i = 0; i < sockets.size(); i++ ) {
         socket = ( Socket ) sockets.elementAt( i );
         socketChannel = socket.getChannel();

         // send message to client
         try {

            // convert message to bytes in charSet
            writeBuffer = charSet.encode( message );

            // write message to socketChannel
            socketChannel.write( writeBuffer );
         }

         // process problems sending object
         catch ( IOException ioException ) {
            ioException.printStackTrace();
            socketChannel.close();
            sockets.remove( socket );
         }

      } // end for

   } // end method writeMessage

   // read message from client
   private void readMessage( SocketChannel socketChannel ) 
      throws IOException
   {
      // read message
      
      try {
         
         
         if ( socketChannel.isOpen() ) 
         {
         	boolean commonCase = true;
            readBuffer.clear();
            socketChannel.read( readBuffer );
            readBuffer.flip();
            CharBuffer charMessage = charSet.decode( readBuffer );
            String message = charMessage.toString().trim();

            // remove and close the connection when client disconnects
            /*if ( message.indexOf( "#leave#" ) >= 0 ) 
            {
               sockets.remove( socketChannel.socket() );
               socketChannel.close();
            }*/
            // add user
            
            
            if ( message.indexOf( "|*?+CaSe_1" ) >= 0 ) 
            {
            	commonCase = false;
               StringTokenizer toz = new StringTokenizer( message, "`" );
            	String userName = toz.nextToken();
            	
            	{
                    userRecord.setName( userName );
                    writeMessage( userRecord.getUserNames() );
                }
            }
            // del user
            if ( message.indexOf( "|*?+CaSe_2" ) >= 0 ) 
            {
            	commonCase = false;
            	StringTokenizer toz = new StringTokenizer( message, "`" );
            	String userName = toz.nextToken();
            	
            	{
                userRecord.delName( userName );
                
                writeMessage( userRecord.getUserNames() );
                // remove and close the connection when client disconnects
                sockets.remove( socketChannel.socket() );
                socketChannel.close();
                }
            }
            
            /*if ( message.indexOf( "|*?+CaSe_3" ) >= 0 ) 
            {
            	commonCase = false;
               //writeMessage( "" );
            }
            else if ( message.indexOf( "|*?+CaSe_4" ) >= 0 ) 
            {
            	commonCase = false;
               //writeMessage( "" );
            }*/
            
            
            
            if ( message.indexOf( "#ChannelSendConnectMode#" ) >= 0 )
            {
            	commonCase = false;
            	int code = message.indexOf( "#ChannelSendConnectMode#" );
            	String tmp = message.substring( ( code - 1 ), code );
	            int NO = Integer.parseInt(tmp);
	          
            	StringTokenizer toz = new StringTokenizer( message, "`" );
            	String userName = toz.nextToken(); 
            	
            	{
            	channelUser[ NO ].addUser( userName ); //////////////////////////////////
            	writeMessage( userName + toz.nextToken() + "`" + userName + "`" 
            	               + channelUser[ NO ].getUserlist() + NO + "#ChannelSendConnectMode#" );
            	               
            	               
            	System.out.println( "#ChannelSendConnectMode#" + userName + toz.nextToken() + "`" + userName + "`" 
            	               + channelUser[ NO ].getUserlist() + NO + "#ChannelSendConnectMode#\n" );
            	}
            
            }
            
            if ( message.indexOf( "#ChannelSendDisconnectMode#" ) >= 0 )
            {
            	commonCase = false;
            	int code = message.indexOf( "#ChannelSendDisconnectMode#" );
            	
            	String tmp = message.substring( ( code - 1 ), code );
	            int NO = Integer.parseInt(tmp);
	            
            	StringTokenizer toz = new StringTokenizer( message, "`" );
            	String userName = toz.nextToken(); 
            	
            	{
            	channelUser[ NO ].delUser( userName ); //////////////////////////////////
            	writeMessage( userName + toz.nextToken() + "`" + userName + "`" + channelUser[ NO ].getUserlist() 
            	              /* content^^^^^^^^^^^^^^*/     + NO + "#ChannelSendDisconnectMode#" );
            	}
              }
              
              if ( message.indexOf( "#DEFAULT_SET_PHOTO#" ) >= 0 )
            {
            	commonCase = false;
            	int type = Integer.parseInt( message.substring(
            	              (message.indexOf( "#DEFAULT_SET_PHOTO#" ) - 1 ), 
            	              (message.indexOf( "#DEFAULT_SET_PHOTO#" ) ) ) );
                StringTokenizer toz = new StringTokenizer( message, "`" );
                String userName = toz.nextToken();
                System.out.println( "\nTTTTTTTTTTTTTTTTTTT" + userName );
                
                for(int i = 0; i<user.length; i++)
                    if( user[ i ].getName().equals( s ) )
                    {
                    	user[ i ].setPhoto( type );
                    	user[ i ].setName( userName );
                    	break;
                    }
                    
                
                String to = "";
                for( int i = 0; i<user.length; i++)
                    to += user[ i ].getName() + "`" + user[ i ].getPhoto() + "`";
                    
                    to += "#SET_PHOTO#";
                    System.out.println( "\nTTTTTTTTTTTTTTTTTTT" + to );
                writeMessage( to ); 
            }
            
            if ( message.indexOf( "#SET_PHOTO#" ) >= 0 )
            {
            	commonCase = false;
            	int type = Integer.parseInt( message.substring(
            	              (message.indexOf( "#SET_PHOTO#" ) - 1 ), 
            	              (message.indexOf( "#SET_PHOTO#" ) ) ) );
                StringTokenizer toz = new StringTokenizer( message, "`" );
                String userName = toz.nextToken();
                
                for(int i = 0; i<user.length; i++)
                {
                	if(  user[ i ].getName().equals( userName ) )
                	{
                		user[ i ].setPhoto( type );
                		break;
                		}
                    else if( user[ i ].getName().equals( s ) )
                    {
                    	user[ i ].setPhoto( type );
                    	user[ i ].setName( userName );
                    	break;
                    }
                }
                    
                
                String to = "";
                for( int i = 0; i<user.length; i++)
                    to += user[ i ].getName() + "`" + user[ i ].getPhoto() + "`";
                    
                    to += "#SET_PHOTO#";
                writeMessage( to ); 
            }
            
            if ( commonCase ) 
            {
               writeMessage( message );
            } 
               
               

         } // end if

      } // end try

      catch ( IOException ioException ) {
         ioException.printStackTrace();
         sockets.remove( socketChannel.socket() );
         socketChannel.close();
      }

   } // end method readMessage
   
   private class UserRecord
   {
   	   final String s = " ";
   	   
       final int userMax = 21;
       private String users[] = new String[ userMax ];
       
       public UserRecord()
       {
           for( int i = 0; i < userMax; i++ )
               users[ i ] = s;
       }
       
       public void setName( String name )
       {
       	
           for( int i = 0; i < userMax; i++ )
           {
           	
           	    
               if( users[ i ].equals( s ) )
               {
                   users[ i ] = name;
                   break;
               }
               
           }
       }
       
       public void delName( String name )
       {
           int i, j;
           for( i = 0; i < userMax; i++ )
           {
               if( users[ i ].equals( name ) )
               {
                   users[ i ] = s;
                   break;
                }
              
           }
           
           // trim array
           for( j = i + 1; j < userMax; j++, i++ )
               users[ i ] = users[ j ];
               
       }
       
       public String getName( int i )
       {
       	   return users[ i ];
       }
       	  
       
       public String getUserNames()
       {
       	   for( int j = 0; j < userRecordField.length; j++ )
       	       userRecordField[ j ].setText( users[ j ] );
       	       
           String userNames = "";
           for( int i = 0; i < userMax; i++ )
               userNames += ( users[ i ] + "`" );
           
            userNames += "|*?+CaSe_1";
           return userNames;
       }
   } // end inner class userRecord 
   
   private class ChannelUser
   {
   	   String users[] = new String[ 21 ];
   	   final String s = " ";
       public ChannelUser()
       {
       	   for ( int i = 0; i < users.length; i++ )
       	       users[ i ] = s;
       }
       
       
       
        public void addUser( String newUser )
		{
			for (int i = 0; i < users.length; i++ )
			{
				if ( users[ i ].equals( newUser ) ) 
					break;
				else if ( users[ i ].equals( s ) )
				{
				    users[ i ] = newUser;
				    break;
		    	}
		    }
	    }
	    
	    public void delUser( String target )
	    {
	    	int i;
			for ( i = 0; i < users.length; i++ )
			{
				if ( users[ i ].equals( target ) )
				{
				    users[ i ] = s;
				    break;
		    	}
		    }
		    
		    // trim array
		    for (int j = i + 1; j < users.length; i++, j++ )
			{ 
		        users[ i ] = users [ j ];
		    }
		     
		 }
		 
	   public String getName( int i )
       {
       	   return users[ i ];
       }

			
		public String getUserlist()
		{
			String userList = "";
			for ( int i = 0; i < users.length; i++ )
			    userList += users[ i ] + "`";
			    
			    //userList += "#ChannelUsers#"; // add code
			
			return userList;
		}
    
   }  	   // end private class ChannelUser
   
       private class CommandListener implements ActionListener
       {
       	   public void actionPerformed( ActionEvent e )
       	   {
       	   	   if ( e.getSource() == commandButton )
       	   	   {
       	   	   	   command = commandArea.getText();
       	   	   	   commandArea.setText("[kill fishyu] or [0 kill fishyu], 0 is channel NO.");
       	   	   	   TakeCommand();
       	   	   	   
       	   	   }
           }
           
           public void TakeCommand()
           {
           	   StringTokenizer toz = new StringTokenizer( command, s );
           	   if( toz.countTokens() == 2 )
           	   {
           	   	   if( toz.nextToken().equals( "kill" ) )
           	   	       controller.killUserRecord( toz.nextToken() );
           	   	   
           	   }
           	   else if( toz.countTokens() == 3 )
           	   {
           	       int channel = Integer.parseInt( toz.nextToken() );
           	       if( toz.nextToken().equals( "kill" ) )
           	           controller.killChannelUser ( channel, toz.nextToken() );
           	   }
           	   
           }
       }
       
       private class Controller
       {
       	   public void killUserRecord( String userName )
       	   {
			try{ 
			    writeMessage( userName + "`" + "#never#" );
			   }
			   catch( IOException ioException ){}
               
       	   }
       	   
       	   public void killChannelUser( int NO, String userName )
       	   {
       	   	   channelUser[ NO ].delUser( userName );
       	   	   try{ 
       	   	   writeMessage( userName + " 離開了頻道." + "`" + userName + "`" + channelUser[ NO ].getUserlist() 
            	              /* content^^^^^^^^^^^^^^*/     + NO + "#ChannelSendDisconnectMode#" );
            	              }
			   catch( IOException ioException ){}
       	   }
       }
       
       private class User
       {
       	   int photoStatus = 0;
       	   String userName = s;
       	   public void setPhoto( int k )
       	   {
       	   	   photoStatus = k;
       	   }
       	   
       	   public int getPhoto()
       	   {
       	   	   return photoStatus;
       	   }
       	   
       	   public void setName( String name )
       	   {
       	   	   userName = name;
       	   }
       	    
       	   public String getName()
       	   {
       	   	   return userName;
       	   } 
       	   
       }
       	   	    
       
	       
	   public static void main( String args[] )
	   {
	      NonBlockingServer application = 
	         new NonBlockingServer();
	      application.runServer();
	   }

}  // end class NonBlockingServer
