package talk.swingui;

import javax.swing.*;
import javax.swing.text.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;

import talk.*;

/** Implements the ITalkView interface.  This object will be created
    by ITalkApp when the createView method is called.
    
    @see ITalkApp#createView
    @see ITalkView
*/
public class TalkView extends JInternalFrame implements ITalkView {
  private ITalkSession mySession;
  private AppendEditor myLocalEditor;
  private JTextArea myRemoteDisplay;
  private JLabel myStatusLine;
  private JButton myDisconnectBn;
  private boolean myRemoteChanged = false;

  private java.util.logging.Logger logger = Logger.getLogger();

  class TalkListener implements DocumentListener {
    public void insertUpdate(DocumentEvent e) {
      String input;
      try {
	input = e.getDocument().getText(e.getOffset(), e.getLength());
	mySession.sendUserInput(input);
      } catch (BadLocationException ex) {}
    }
    public void removeUpdate(DocumentEvent e) {
      // always removes the last character
      mySession.sendBackspace();
    }
    public void changedUpdate(DocumentEvent e) {}
  }
  
  public TalkView(Dimension dim, ITalkSession session, String title) {
    super(title,
	  true,			// resizable
	  true,			// closable
	  true,			// maximizable
	  true);		// iconifiable
    
    mySession = session;

    // create local editor
    myLocalEditor = new AppendEditor();
    myLocalEditor.getDocument().addDocumentListener(new TalkListener());
    myLocalEditor.setEnabled(false);
    myLocalEditor.setLineWrap(true);
    myLocalEditor.setWrapStyleWord(true);
    JScrollPane localScroll = new JScrollPane(myLocalEditor);

    // create remote display
    myRemoteDisplay = new JTextArea();
    myRemoteDisplay.setEnabled(false);
    myRemoteDisplay.setLineWrap(true);
    myRemoteDisplay.setWrapStyleWord(true);
    JScrollPane remoteScroll = new JScrollPane(myRemoteDisplay);
    
    // make window always show bottom line
    remoteScroll.getVerticalScrollBar().getModel().addChangeListener(
      new ChangeListener() {
      public void stateChanged(ChangeEvent ec) {
	// Only want to force scroll bar to the end if contents were
	// changed.  We want to let the user scroll back.
	if (myRemoteChanged) {
	  myRemoteChanged = false;
	  BoundedRangeModel sb = (BoundedRangeModel)ec.getSource();
	  int max = sb.getMaximum();
	  int ext = sb.getExtent();
	  sb.setValue(max-ext);
	}
      }
    });

    // a pane to hold both local and remote displays
    JPanel displayPane = new JPanel(new GridLayout(2, 1));
    displayPane.add(localScroll);
    displayPane.add(remoteScroll);

    // disconnect and status
    myDisconnectBn = new JButton("Disconnect");
    myDisconnectBn.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
	mySession.disconnect();
      }
    });
    myDisconnectBn.setEnabled(false);
    
    myStatusLine = new JLabel(" ");

    JPanel bottomPane = new JPanel(new BorderLayout());
    bottomPane.add(myDisconnectBn, BorderLayout.WEST);
    bottomPane.add(myStatusLine, BorderLayout.CENTER);

    // now lay things out
    getContentPane().add(displayPane, BorderLayout.CENTER);
    getContentPane().add(bottomPane, BorderLayout.SOUTH);

    // disconnect if user closes this window
    addInternalFrameListener(new InternalFrameAdapter() {
      public void internalFrameClosing(InternalFrameEvent e) {
	mySession.disconnect();
      }
    });

    pack();
    setSize(dim);
    show();
  }

  /*
    ITalkView implementation
  */

  class RunTask implements Runnable {
    protected String myParam = "";
    public RunTask() {}
    public RunTask(String param) { myParam = param; }
    public void run() {}
  }
  
  /** Call when a connection has been established with someone */
  public void connected(String withWho) {
    Runnable task = new Runnable() {
      public void run() {
	myLocalEditor.setEnabled(true);
	myDisconnectBn.setEnabled(true);
      }
    };
    SwingUtilities.invokeLater(task);
    displayStatusMessage("Connection established with "+withWho);
  }

  /** Call when a connection has been closed */
  public void disconnected() {
    Runnable task = new Runnable() {
      public void run() {
	myLocalEditor.setEnabled(false);
	myDisconnectBn.setEnabled(false);
      }
    };
    SwingUtilities.invokeLater(task);    
    displayStatusMessage("Connection closed");
  }

  /** Call when a connect attempt fails */
  public void connectFailed(String why) {
    displayErrorMessage("Failed to connect: "+why);
  }

  /** Display a status message to local user */
  public void displayStatusMessage(String msg) {
    Runnable task = new RunTask(msg) {
      public void run() {
	// I add the space at the end to make sure there's always some text
	// otherwise the label disappears
	myStatusLine.setText(myParam+" ");
      }
    };
    SwingUtilities.invokeLater(task);        
  }

  /** Display an error message to local user */
  public void displayErrorMessage(String msg) {
    Runnable task = new RunTask(msg) {
      public void run() {
	JOptionPane.showMessageDialog(TalkView.this, myParam,
				      "Error: "+TalkView.this.getTitle(),
				      JOptionPane.ERROR_MESSAGE);	
      }
    };
    SwingUtilities.invokeLater(task);    
  }

  /** Display input received from remote client */
  public void displayRemoteInput(String input) {    
    Document doc = myRemoteDisplay.getDocument();
    int endPos = doc.getEndPosition().getOffset();
    try {
      doc.insertString(endPos-1, input,
		       null);	// FIXME! probably want to use attributes
      myRemoteChanged = true;
    } catch (BadLocationException e) {
      logger.severe("Got BadLocationException in displayRemoteInput: "+e);
    }
  }

  /** Remote client hit backspace */
  public void displayBackspace() {
    Document doc = myRemoteDisplay.getDocument();
    try {
      doc.remove(doc.getEndPosition().getOffset() - 2, 1);
      myRemoteChanged = true;
    } catch (BadLocationException e) {
      logger.severe("Got BadLocationException in displayBackspace: "+e);
    } 
  }

  // test TalkView
  public static void main(String[] args) {
    JFrame frame = new JFrame("TestTalkView");
    JButton newConnectionBn = new JButton("New Connection");
    JDesktopPane desktop = new JDesktopPane();
    
    frame.getContentPane().setLayout(new BorderLayout());
    frame.getContentPane().add(newConnectionBn, BorderLayout.NORTH);
    frame.getContentPane().add(desktop, BorderLayout.CENTER);

    class CnctListener implements ActionListener {
      private JDesktopPane myDesktop;
      public CnctListener(JDesktopPane dsk) { myDesktop = dsk; }
      public void actionPerformed(ActionEvent e) {
	TestTalkSession sess = new TestTalkSession(myDesktop);
	sess.connect("lucifer", "hades.com", "ttyp0", "ajenkins", "localhost");
      }
    }
    newConnectionBn.addActionListener(new CnctListener(desktop));

    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
	System.exit(0);
      }
    });

    frame.pack();
    frame.setSize(500, 500);
    frame.show();
  } // main      
}

class TestTalkSession implements ITalkSession {
  private ITalkView myView;
  private JDesktopPane myDesktop;
      
  TestTalkSession(JDesktopPane desktop) {
    myDesktop = desktop;
  }

  public void connect(String ruser, String rhost, String rtty,
		      String luser, String lhost) {
    myView = new TalkView(new Dimension(400, 300), this,
			  ruser+"@"+rhost+" "+rtty);
    myDesktop.add((TalkView)myView);
    myView.connected(ruser+"@"+rhost);

    Thread th = new Thread() {
      public void run() {
	String input = "Now is the time for all good men "+
	  "to come to the aid of their party\n";
	for (int j = 0; j < 10; j++) {
	  for (int i = 0; i < input.length(); i++) {
	    myView.displayRemoteInput(input.substring(i, i+1));
	    try { sleep(100); } catch (InterruptedException e) {}
	  }
	}
	disconnect();
      }
    };
    th.start();
  }

  public void disconnect() {
    System.out.println("Disconnected");
    myView.disconnected();
  }

  public boolean isConnected() { return true; }

  public void sendUserInput(String input) {
    System.out.println("sent user input: "+input);
  }
  public void sendBackspace() {
    System.out.println("sent backspace");
  }
} // class TestTalkSession

/** A restricted version of JTextArea.  It only allows text to be
    appended to the end of the buffer, or deleted one character at a
    time from the end. */
class AppendEditor extends JTextArea {
  public AppendEditor() {
    super();
  }
  public AppendEditor(int rows, int cols) {
    super(rows, cols);
  }
  
  protected Document createDefaultModel() {
    return new AppendDocument();
  }

  protected class AppendDocument extends PlainDocument {
    public AppendDocument() { super(); }
    
    public void insertString(int offset,
			     String str,
			     AttributeSet a)
      throws BadLocationException
    {
      if (offset != getEndPosition().getOffset() - 1) {
	throw new BadLocationException("insert not at end", offset);
      } else
	super.insertString(offset, str, a);
    }
      
    public void remove(int offset, int len) throws BadLocationException {
      if (len > 1 ||
	  (len == 1 && offset != getEndPosition().getOffset() - 2)) {
	throw new BadLocationException("bad remove position or length",
				       offset);
      }
      else
	super.remove(offset, len);
    }
  }
}
