// java telnet hostname [port]

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;

// Terminal wyświetla tekst w oknie przewijając je w razie potrzeby
class Terminal extends Canvas {
  private int charWidth, charHeight;  // Wielkość czcionki
  private String[] text;  // text[0] to aktualny wiersz na dole
  private final int margin=4;  // Przestrzeń wokół krawędzi okna (piksele)
  private final int lines=50;  // Liczba wierszy do zapisania

  // Konstruktor; ustawienie początkowej wielkości (w znakach) i czcionki
  Terminal() {
    charHeight=12;
    setFont(new Font("Monospaced", Font.PLAIN, charHeight));
    charWidth=getFontMetrics(getFont()).stringWidth(" ");
    text=new String[lines];
    for (int i=0; i<lines; ++i)
      text[i]="";
    setSize(80*charWidth+margin*2, 25*charHeight+margin*2);
    requestFocus();

    // Użycie myszy do przejęcia ogniska
    addMouseListener(new MouseAdapter() {
      public void mousePressed(MouseEvent e) {
        requestFocus();
      }
    });
  }
 
  // Wyświetlenie i zapisanie znaku
  public void put(char c) {
    Graphics g=getGraphics();
    if (c=='\r') {  // Powrót
      for (int i=lines-1; i>0; --i)
        text[i]=text[i-1];
      text[0]="";
      update(g);  // Oczyszczenie i odświeżenie ekranu
    }
    else if (c==8 || c==127 || c==247)  // Backspace, delete, telnet EC
    {
      int len=text[0].length();
      if (len>0) {
        --len;
        text[0]=text[0].substring(0, len);
        g.setColor(getBackground());
        g.fillRect(len*charWidth+margin, getSize().height-margin-charHeight,
                   (len+1)*charWidth+margin, getSize().height-margin);
      }
    }
    else if (c=='\t') {  // Kolejna kolumna przemieszczona o 8 znaków
      text[0]+="        ";
      text[0].substring(0, text[0].length()&-8);
    }
    else if (c>=32 && c<127) {  // Dozwolone znaki
      g.drawString(""+c, margin+text[0].length()*charWidth,
                   getSize().height-margin);
      text[0]+=c;
    }
    g.dispose();
  }
 
  // Wyświetlenie tekstu
  public void paint(Graphics g) {
    int height=getSize().height;
    for (int i=0; i<lines; ++i)
      g.drawString(text[i], margin, height-margin-i*charHeight);
  }
}

// Wątek Receiver oczekuje na znaki z InputStream i wysyła je
// do Terminala. Także negocjacja opcji terminala.
class Receiver extends Thread {
  private InputStream in;
  private OutputStream out;
  private Terminal terminal;

  public Receiver(InputStream in, OutputStream out, Terminal terminal) {
    this.in=in;
    this.out=out;
    this.terminal=terminal;
    start();
  }
 
  // Odczyt i przesłanie znaków do terminala
  public void run() {
    while (true) {
      try {
        int c=in.read();
        if (c<0) {  // EOF
          System.out.println("Połączenie przerwane przez zdalny host");
          return;
        }
        else if (c==255) {  // Negocjacja opcji terminala (RFC 854)
          int c1=in.read();  // 253=do, 251=will
          int c2=in.read();  // opcja
          if (c1==253)  // do option, wysłanie "won't do option"
            out.write(new byte[] {(byte)255, (byte)252, (byte)c2});
          else if (c1==251) // will do option, wysłanie "don't do option"
            out.write(new byte[] {(byte)255, (byte)254, (byte)c2});
        }
        else
          terminal.put((char)c);
      }
      catch (IOException x) {
        System.out.println("Odbiornik: "+x);
      }
    }
  }
}
 
// TelnetWindow. Wysyła dane wprowadzone na klawiaturze z terminala do zdalnego gniazda
// i uruchamia odbiornik w celu odbioru znaków z gniazda i wyświetlenia ich
// w terminalu.
class TelnetWindow extends Frame {
  Terminal terminal;
  InputStream in;
  OutputStream out;

  // Konstruktor, brak innych metod
  TelnetWindow(String hostname, int port) {
    super("telnet "+hostname+" "+port);  // Ustawienie tytułu

    // Ustawienie okna
    add(terminal=new Terminal());

    // Zamknięcie okna
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        dispose();
        try {
          out.close();
        }
        catch (IOException x) {
          System.out.println("Zamykanie połączenia: "+x);
        }
      }
      public void windowClosed(WindowEvent e) {
        System.exit(0);
      }
    });
 
    // Obsługa kluczy
    terminal.addKeyListener(new KeyAdapter() {
      public void keyTyped(KeyEvent e) {
        char k=e.getKeyChar();
        try {
          terminal.put(k);
          out.write((int)k);
          if (k=='\r') {
            out.write('\n');  // Konwersja CR do CR-LF
            out.flush();
          }
        }
        catch (IOException x) {
          System.out.println("Wysyłanie: "+x);
        }
      }
    });

    try {

      // Otwarcie połączenia
      System.out.println("Otwieranie połączenia do "+hostname+" na porcie "+port);
      Socket socket=new Socket(hostname, port);
      InetAddress addr=socket.getInetAddress();
      System.out.println("Połączono z "+addr.getHostAddress());
      in=socket.getInputStream();
      out=socket.getOutputStream();

      // Wyświetlenie okna
      pack();
      setVisible(true);
 
      // Uruchomienie odbiornika
      new Receiver(in, out, terminal);
      System.out.println("Gotowy");
    }
    catch (UnknownHostException x) {
      System.out.println("Unknown host: "+hostname+" "+x);
      System.exit(1);
    }
    catch (IOException x) {
      System.out.println(x);
      System.exit(1);
    }
  }
}

// Główny program
public class telnet {
  public static void main(String[] argv) {

    // Analiza argumentów: telnet hostname port
    String hostname="";
    int port=23;
    try {
      hostname=argv[0];
      if (argv.length>1)
        port=Integer.parseInt(argv[1]);
    }
    catch (ArrayIndexOutOfBoundsException x) {
      System.out.println("Użycie: java telnet hostname [port]");
      System.exit(1);
    }
    catch (NumberFormatException x) {}
    TelnetWindow t1=new TelnetWindow(hostname, port);
  }
}


