// Réseau
// Projet 1
//
// Forum ftp
//
// Christophe Boyanique
// Emmanuel Pinard
// Mai 1999
//
//
// Programme principal: gestion applet et réseau

import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.net.*;
import java.io.*;
import java.util.Date;

public class forum extends Applet
{
  static int IO_OK    = 0;
  static int IO_ERROR = 1;
  
  private String prf_server = new String("imac.u-paris2.fr"); // Serveur ftp
  private int prf_port = 21;                                  // Port
  private String prf_login = new String("forum");             // User
  private String prf_pwd = new String("guest");               // Passwd

  private TextArea t_txt;    // Zone de texte
  private TextArea t_log;    // Fenêtre de log
  private Panel p_settings;  // Panel des boutons
  private Label l_from;
  private TextField t_from;
  private Label l_subject;
  private TextField t_subject;
  private Label l_forum;
  private List lst_forum;
  private Panel p_buttons;   // Panel des boutons
  private Button b_send;     // Bouton "Envoyer"
  private Button b_clear;    // Bouton "Effacer"

  public forum()
  {
  }

  public void init()
  {
    // Crée la zone texte de log:
    t_log  = new TextArea();
    t_log.setEditable(false);

    // Crée la zone texte du message:
    t_txt = new TextArea();

    // Crée le panel contenant les boutons:
    p_settings = new Panel();

    // Le panel contiendra 2x3 boutons:
    p_settings.setLayout(new GridLayout(2,3));

    // Crée le texte "From:"
    l_from = new Label("From:");

    // Crée la zone de texte "From:"
    t_from = new TextField(60);

    // Crée le texte "Subject:"
    l_subject = new Label("Subject:");

    // Crée la zone de texte "Subject:"
    t_subject = new TextField(60);

    // Crée le texte "Forum:"
    l_forum = new Label("Forum:");
    lst_forum = new List();

    // Ajoute les boutons:
    p_settings.add(l_from);
    p_settings.add(t_from);
    p_settings.add(l_forum);
    p_settings.add(l_subject);
    p_settings.add(t_subject);
    p_settings.add(lst_forum);

    // Crée le panel contenant les boutons:
    p_buttons = new Panel();

    // Le panel contiendra 4x1 boutons:
    p_buttons.setLayout(new GridLayout(1,2));

    // Ajoute un bouton "Envoyer":
    b_send = new Button("Envoyer");
    b_send.addActionListener(new evt_adaptateur(evt_adaptateur.SEND, new evt_delegue(), (Object)this));

    // Ajoute un bouton "Effacer":
    b_clear = new Button("Effacer");
    b_clear.addActionListener(new evt_adaptateur(evt_adaptateur.CLEAR, new evt_delegue(), (Object)this));

    // Ajoute les boutons:
    p_buttons.add(b_clear);
    p_buttons.add(b_send);

    setLayout(new GridLayout(4,1));
    add(t_log);
    add(p_settings);
    add(t_txt);
    add(p_buttons);
  }

  public void start()
  {
    get_forums_liste();
  }

  public void stop()
  {
  }

  // Cette methode recupere la liste des forums:
  // il faut recuperer la liste des repertoires dans ~/public_html
  private void get_forums_liste()
  {
    Socket       sock;
    Socket       datasock;
    InputStream  instream;
    InputStream  indatastream;
    String       reponse;
    String       ip;
    int          port;
    int          err;
    
    try
    {
      // Cree la socket et se connecte sur le serveur
      sock = new Socket(prf_server, prf_port);
      t_log.append("Connected to " + sock.toString() + "\n");

      // Recupere le flut d'entrée (input stream) de la socket
      instream = sock.getInputStream();

      // Lit la réponse du serveur
      reponse = read_answer_line(sock);
      repaint();
      if ( get_return_code(reponse) != 220 )
      {
        t_log.append("Erreur de connexion\n");
        repaint();
        sock.close();
        return;
      }

      // Envoie la commande USER pour s'identifier
      err = write_command_line(sock, "USER " + prf_login);
      repaint();

      reponse = read_answer_line(sock);
      repaint();
      if ( get_return_code(reponse) != 331 )
      {
        t_log.append("Erreur d'identification\n");
        repaint();
        sock.close();
        return;
      }

      // Envoie la commande PASS pour finir de s'identifier
      err = write_command_line(sock, "PASS " + prf_pwd);
      repaint();

      reponse = read_answer_line(sock);
      repaint();
      if ( get_return_code(reponse) != 230 )
      {
        t_log.append("Erreur d'identification\n");
        repaint();
        sock.close();
        return;
      }

      // Change de répertoire:
      err = write_command_line(sock, "CWD public_html");
      repaint();

      reponse = read_answer_line(sock);
      repaint();
      if ( get_return_code(reponse) != 250 )
      {
        t_log.append("Erreur de chemin\n");
        repaint();
        sock.close();
        return;
      }

      // Envoie la commande PASS pour passer en mode passive
      // le serveur ouvre une socket de données et attend une
      // connexion entrante
      err = write_command_line(sock, "PASV");
      repaint();

      reponse = read_answer_line(sock);
      repaint();
      if ( get_return_code(reponse) != 227 )
      {
        t_log.append("Erreur mode passif\n");
        repaint();
        sock.close();
        return;
      }

      // Récupère l'adresse IP du serveur sur lequel se connecter:
      ip = get_ip(reponse);

      // Récupère le port sur lequel ouvrir la socket:
      port = get_port(reponse);

      // Crée la socket de données et se connecte
      datasock = new Socket(ip, port);
      t_log.append("Connected to " + datasock.toString() + "\n");

      // Récupère le flut d'entrée (input stream) de la socket
      indatastream = datasock.getInputStream();

      // Envoie la commande NLST
      err = write_command_line(sock, "NLST");
      repaint();

      reponse = read_answer_line(sock);
      repaint();
      if ( get_return_code(reponse) != 150 )
      {
        t_log.append("Erreur commande NLST\n");
        repaint();
        datasock.close();
        sock.close();
        return;
      }

      lst_forum.removeAll();
      while ( indatastream.available() > 0 )
      {
        reponse = read_line(datasock);
        if ( reponse.compareTo(".")!=0 && reponse.compareTo("..")!=0 )
          lst_forum.addItem(reponse);
      }

      // Ferme la socket de données
      datasock.close();

      reponse = read_answer_line(sock);
      repaint();
      if ( get_return_code(reponse) != 226 )
      {
        t_log.append("Erreur fin de commande LIST\n");
        repaint();
        sock.close();
        return;
      }

      err = write_command_line(sock, "QUIT");
      repaint();

      reponse = read_answer_line(sock);
      repaint();

      sock.close();
    }
    catch (IOException e)
    {
      t_log.append("Erreur de connexion!\n");
      repaint();
      return;
    }
    catch (SecurityException e)
    {
      t_log.append("Connexion refusée par le viewer!\n");
      repaint();
      return;
    }

  }

  // Cette méthode va lire une ligne de réponse sur la socket de
  // service.
  // En retour on récupère la dernière ligne (dans le cas d'un
  // message de retour multilignes)
  // Les \r sont filtrés
  private String read_answer_line(Socket sock)
  {
    InputStream        istream;
    String             str;
    String             endstr;
    char c;
    int  nextline = 0;

    // Initialisation du buffer
    str = new String();
    endstr = new String();

    try
    {
      // Recupere le flot associe a la socket
      istream = sock.getInputStream();

      // Cette boucle va permettre de lire plusieurs lignes si besoin
      do
      {
        // Cette deuxieme boucle lit une ligne
        while ((c = (char) istream.read()) != '\n')
        {
          // On ignore le caractere '\r' (Carriage Return) present avant
          // le caractere de fin de ligne '\n' (New Line)
          if (c != '\r')
          {
            // Ajoute le caractere lu en fin de buffer
            str = str + c;
          }
        }
        // On ajoute nous meme le caractere de fin de ligne (car celui-ci
        // n'est pas copie: voir test de sortie de la boucle)
        str = str + "\n";

        // Affiche la ligne dans la zone log
        t_log.append(str);
        repaint();

        // Test pour savoir si le code du serveur contient encore un ou
        // plusieurs lignes: dans ce cas le code est du type "xyz-blablabla"
        // sinon la dernier ligne est du type "xyz blablabla" (cf rfc959)
        if (nextline == 0)
        {
          if (str.charAt(3) == '-')
          {
            nextline = 1;
            endstr = str.substring(0,3) + "-";
          }
        }
        else
        {
          if (str.startsWith(endstr))
            nextline = 0;
        }

      } while (nextline != 0);
    }
    catch (IOException e)
    {
      t_log.append(str + "\n" + "Erreur d'entree-sortie");
      repaint();
      return str;
    }
    return str;
  }

  // Cette méthode va lire une ligne sur la socket de données.
  // en retour on récupère la ligne lue sans le \n ni le \r
  private String read_line(Socket sock)
  {
    InputStream        istream;
    String             str;
    char c;
    int  nextline;

    // Initialisation du buffer
    str = new String();

    try
    {
      // Recupere le flot associe a la socket
      istream = sock.getInputStream();

      // Cette deuxieme boucle lit une ligne
      while ((c = (char) istream.read()) != '\n')
      {
        // On ignore le caractere '\r' (Carriage Return) present avant
        // le caractere de fin de ligne '\n' (New Line)
        if (c != '\r')
        {
          // Ajoute le caractere lu en fin de buffer
          str = str + c;
        }
      }

    }
    catch (IOException e)
    {
      t_log.append(str + "\n" + "Erreur d'entree-sortie");
      repaint();
      return str;
    }
    return str;
  }


  // Cette méthode extrait le code décimal de status depuis
  // une ligne de réponse.
  private int get_return_code(String ligne)
  {
    int code;

    try
    {
      code = Integer.parseInt( ligne.substring(0, 3) );
    }
    catch (NumberFormatException e)
    {
      code = -1;
    }
    return code;
  }

  // Cette méthode extrait l'adresse IP envoyé dans le status PASV
  private String get_ip(String ligne)
  {
    String  ip;
    int     i1, i2;

    // Ip chiffre 1:
    i1 = ligne.indexOf('(') + 1;
    i2 = ligne.indexOf(',');
    ip = ligne.substring(i1, i2);

    // Ip chiffre 2:
    i1 = ligne.indexOf(',', i2) + 1;
    i2 = ligne.indexOf(',' , i1);
    ip = ip + "." + ligne.substring(i1, i2);

    // Ip chiffre 3:
    i1 = ligne.indexOf(',', i2) + 1;
    i2 = ligne.indexOf(',' , i1);
    ip = ip + "." + ligne.substring(i1, i2);

    // Ip chiffre 4:
    i1 = ligne.indexOf(',', i2) + 1;
    i2 = ligne.indexOf(',' , i1);
    ip = ip + "." + ligne.substring(i1, i2);

    return ip;
  }

  // Cette méthode extrait le port IP envoyé dans le status PASV
  private int get_port(String ligne)
  {
    int  port;
    int  i1, i2;

    // Ip chiffre 1:
    i1 = ligne.indexOf('(') + 1;
    i2 = ligne.indexOf(',');
    // Ip chiffre 2:
    i1 = ligne.indexOf(',', i2) + 1;
    i2 = ligne.indexOf(',' , i1);
    // Ip chiffre 3:
    i1 = ligne.indexOf(',', i2) + 1;
    i2 = ligne.indexOf(',' , i1);
    // Ip chiffre 4:
    i1 = ligne.indexOf(',', i2) + 1;
    i2 = ligne.indexOf(',' , i1);

    // Port chiffre 1:
    i1 = ligne.indexOf(',', i2) + 1;
    i2 = ligne.indexOf(',' , i1);
    try
    {
      port = 256 * Integer.parseInt( ligne.substring(i1, i2) );
    }
    catch (NumberFormatException e)
    { return 0; }
    
    // Port chiffre 2:
    i1 = ligne.indexOf(',', i2) + 1;
    i2 = ligne.indexOf(')' , i1);
    try
    {
      port = port + Integer.parseInt( ligne.substring(i1, i2) );
    }
    catch (NumberFormatException e)
    { return 0; }

    return port;
  }

  // Cette méthode envoie une ligne de commande sur la socket de
  // service.
  private int write_command_line(Socket sock, String line)
  {
    OutputStream        ostream;
    int i;

    t_log.append(line + "\n");
    repaint();

    line += "\r\n";

    try
    {
      ostream = sock.getOutputStream();

      for (i=0; i<line.length(); i++)
        ostream.write((int)line.charAt(i));
    }
    catch (IOException e)
    {
      t_log.append("Erreur d'entree-sortie");
      repaint();
      return IO_ERROR;
    }
    return IO_OK;
  }

  // Cette méthode envoie une ligne sur la socket de données
  private int write_line(Socket sock, String line)
  {
    OutputStream        ostream;
    int i;

    try
    {
      ostream = sock.getOutputStream();

      for (i=0; i<line.length(); i++)
        ostream.write((int)line.charAt(i));
    }
    catch (IOException e)
    {
      t_log.append("Erreur d'entree-sortie");
      repaint();
      return IO_ERROR;
    }
    return IO_OK;
  }

  // Fonction appellée par le délégué lors de l'envoi du message
  void send()
  {
    int ok = 1;

    if ( (t_from.getText()).compareTo("") == 0 )
    {
      t_log.append("Le champ From doit contenir un nom d'expediteur"+"\n");
      repaint();
      ok = 0;
    }
    if ( (t_subject.getText()).compareTo("") == 0 )
    { 
      t_log.append("Le champ Subject doit contenir l'objet du message"+"\n");
      repaint();
      ok = 0;
    }
    if ( (t_txt.getText()).compareTo("") == 0 )
    { 
      t_log.append("Vous n'avez pas tape de message"+"\n");
      repaint();
      ok = 0;
    }
    if ( lst_forum.getSelectedItem() == null )
    { 
      t_log.append("Vous n'avez pas choisi de forum"+"\n");
      repaint();
      ok = 0;
    }

    if (ok == 1)
    {
      send_message();

    }

  }

  // Fonction appellée par le délégué lors de la remise à zero du texte
  void clear()
  {
    t_from.setText("");
    t_subject.setText("");
    t_txt.setText("");
    repaint();
  }

  // Cette méthde va poster le message sur le serveur
  private void send_message()
  {
    Date          date;
    Socket        sock;
    Socket        datasock;
    InputStream   instream;
    OutputStream  outstream;
    InputStream   indatastream;
    OutputStream  outdatastream;
    String        reponse;
    String        ip;
    int           port;
    int           err;
    int           max = 0, num = 0;
    
    try
    {
      // Crée la socket et se connecte sur le serveur
      sock = new Socket(prf_server, prf_port);
      t_log.append("Connected to " + sock.toString() + "\n");

      // Récupère le flut d'entrée (input stream) de la socket
      instream = sock.getInputStream();

      // Lit la réponse du serveur
      reponse = read_answer_line(sock);
      repaint();
      if ( get_return_code(reponse) != 220 )
      {
        t_log.append("Erreur de connexion\n");
        repaint();
        sock.close();
        return;
      }

      // Envoie la commande USER pour s'identifier
      err = write_command_line(sock, "USER " + prf_login);
      repaint();

      reponse = read_answer_line(sock);
      repaint();
      if ( get_return_code(reponse) != 331 )
      {
        t_log.append("Erreur d'identification\n");
        repaint();
        sock.close();
        return;
      }

      // Envoie la commande PASS pour finir de s'identifier
      err = write_command_line(sock, "PASS " + prf_pwd);
      repaint();

      reponse = read_answer_line(sock);
      repaint();
      if ( get_return_code(reponse) != 230 )
      {
        t_log.append("Erreur d'identification\n");
        repaint();
        sock.close();
        return;
      }

      // Change de répertoire:
      err = write_command_line(sock, "CWD public_html");
      repaint();

      reponse = read_answer_line(sock);
      repaint();
      if ( get_return_code(reponse) != 250 )
      {
        t_log.append("Erreur de chemin\n");
        repaint();
        sock.close();
        return;
      }

      // Se place dans le répertoire du forum choisi:
      err = write_command_line(sock, "CWD " + lst_forum.getSelectedItem());
      repaint();

      reponse = read_answer_line(sock);
      repaint();
      if ( get_return_code(reponse) != 250 )
      {
        t_log.append("Erreur de chemin\n");
        repaint();
        sock.close();
        return;
      }


      // Début du NLST
      // Maintenant il faut faire un NLST pour récupérer le numero du
      // dernier message

      // Envoie la commande PASS pour passer en mode passive
      // le serveur ouvre une socket de données et attend une
      // connexion entrante
      err = write_command_line(sock, "PASV");
      repaint();

      reponse = read_answer_line(sock);
      repaint();
      if ( get_return_code(reponse) != 227 )
      {
        t_log.append("Erreur mode passif\n");
        repaint();
        sock.close();
        return;
      }

      // Récupère l'adresse IP du serveur sur lequel se connecter:
      ip = get_ip(reponse);

      // Récupère le port sur lequel ouvrir la socket:
      port = get_port(reponse);

      // Crée la socket de données et se connecte
      datasock = new Socket(ip, port);
      t_log.append("Connected to " + datasock.toString() + "\n");

      // Récupère le flut d'entrée (input stream) de la socket
      indatastream = datasock.getInputStream();

      // Envoie la commande NLST
      err = write_command_line(sock, "NLST");
      repaint();

      reponse = read_answer_line(sock);
      repaint();

      // Code 550: répertoire vide
      if ( get_return_code(reponse) == 550 )
      {
        max = 0;

        // Ferme la socket de données
        datasock.close();
      }
      else if ( get_return_code(reponse) == 150 )
      {
        while ( indatastream.available() > 0 )
        {
          reponse = read_line(datasock);

          try
          {
            num = Integer.parseInt(reponse);
          }
          catch (NumberFormatException e)
          {
            num = 0;
          }

          if (num > max)
            max = num;
        }

        // Ferme la socket de données
        datasock.close();

        reponse = read_answer_line(sock);
        repaint();
        if ( get_return_code(reponse) != 226 )
        {
          t_log.append("Erreur fin de commande NLST\n");
          repaint();
          sock.close();
          return;
        }
      }
      else
      {
        t_log.append("Erreur commande NLST\n");
        repaint();
        datasock.close();
        sock.close();
        return;
      }

      // Fin du NLST


      // Notre numéro de message:
      num = max + 1;


      // Réglage en type binaire
      err = write_command_line(sock, "TYPE I");
      repaint();

      reponse = read_answer_line(sock);
      repaint();
      if ( get_return_code(reponse) != 200 )
      {
        t_log.append("Erreur de type (binaire)\n");
        repaint();
        sock.close();
        return;
      }


      // Début du STOU
      // Maintenant il faut faire un STOU pour stocker un fichier en
      // évitant les doublons

      // Envoie la commande PASS pour passer en mode passive
      // le serveur ouvre une socket de données et attend une
      // connexion entrante
      err = write_command_line(sock, "PASV");
      repaint();

      reponse = read_answer_line(sock);
      repaint();
      if ( get_return_code(reponse) != 227 )
      {
        t_log.append("Erreur mode passif\n");
        repaint();
        sock.close();
        return;
      }

      // Récupère l'adresse IP du serveur sur lequel se connecter:
      ip = get_ip(reponse);

      // Récupère le port sur lequel ouvrir la socket:
      port = get_port(reponse);

      // Crée la socket de données et se connecte
      datasock = new Socket(ip, port);
      t_log.append("Connected to " + datasock.toString() + "\n");

      // Récupère le flut d'entrée (input stream) de la socket
      outdatastream = datasock.getOutputStream();

      // Envoie la commande STOU
      err = write_command_line(sock, "STOU " + Integer.toString(num) );
      repaint();

      reponse = read_answer_line(sock);
      repaint();

      // Code 150: problème
      if ( get_return_code(reponse) != 150 )
      {
        t_log.append("Erreur commande STOU\n");
        repaint();
        datasock.close();
        sock.close();
        return;
      }

      write_line(datasock, "From: " + t_from.getText() + "\n");
      date = new Date();
      write_line(datasock, "Date: " + date.toString() + "\n");
      write_line(datasock, "Subject: " + t_subject.getText() + "\n");
      write_line(datasock, "\n");
      write_line(datasock, t_txt.getText() + "\n");

      // Ferme la socket de données
      datasock.close();

      reponse = read_answer_line(sock);
      repaint();
      if ( get_return_code(reponse) != 226 )
      {
        t_log.append("Erreur fin de commande STOU\n");
        repaint();
        sock.close();
        return;
      }

      // Fin du STOU


      err = write_command_line(sock, "QUIT");
      repaint();

      reponse = read_answer_line(sock);
      repaint();

      sock.close();
    }
    catch (IOException e)
    {
      t_log.append("Erreur de connexion!\n");
      repaint();
      return;
    }
    catch (SecurityException e)
    {
      t_log.append("Connexion refusée par le viewer!\n");
      repaint();
      return;
    }

  }

}

