import java.io.*;
import java.lang.*;
import java.lang.reflect.*;
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.util.*;

/**
 * @version  21 sep 1998
 * @author   Philippe Chassignet, Ecole Polytechnique
 * @see      Output
 * @see      TD
 */
final class TextOutput extends OutputStream {
  TextArea outArea;
  Method init;

  public TextOutput(TextArea out) {
    outArea = out;
    init = null;
  }

  public TextOutput(Method m) {
    outArea = null;
    init = m;
  }

  public void write(int b) {
	byte data[] = {(byte)b};
	write(data, 0, 1);
  }
  
  public void write(byte b[], int off, int len) {
    if ( outArea == null )
      try {
        outArea = (TextArea)init.invoke(null, new Object[0]);
      }
      catch (InvocationTargetException e) {}
      catch (IllegalAccessException e) {}
    outArea.append(new String(b, off, len));
  }

}

/**
 * @version  6 aug 1998
 * @author   Philippe Chassignet, Ecole Polytechnique
 * @see      Input
 * @see      TD
 */
final class TextInput extends InputStream {
  static char[] eol = { '\n', (char)0 };
  StringBuffer buffer = new StringBuffer();
  int pos = 0;
  TextArea input;
  Method init;

  public TextInput(TextArea in) {
    input = in;
    init = null;
  }

  public TextInput(Method m) {
    input = null;
    init = m;
  }

  public synchronized void reset() {
    buffer.setLength(0);
    pos = 0;
  }

  public synchronized int read() throws IOException {
    if ( input == null )
      try {
        input = (TextArea)init.invoke(null, new Object[0]);
      }
      catch (InvocationTargetException e) {}
      catch (IllegalAccessException e) {}
    try {	
	  if ( pos >= buffer.length() ) {
	    if (pos > 0)
	      reset();
	    input.setVisible(true);
	    input.requestFocus();
	    wait();
	  }
	  char c = buffer.charAt(pos++);
	  if (c != 0)
	    return c;
	}
	catch (InterruptedException e) {}
    return -1;
  }

  public synchronized void put(String s) {
    buffer.append(s).append(eol);
    notify();
  }

  public int available() {
    return buffer.length() - pos;
  }

}

/**
 * @version  6 aug 1998
 * @author   Philippe Chassignet, Ecole Polytechnique
 * @see      Output
 * @see      Input
 */
public abstract class TD extends MacLib {
  private static Toolkit toolkit = Toolkit.getDefaultToolkit();

  private static Clipboard clipboard = null;
  private static Frame textFrame = null;
  private static TextArea outputArea = null;
  private static TextArea inputArea = null;
  private static TextArea focusArea = null;
  private static int accelKey;
  private static Font textFont = new Font("Monospaced", Font.PLAIN, 10);
  
//  private static Dialog confirmationDialog = null;
  private static boolean canExit = true;

  private static TextOutput textOutput = initOutput();
  private static TextInput textInput = initInput();;

  public static final Output stdOut = new Output(textOutput);
  public static final Output sysOut = new Output(System.out);
  public static final Output sysErr = new Output(System.err);
  public static final Input stdIn = new Input(textInput, stdOut);
  public static final Input sysIn = new Input(System.in, sysOut);
  public static final char EOF = Input.EOF_CHAR;
  
  public TD() { System.err.println("new TD() !"); }

  private static void checkText() {
    if( textFrame == null ) {
      initText();
      setText(textFrame);
    }
  }

  private static final TextOutput initOutput() {
    try {
      return new TextOutput((Class.forName("TD")).getMethod( "getOutput", new Class[0] ));
    }
    catch (ClassNotFoundException e) {}
    catch (NoSuchMethodException e) {}
    return null;
    }

  public static final TextArea getOutput() {
    checkText();
    return outputArea;
  }

  private static final TextInput initInput() {
    try {
      return new TextInput((Class.forName("TD")).getMethod( "getInput", new Class[0] ));
    }
    catch (ClassNotFoundException e) {}
    catch (NoSuchMethodException e) {}
    return null;
    }

  public static final TextArea getInput() {
    checkText();
    return inputArea;
  }

  private static final void enterLine() {
    outputArea.append(inputArea.getText());
    outputArea.append("\n");
    textInput.put(inputArea.getText());
    inputArea.setText("");
  }

  private static final void eofLine() {
    outputArea.append(inputArea.getText());
    outputArea.append("<EOF>\n");
    textInput.put(inputArea.getText()+String.valueOf((char)Input.EOF_MARK));
    inputArea.setText("");
  }

  private static final void doCopy(TextArea area) {
    StringSelection content = new StringSelection(area.getSelectedText());
    clipboard.setContents(content, content);
  }

  private static final void doPaste() {
    String s = "";
    try {
      s = (String)clipboard.getContents(inputArea).getTransferData(DataFlavor.stringFlavor);
    }
    catch (Exception ex) {}
    String b = inputArea.getText();
    if( focusArea != inputArea )
      inputArea.select(b.length(), b.length());
    String e = b.substring(inputArea.getSelectionEnd());
    b = b.substring(0, inputArea.getSelectionStart()).concat(s);
    int start = 0;
    for (int i = 0; i < b.length(); i++)
      if ((b.charAt(i) == '\n')||(b.charAt(i) == '\r')) {
        s = b.substring(start, i);
        outputArea.append(s);
        outputArea.append("\n");
        textInput.put(s);
        start = i+1;
      }
      s = b.substring(start).concat(e);
      inputArea.setText(s);
  }

  private static final void initText() {
    clipboard = toolkit.getSystemClipboard();
    accelKey = toolkit.getMenuShortcutKeyMask();

    textFrame = new Frame("Text");
    textFrame.setSize(500, 480);
    textFrame.setLocation(10, 30);

    final MenuBar menuBar = new MenuBar();

    outputArea = new TextArea("", 10, 80, TextArea.SCROLLBARS_VERTICAL_ONLY);
    outputArea.setFont(textFont);
    outputArea.setBackground(Color.white);
    outputArea.setForeground(Color.black);
    outputArea.setEditable(true);
    outputArea.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent e) {
        if ((e.getModifiers() & accelKey) == 0) {
          if ( e.getKeyCode() == KeyEvent.VK_ENTER )
            enterLine();
          else if ( e.getKeyCode() == KeyEvent.VK_ESCAPE ) {
            eofLine();
          }
          else
            inputArea.append(String.valueOf(e.getKeyChar()));
          e.consume();
          inputArea.requestFocus();
        }
      }
    });
    outputArea.addFocusListener(new FocusAdapter() {
      public void focusGained(FocusEvent e) {
        focusArea = outputArea;
      }
    });

    inputArea = new TextArea("", 1, 80, TextArea.SCROLLBARS_VERTICAL_ONLY);
    inputArea.setForeground(Color.black);
    inputArea.setEditable(true);
    inputArea.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent e) {
        if ((e.getModifiers() & accelKey) == 0) {
          if ( e.getKeyCode() == KeyEvent.VK_ENTER ) {
            enterLine();
            e.consume();
          }
          else if ( e.getKeyCode() == KeyEvent.VK_ESCAPE ) {
            eofLine();
            e.consume();
          }
        }
      }
    });
    inputArea.addFocusListener(new FocusAdapter() {
      public void focusGained(FocusEvent e) {
        focusArea = inputArea;
      }
    });

    Menu editMenu = new Menu("Edit");
    menuBar.add(editMenu);

    MenuItem copyItem = new MenuItem("Copy", new MenuShortcut('c'));
    copyItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        doCopy(focusArea);
      }
    });
    editMenu.add(copyItem);

    MenuItem pasteItem = new MenuItem("Paste", new MenuShortcut('v'));
    pasteItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        doPaste();
      }
    });
    editMenu.add(pasteItem);

    MenuItem clearItem = new MenuItem("Clear", new MenuShortcut('k'));
    clearItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        clearScreen();
      }
    });
    editMenu.add(clearItem);

    textFrame.setLayout(new GridBagLayout());
    GridBagConstraints c = new GridBagConstraints();
    c.fill = GridBagConstraints.BOTH;
    c.gridy = 0;
    c.gridheight = 5;
    c.weighty = 1;
    c.gridx = 0;
    c.gridwidth = 6;
    c.weightx = 1;
    textFrame.add(outputArea, c);
    c.gridy = 5;
    c.gridheight = 1;
    c.weighty = 0;
    c.gridx = 0;
    c.gridwidth = 1;
    c.weightx = 0;
    textFrame.add(new Label("Input :"), c);
    c.gridx = 1;
    c.gridwidth = 5;
    textFrame.add(inputArea, c);

    textFrame.setMenuBar(menuBar);
    textFrame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        textFrame.setVisible(false);
      }
    });
    textFrame.setVisible(true);
  }

  public static final void initTD() {
    initMacLib();
    stdOut.flush();
    sysOut.flush();
    sysErr.flush();
    if( textFrame != null ) {
      outputArea.setText("");
      inputArea.setText("");
      textInput.reset();
      stdIn.clearLine();
      stdIn.resetEOF();
    }
    canExit = false;
  }
  
  public static final void exit() {
    if (canExit)
      System.exit(0);
  }
/*
  public static void exit() {
    if ( textFrame != null ) {
      initDialog();
      try {
        synchronized(confirmationDialog) {
          confirmationDialog.wait();
        }
      }
      catch (Exception e) {
        System.err.println(">Exit confirmation : " + e);
      }
      confirmationDialog.dispose();
      confirmationDialog = null;
      textFrame.dispose();
      textFrame = null;
    }
    System.err.println("DONE");
  }
  
  private static void initDialog() {
    confirmationDialog = new Dialog(new Frame(),
                                    "Program terminated",
                                    false);
    Label confirmationLabel = new Label("Close Text window ?");
    final Button confirmationOkButton = new Button("Ok");
    Color backgroundColor = new Color(230, 216, 174);
    confirmationDialog.setBackground(backgroundColor);

    ActionListener confirmationOkAction = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        synchronized(confirmationDialog) {
          confirmationDialog.notify();
        }
      }
    };

    confirmationOkButton.addActionListener(confirmationOkAction);
      
    Font guiFont = new Font("SansSerif", Font.BOLD, 12);
    confirmationLabel.setFont(guiFont);
    confirmationOkButton.setFont(guiFont);
    confirmationDialog.setSize(250, 90);
    confirmationDialog.setLocation(10, 30);
    confirmationDialog.setLayout(new GridBagLayout());
      
    GridBagConstraints c = new GridBagConstraints();
    c.insets = new Insets(5, 5, 5, 5);
    c.gridx = 0; c.gridy = 0;
    c.gridwidth = 1; c.gridheight = 1;
    c.weightx = 1.0; c.weighty = 0.0;
    c.fill = GridBagConstraints.BOTH;
    confirmationDialog.add(confirmationLabel, c);
    c.weightx = 0.0; c.weighty = 0.0;
    c.fill = GridBagConstraints.NONE;
    c.anchor = GridBagConstraints.EAST;
    c.gridx = 1; c.gridy = 1;
    c.gridwidth = 1;
    confirmationDialog.add(confirmationOkButton, c);
    
    confirmationDialog.show();
  }
*/
  
  // Synchronization ???
  private static final void sync() {
    toolkit.sync();
    try {
      Thread.currentThread().sleep(20);
    }
    catch (Exception e) {
    }
  }
  
  // Texte
  public static final void clearScreen() {
    if( textFrame != null ) {
      outputArea.setText("");
//      sync();
    }
  }

// Output

  public static final Output rewriteFile(String fileName) {
    try {
      return new Output(fileName);
    }
    catch (IOException e) {
      System.err.println("can't rewrite file '" + fileName + "' : " + e);
      return null;
    }
  }
    
  public static final void close(Output output) {
    output.close();
  }

  public static final void write(char c) {
    stdOut.write(c);
    sync();
  }

  public static final void write(Output output, char c) {
    output.write(c);
    sync();
  }

  public static final void write(String s) {
    stdOut.write(s);
    sync();
  }

  public static final void write(Output output, String s) {
    output.write(s);
    sync();
  }

  public static final void write(char buf[], int off, int len) {
    stdOut.write(buf, off, len);
    sync();
  }

  public static final void write(Output output, char buf[], int off, int len) {
    output.write(buf, off, len);
    sync();
  }

  public static final void writeln(String s) {
    stdOut.write(s);
    stdOut.println();
    sync();
  }

  public static final void writeln(Output output, String s) {
    output.write(s);
    output.println();
    sync();
  }

  public static final void writeln() {
    stdOut.println();
    sync();
  }

  public static final void writeln(Output output) {
    output.println();
    sync();
  }

  public static final void flush() {
    stdOut.flush();
  }

  public static final void flush(Output output) {
    output.flush();
  }

// Input

  public static final Input resetFile(String fileName) {
    try {
      return new Input(fileName);
    }
    catch (FileNotFoundException e) {
      System.err.println("can't reset file '" + fileName + "' : " + e);
      return null;
    }
  }

  public static final void close(Input input) {
    input.close();
  }

  public static final boolean eof() {
    return stdIn.eof();
  }

  public static final boolean eof(Input input) {
    return input.eof();
  }

  public static final void clearEOF() {
    stdIn.resetEOF();
  }
  
  public static final void clearEOF(Input input) {
    input.resetEOF();
  }
  
  public static final char getChar() {
    return stdIn.getChar();
  }

  public static final char getChar(Input input) {
    return input.getChar();
  }

  public static final String getToken() {
    return stdIn.getToken();
  }

  public static final String getToken(Input input) {
    return input.getToken();
  }

  public static final String getLine() {
    return stdIn.getLine();
  }

  public static final String getLine(Input input) {
    return input.getLine();
  }

  public static final String[] tokenize(String s) {
    StringTokenizer strtok = new StringTokenizer(s);
    int n = strtok.countTokens();
    String[] tokens = new String[n];
    for (int i = 0; i < n; i++)
      tokens[i] = strtok.nextToken();
    return tokens;
  }
  
}

