package net.sourceforge.jsocks.socks;

import java.awt.*;
import java.awt.event.*;

/**
  Socks configuration dialog.<br>
  Class which provides GUI means of getting Proxy configuration
  from the user.
 */
public class SocksDialog extends Dialog
                         implements WindowListener,
                                    ItemListener,
                                    ActionListener,
                                    Runnable{

//GUI components
   TextField host_text,
             port_text,
             user_text,
             password_text,
             direct_text;
   Button add_button,
          remove_button,
          cancel_button,
          ok_button,
          dismiss_button;
   List direct_list;
   Checkbox socks4radio,
            socks5radio,
            none_check,
            up_check,
            gssapi_check;

   Dialog warning_dialog;
   Label warning_label;

   String host,user,password;
   int port;
   Thread net_thread = null;
   
   //CheckboxGroups
   CheckboxGroup socks_group = new CheckboxGroup();

   Proxy proxy;
   InetRange ir;

   final static int COMMAND_MODE = 0;
   final static int OK_MODE = 1;

   int mode;

   /**Wether to resolve addresses in separate thread.
      <p>
      Default value is true, however on some JVMs, namely one from
      the Microsoft, it doesn't want to work properly, separate thread
      can't close the dialog opened in GUI thread, and everuthing else
      is then crashes.
      <p>
      When setting this variable to false, SocksDialog will block while
      trying to look up proxy host, and if this takes considerable amount
      of time it might be annoying to user.

   */
   public static boolean useThreads = true;


// Constructors
////////////////////////////////////
   /**
     Creates SOCKS configuration dialog.<br>
     Uses default initialisation:<br>
     Proxy host: socks-proxy <br>
     Proxy port: 1080 <br>
     Version: 5<br>

    */
   public SocksDialog(Frame parent){
      this(parent,null);
   }
   /**
    Creates SOCKS configuration dialog and initialises it
    to given proxy.
    */
   public SocksDialog(Frame parent,Proxy init_proxy){
     super(parent,"Proxy Configuration",true);
     warning_dialog = new Dialog(parent,"Warning",true);

     guiInit();
     setResizable(false);
     addWindowListener(this);
     Component[] comps = getComponents();
     for(int i=0;i<comps.length;++i){
        if(comps[i] instanceof Button)
           ((Button)comps[i]).addActionListener(this);
        else if(comps[i] instanceof TextField)
           ((TextField) comps[i]).addActionListener(this);
        else if(comps[i] instanceof Checkbox){
           ((Checkbox)comps[i]).addItemListener(this);
        }
     }
     proxy = init_proxy;
     if(proxy != null) doInit(proxy);
     else ir = new InetRange();

     dismiss_button.addActionListener(this);
     warning_dialog.addWindowListener(this);
   }

//Public Methods
////////////////

   /**
     Displays SOCKS configuartion dialog.
     <p>
     Returns initialised proxy object, or null if user cancels dialog
     by either pressing Cancel or closing the dialog window.
    */
   public Proxy getProxy(){
      mode = COMMAND_MODE;
      pack();
      show();
      return proxy;
   }

   /**
     Initialises dialog to given proxy and displays SOCKS configuartion dialog.
     <p>
     Returns initialised proxy object, or null if user cancels dialog
     by either pressing Cancel or closing the dialog window.
    */
    public Proxy getProxy(Proxy p){
      if(p != null){
         doInit(p);
      }
      mode = COMMAND_MODE;
      pack();
      show();
      return proxy;
   }

// WindowListener Interface
/////////////////////////////////
   public void windowActivated(java.awt.event.WindowEvent e){
   }
   public void windowDeactivated(java.awt.event.WindowEvent e){
   }
   public void windowOpened(java.awt.event.WindowEvent e){
   }
   public void windowClosing(java.awt.event.WindowEvent e){
      Window source = e.getWindow();
      if(source == this){
         onCancel();
      }else if(source == warning_dialog){
         onDismiss();
      }
   }
   public void windowClosed(java.awt.event.WindowEvent e){
   }
   public void windowIconified(java.awt.event.WindowEvent e){
   }
   public void windowDeiconified(java.awt.event.WindowEvent e){
   }

//ActionListener interface
///////////////////////////
   public void actionPerformed(ActionEvent ae){

      Object source = ae.getSource();

      if(source == cancel_button )
         onCancel();
      else if(source == add_button || source == direct_text)
         onAdd();
      else if(source == remove_button)
         onRemove();
      else if(source == dismiss_button)
         onDismiss();
      else if(source == ok_button || source instanceof TextField)
         onOK();
   }
//ItemListener interface
////////////////////////
   public void itemStateChanged(ItemEvent ie){
      Object source = ie.getSource();
      //System.out.println("ItemEvent:"+source);
      if(source == socks5radio || source == socks4radio)
         onSocksChange();
      else if(source == up_check)
         onUPChange();

   }
//Runnable interface
////////////////////
   /**
     Resolves proxy address in other thread, to avoid
     annoying blocking in GUI thread.
   */
   public void run(){

      if(!initProxy()){
         //Check if we have been aborted
         if(mode != OK_MODE) return;
         if(net_thread != Thread.currentThread()) return;

         mode = COMMAND_MODE;
         warning_label.setText("Look up failed.");
         warning_label.invalidate();
         return;
      }

      //System.out.println("Done!");
      while(!warning_dialog.isShowing())
           ; /* do nothing*/;

      warning_dialog.dispose();
      //dispose(); //End Dialog
   }

//Private Methods
///////////////////
   private void onOK(){
      host = host_text.getText().trim();
      user = user_text.getText();
      password = password_text.getText();

      if(host.length() == 0){
        warn("Proxy host is not set!");
        return;
      }
      if(socks_group.getSelectedCheckbox() == socks4radio){
        if(user.length() == 0){
          warn("User name is not set");
          return;
        }

      }else{
        if(up_check.getState()){
          if(user.length() == 0){
            warn("User name is not set.");
            return;
          }
          if(password.length()==0){
            warn("Password is not set.");
            return;
          }
        }else if(!none_check.getState()){
          warn("Please select at least one Authentication Method.");
          return;
        }
      }

      try{
         port = Integer.parseInt(port_text.getText());
      }catch(NumberFormatException nfe){
         warn("Proxy port is invalid!");
         return;
      }

      mode = OK_MODE;

      if(useThreads){
         net_thread = new Thread(this);
         net_thread.start();

         info("Looking up host: "+host);
         //System.out.println("Info returned.");
      }else if(!initProxy()){
           warn("Proxy host is invalid.");
           mode = COMMAND_MODE;
         }

      if(mode == OK_MODE) dispose();
   }

   private void onCancel(){
      //System.out.println("Cancel");
      proxy = null;
      dispose();
   }

   private void onAdd(){
      String s = direct_text.getText();
      s.trim();
      if(s.length() == 0 ) return;
      //Check for Duplicate
      String[] direct_hosts = direct_list.getItems();
      for(int i=0;i<direct_hosts.length;++i)
         if(direct_hosts[i].equals(s)) return;

      direct_list.add(s);
      ir.add(s);
   }
   private void onRemove(){
      int index = direct_list.getSelectedIndex();
      if(index < 0) return;
      ir.remove(direct_list.getItem(index));
      direct_list.remove(index);
      direct_list.select(index);
   }

   private void onSocksChange(){
      Object selected = socks_group.getSelectedCheckbox();
      if(selected == socks4radio){
         user_text.setEnabled(true);
         password_text.setEnabled(false);
         none_check.setEnabled(false);
         up_check.setEnabled(false);
      }else{
         if(up_check.getState()){
           user_text.setEnabled(true);
           password_text.setEnabled(true);
         }else{
           user_text.setEnabled(false);
           password_text.setEnabled(false);
         }
         none_check.setEnabled(true);
         up_check.setEnabled(true);
      }
      //System.out.println("onSocksChange:"+selected);
   }
   private void onUPChange(){
      //System.out.println("onUPChange");
      if(up_check.getState()){
        user_text.setEnabled(true);
        password_text.setEnabled(true);
      }else{
        user_text.setEnabled(false);
        password_text.setEnabled(false);
      }
   }
   private void onDismiss(){
     warning_dialog.dispose();
     if(mode == OK_MODE){
       mode = COMMAND_MODE;
       if(net_thread!=null) net_thread.interrupt();
     }
   }

   private void doInit(Proxy p){
     if(p.version == 5){
       socks_group.setSelectedCheckbox(socks5radio);
       onSocksChange();
       if(((Socks5Proxy)p).getAuthenticationMethod(0)!=null)
          none_check.setState(true);
       UserPasswordAuthentication auth = (UserPasswordAuthentication) 
                                  ((Socks5Proxy)p).getAuthenticationMethod(2);
       if(auth!=null){
          user_text.setText(auth.getUser());
          password_text.setText(auth.getPassword());
          up_check.setState(true);
          onUPChange();
       }
     }else{
       socks_group.setSelectedCheckbox(socks4radio);
       onSocksChange();
       user_text.setText(((Socks4Proxy)p).user);
     }
     ir = (InetRange)(p.directHosts.clone());
     String[] direct_hosts = ir.getAll();
     direct_list.removeAll();
     for(int i=0;i<direct_hosts.length;++i)
       direct_list.add(direct_hosts[i]);

     host_text.setText(p.proxyIP.getHostName());
     port_text.setText(""+p.proxyPort);

   }

   private boolean initProxy(){
      try{
         if(socks_group.getSelectedCheckbox() == socks5radio){
            proxy = new Socks5Proxy(host,port);
            if(up_check.getState())
              ((Socks5Proxy)proxy).setAuthenticationMethod(2,
                           new UserPasswordAuthentication(user,password));
            if(!none_check.getState())
              ((Socks5Proxy)proxy).setAuthenticationMethod(0,null);
         }
         else
            proxy = new Socks4Proxy(host,port,user);
      }catch(java.net.UnknownHostException uhe){
         return false;
      }
      proxy.directHosts = ir;
      return true;
   }
   private void info(String s){
     msgBox("Info",s);
   }

   private void warn(String s){
     msgBox("Warning",s);
   }
   
   private void msgBox(String title, String message){
     warning_label.setText(message);
     warning_label.invalidate();
     warning_dialog.setTitle(title);
     warning_dialog.pack();
     warning_dialog.show();
   }

/*======================================================================
  Form:
  Table:
          +---+-------+---+---+
          |   |       |   |   |
          +---+-------+---+---+
          |           |       |
          +---+-------+-------+
          |   |       |       |
          +---+-------+-------+
          |   |       |       |
          +---+-------+-------+
          |           |       |
          +-----------+-------+
          |           |       |
          |           +---+---+
          |           |   |   |
          +-----------+---+---+
          |           |   |   |
          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
*/

   void guiInit(){
      //Some default names used
      Label label;
      Container container;
      Font font;

      GridBagConstraints c = new GridBagConstraints();

      font = new Font("Dialog",Font.PLAIN,12);
      
      container = this;
      //container = new Panel();
      container.setLayout(new GridBagLayout());
      container.setFont(font);
      container.setBackground(SystemColor.menu);
      c.insets = new Insets(3,3,3,3);
      
      c.gridx=0; c.gridy=0;
      c.gridwidth=1; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTHEAST;
      label = new Label("Host:");
      container.add(label,c);
      
      c.gridx=1; c.gridy=0;
      c.gridwidth=2; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTHWEST;
      host_text = new TextField("socks-proxy",15);
      container.add(host_text,c);
      
      c.gridx=3; c.gridy=0;
      c.gridwidth=1; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTHEAST;
      label = new Label("Port:");
      container.add(label,c);
      
      c.gridx=4; c.gridy=0;
      c.gridwidth=1; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTHWEST;
      port_text = new TextField("1080",5);
      container.add(port_text,c);
      
      c.gridx=0; c.gridy=1;
      c.gridwidth=3; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTH;
      socks4radio = new Checkbox("Socks4",socks_group,false);
      //1.0 compatible code
      //socks4radio = new Checkbox("Socks4",false);
      //socks4radio.setCheckboxGroup(socks_group);
      socks4radio.setFont(new Font(font.getName(),Font.BOLD,14));
      container.add(socks4radio,c);
      
      c.gridx=3; c.gridy=1;
      c.gridwidth=2; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTH;
      socks5radio = new Checkbox("Socks5",socks_group,true);
      //1.0 compatible code
      //socks5radio = new Checkbox("Socks5",true);
      //socks5radio.setCheckboxGroup(socks_group);
      socks5radio.setFont(new Font(font.getName(),Font.BOLD,14));
      container.add(socks5radio,c);
      
      c.gridx=0; c.gridy=2;
      c.gridwidth=1; c.gridheight=1;
      c.anchor=GridBagConstraints.EAST;
      label = new Label("User Id:");
      container.add(label,c);
      
      c.gridx=1; c.gridy=2;
      c.gridwidth=2; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTHWEST;
      user_text = new TextField("",15);
      user_text.setEnabled(false);
      container.add(user_text,c);
      
      c.gridx=3; c.gridy=2;
      c.gridwidth=2; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTH;
      label = new Label("Authentication");
      label.setFont(new Font(font.getName(),Font.BOLD,14));
      container.add(label,c);
      
      c.gridx=0; c.gridy=3;
      c.gridwidth=1; c.gridheight=1;
      c.anchor=GridBagConstraints.EAST;
      label = new Label("Password:");
      container.add(label,c);
      
      c.gridx=1; c.gridy=3;
      c.gridwidth=2; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTHWEST;
      password_text = new TextField("",15);
      password_text.setEchoChar('*');
      password_text.setEnabled(false);
      //password_text.setEchoCharacter('*');//1.0
      container.add(password_text,c);
      
      c.gridx=3; c.gridy=3;
      c.gridwidth=2; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTHWEST;
      none_check = new Checkbox("None",true);
      container.add(none_check,c);
      
      c.gridx=0; c.gridy=4;
      c.gridwidth=3; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTH;
      label = new Label("Direct Hosts");
      label.setFont(new Font(font.getName(),Font.BOLD,14));
      container.add(label,c);
      
      c.gridx=3; c.gridy=4;
      c.gridwidth=2; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTHWEST;
      up_check = new Checkbox("User/Password",false);
      container.add(up_check,c);
      
      c.gridx=0; c.gridy=5;
      c.gridwidth=3; c.gridheight=2;
      c.anchor=GridBagConstraints.NORTHWEST;
      c.fill = GridBagConstraints.BOTH;
      direct_list = new List(3);
      container.add(direct_list,c);
      
      c.gridx=3; c.gridy=5;
      c.gridwidth=2; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTHWEST;
      gssapi_check = new Checkbox("GSSAPI",false);
      gssapi_check.setEnabled(false);
      container.add(gssapi_check,c);
      
      c.gridx=0; c.gridy=7;
      c.gridwidth=3; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTHWEST;
      direct_text = new TextField("",25);
      container.add(direct_text,c);
      
      c.gridx=3; c.gridy=7;
      c.gridwidth=1; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTH;
      add_button = new Button("Add");
      container.add(add_button,c);
      
      c.gridx=3; c.gridy=6;
      c.gridwidth=1; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTH;
      remove_button = new Button("Remove");
      container.add(remove_button,c);
      
      c.gridx=1; c.gridy=8;
      c.gridwidth=1; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTH;
      cancel_button = new Button("Cancel");
      container.add(cancel_button,c);
      
      c.gridx=0; c.gridy=8;
      c.gridwidth=1; c.gridheight=1;
      c.anchor=GridBagConstraints.NORTHWEST;
      ok_button = new Button("OK");
      container.add(ok_button,c);

      //up_check.setEnabled(false);

      //Warning Dialog
      dismiss_button = new Button("Dismiss");
      warning_label  = new Label("",Label.CENTER);
      warning_label.setFont(new Font("Dialog",Font.BOLD,15));

      Panel p = new Panel();
      p.add(dismiss_button);
      warning_dialog.add(p,BorderLayout.SOUTH);
      warning_dialog.add(warning_label,BorderLayout.CENTER);
      warning_dialog.setResizable(false);
   }//end guiInit

/*
// Main 
////////////////////////////////////
   public static void main(String[] args) throws Exception{
      Frame f = new Frame("Test for SocksDialog");
      f.add("Center", new Label("Fill the Dialog"));
      SocksDialog socksdialog = new SocksDialog(f);
      f.pack();
      f.show();
      f.addWindowListener(socksdialog);
      Proxy p = socksdialog.getProxy();
      System.out.println("Selected: "+p);
   }
*/

}//end class
