import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;
import java.awt.image.BufferedImage;
import java.math.BigInteger;

/**
  *
  * Beschreibung
  *
  * @version 1.1 vom 30.01.2020
  * @author Thomas Schaller
   * Version 1.1: Hash-Funkion ausgetauscht, damit Hashes unterschiedlicher. , Ö/P-Schlüssel in der gleichen Farbe
  */

public class ChatClient extends JFrame implements MySocketListener {
  //
  protected int HOEHE = 60;
  // Anfang Attribute
  protected JPanel jPanel1 = new JPanel(null, true);
  protected JLabel jLabel2 = new JLabel();
  protected JTextField jTFName = new JTextField();
  protected JTextField jTFAdress = new JTextField();
  protected JLabel jLabel3 = new JLabel();
  protected JButton jBVerbinden = new JButton();
  protected JLabel jLabel1 = new JLabel();
  protected JNumberField jNFPort = new JNumberField();
  protected JTextField jTFNachricht = new JTextField();
  
  protected MySocket client = null;
  protected JLabel jLabel5 = new JLabel();
  protected JScrollPane jScrollPane1 = new JScrollPane();
  protected JScrollPane jScrollPane2 = new JScrollPane();
  protected JPanel jp;
  protected ArrayList<Nachricht> nachrichten;
  protected ArrayList<Key> keys;
  protected ArrayList<String> kontakte;
  protected Key dragKey = null;
  protected JLabel jLabel4 = new JLabel();
  protected  GridBagLayout gbl;
  protected int keyColor = 0;
  private JLabel jLabel6 = new JLabel();
  // Ende Attribute
  
  public ChatClient(String title) {
    // Frame-Initialisierung
    super(title);
    setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    addWindowListener(new WindowListener() {
      public void windowActivated(WindowEvent e) {}
      public void windowClosed(WindowEvent e) {}
      public void windowClosing(WindowEvent e) {
        if (client != null) {
          client.sendeNachricht("exit");
          client.trenneVerbindung();
        } // end of if
      }
      public void windowDeactivated(WindowEvent e) {}
      public void windowDeiconified(WindowEvent e) {}
      public void windowIconified(WindowEvent e){}
      public void windowOpened(WindowEvent e){}
    });
    
    
    int frameWidth = 1081; 
    int frameHeight = 683;
    setSize(frameWidth, frameHeight);
    Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
    int x = (d.width - getSize().width) / 2;
    int y = (d.height - getSize().height) / 2;
    setLocation(x, y);
    setResizable(false);
    Container cp = getContentPane();
    cp.setLayout(null);
    // Anfang Komponenten
    
    jPanel1.setBounds(8, 8, 1041, 41);
    jPanel1.setOpaque(false);
    jPanel1.setBorder(BorderFactory.createBevelBorder(1, Color.WHITE, Color.DARK_GRAY));
    cp.add(jPanel1);
    jLabel2.setBounds(264, 11, 62, 20);
    jLabel2.setText("Server");
    jPanel1.add(jLabel2);
    jTFName.setBounds(80, 11, 150, 20);
    jTFName.setText("anonymous");
    jPanel1.add(jTFName);
    jTFAdress.setBounds(312, 11, 150, 20);
    jTFAdress.setText("localhost");
    jPanel1.add(jTFAdress);
    jLabel3.setBounds(496, 11, 62, 20);
    jLabel3.setText("Port");
    jPanel1.add(jLabel3);
    jBVerbinden.setBounds(936, 8, 99, 25);
    jBVerbinden.setText("Verbinden");
    jBVerbinden.setMargin(new Insets(2, 2, 2, 2));
    jBVerbinden.addActionListener(new ActionListener() { 
      public void actionPerformed(ActionEvent evt) { 
        jBVerbinden_ActionPerformed(evt);
      }
    });
    jPanel1.add(jBVerbinden);
    
    
    jLabel1.setBounds(8, 12, 62, 18);
    jLabel1.setText("Name");
    jPanel1.add(jLabel1);
    jNFPort.setBounds(528, 11, 75, 20);
    jNFPort.setText("44444");
    jPanel1.add(jNFPort);
    jLabel5.setBounds(8, 64, 62, 20);
    jLabel5.setText("Schlüssel");
    cp.add(jLabel5);
    keys = new ArrayList<Key>();
    
    JPanel jp2 = new PicPanel(keys);
    jp2.setToolTipText("");
    jScrollPane2 = new JScrollPane(jp2);
    
    jScrollPane2.setBounds(96, 64, 953, 57);
    jp2.addMouseListener(new MouseAdapter() { 
      public void mousePressed(MouseEvent evt) { 
        keyCached(evt);
      }
      public void mouseReleased(MouseEvent evt) {
        keyDropped(evt);
      }
    });
    cp.add(jScrollPane2);
    jLabel4.setBounds(8, 144, 86, 20);
    jLabel4.setText("Nachrichten");
    cp.add(jLabel4);
    addWindowListener(new WindowAdapter() { 
      public void windowClosing(WindowEvent evt) { 
        ChatClient_WindowClosing(evt);
      }
    });
    jLabel6.setBounds(816, 624, 229, 20);
    jLabel6.setText("(cc) 2020, Thomas Schaller, Version 1.1");
    jLabel6.setForeground(Color.GRAY);
    cp.add(jLabel6);
    // Ende Komponenten
    
    jTFNachricht.setBounds(10, 20, 358, 20);
    jTFNachricht.addActionListener(new ActionListener() { 
      public void actionPerformed(ActionEvent evt) { 
        jTFNachricht_ActionPerformed(evt);
      }
    });
    
    
    Random ran = new Random();
    nachrichten = new ArrayList<Nachricht>();
    
    
    kontakte = new ArrayList<String>();
    kontakte.add("alle");
    
    
    jp = new JPanel();
    jScrollPane1 = new JScrollPane(jp);
    jScrollPane1.setBounds(96, 136, 953, 481);
    jScrollPane1.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
    jp.addMouseListener(new MouseAdapter() { 
      public void mousePressed(MouseEvent evt) { 
        keyMessageCached(evt);
      }
      public void mouseReleased(MouseEvent evt) {
        keyMessageDropped(evt);
      }
    });
    cp.add(jScrollPane1);
    gbl = new GridBagLayout();
    jp.setLayout(gbl);
    
    this.updateNachrichtenListe();
    
    
    
    setVisible(true);
  } // end of public ChatClient
  
  // Anfang Methoden
  public void jBVerbinden_ActionPerformed(ActionEvent evt) {
    String a = jTFAdress.getText();
    int p = jNFPort.getInt();
    if (client != null && client.isAktiv()) {
      jTFName.setEnabled(true);
      jTFAdress.setEnabled(true);
      jNFPort.setEnabled(true);
      jBVerbinden.setText("Verbinden");
      client.sendeNachricht("exit");
      client.trenneVerbindung();
    } else {
      client = new MySocket(a, p, this);
      if (client.isAktiv()) {
        jTFName.setEnabled(false);
        jTFAdress.setEnabled(false);
        jNFPort.setEnabled(false);
        jBVerbinden.setText("Trennen");
        client.sendeNachricht(("user:"+jTFName.getText()+":alle"));
      }
      
      
    } // end of if-else
    
  } // end of jBVerbinden_ActionPerformed
  
  
  public  byte[] appendOther(byte[] a1, byte[] a2){
    byte[] a3 = new byte[a1.length + a2.length];
    System.arraycopy(a1, 0, a3, 0, a1.length);
    System.arraycopy(a2, 0, a3, a1.length, a2.length);     
    return a3;
  }
  
  
  public void jBSend_ActionPerformed(ActionEvent evt) {
    if (client != null && client.isAktiv()) {
      JMyButton button = (JMyButton) evt.getSource();
      Nachricht n = button.getNachricht();
      n.setEmpfaenger((String) button.getEmpfaenger().getSelectedItem());
      String b = "mess:"+n.getAbsender()+":"+n.getEmpfaenger()+":"+Base64.encode(n.getInhalt())+":"+Base64.encode(n.getHashCode());
      client.sendeNachricht(b);
      this.updateNachrichtenListe();
    } else {
      jTFName.setEnabled(true);
      jTFAdress.setEnabled(true);
      jNFPort.setEnabled(true);
      jBVerbinden.setEnabled(true);
    } // end of if
  } // end of jBSend_ActionPerformed
      
      
  public void generateKeyPair(int bits) {
    RSA rsa = new RSA(bits);
    rsa.generateKey();
    int c = keyColor;
    addKey(rsa.getPublicKey());
    keyColor = c;
    addKey(rsa.getPrivateKey());
  }                          
    
  public void addKey(Key k) {
    boolean schonda = false;
    for (Key kk : keys) {
      if (kk.getN().equals(k.getN()) && kk.getE().equals(k.getE())) {
        JOptionPane.showMessageDialog(this,
          "Dieser Schlüssel entspricht dem schon vorhandenen Schlüssel "+kk.getName(),
          "Achtung",
          JOptionPane.WARNING_MESSAGE);
          break;
      } // end of if
    } // end of for
    
    k.setImage("key"+keyColor);
    keyColor = (keyColor+1)%10;
    keys.add(k);
    
    
    jScrollPane2.repaint();
  }
    
  public void showPublicZertKey() {
    Key k = RSA.getPublicKeyZert();
    addKey(k);
    
  }
    
  public void signKey(Key k) {
    Key nk = new Key(k.getName()+"(z)",k.getE(),k.getN());
    nk.sign(RSA.getPrivateKeyZert(),jTFName.getText());
    nk.setImage(k.getFilename()+"s");
    
    keys.add(keys.indexOf(k)+1,nk);
    jScrollPane2.repaint();
  }
    
  public void deleteKey(Key k) {
    keys.remove(k);
    jScrollPane2.repaint();
  }
    
    
    
  public void neuerClient(MySocket client) {
    
  }
    
  public void nachrichtEmpfangen(MySocket client) {
    String s = client.holeNachricht();
    String[] ss = s.split(":");
    
    if (ss[0].equals("user")) {
      kontakte.add(ss[1]);
      Nachricht n = new Nachricht("Server","alle","Neuer User: "+ss[1]);
      n.setSended(true);
      nachrichten.add(0,n);
      this.updateNachrichtenListe();
      
    } 
    if(ss[0].equals("mess")) {
      String hash;
      if (ss.length==5) {
        hash = ss[4];
      } else {
        hash = "";
      }// end of if
      Nachricht n = new Nachricht(ss[1],ss[2],Base64.decode(ss[3]),Base64.decode(hash));
      nachrichten.add(0,n);
      this.updateNachrichtenListe();
      
    } // end of if
  }
    
  public void verbindungBeendet(MySocket client) {
    jTFName.setEnabled(true);
    jTFAdress.setEnabled(true);
    jNFPort.setEnabled(true);
    jBVerbinden.setText("Verbinden");
    client = null;
  }
    
  public void jTFNachricht_ActionPerformed(ActionEvent evt) {
    Nachricht n = new Nachricht(jTFName.getText(),"", jTFNachricht.getText());
    jTFNachricht.setText("");
    nachrichten.add(0,n);
    this.updateNachrichtenListe();
    jTFNachricht.requestFocus();
  } // end of jTFNachricht_ActionPerformed
    
  public void keyCached(MouseEvent evt) {
    Key k = null;
    if (evt.getX()>10 && evt.getX()< keys.size()*32+10) {
      int k_nr = (int) ((evt.getX()-10) /32);
      k = keys.get(k_nr);
    }
    if (evt.getButton() == evt.BUTTON1) {
      if (k != null) {
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        dragKey = k;
        Cursor c = toolkit.createCustomCursor(dragKey.getImage() , new Point(0, 0), "img");
        setCursor (c);
        
      }
      
    } else {
      
      new KeyPopup(evt,this, k);  
    }  // end of if
    
  }
    
    
    
  public void keyDropped(MouseEvent evt) {
    GridBagConstraints c = new GridBagConstraints();
    
    Point p = evt.getPoint();
    Point pp = SwingUtilities.convertPoint(jScrollPane2, p, jScrollPane2.getParent());
    Point pp2 = SwingUtilities.convertPoint(jScrollPane2, p, jScrollPane1);
    
    if (pp2.getY()>0 && pp2.getY() < jScrollPane1.getHeight() && pp2.getX()>=0 && pp2.getX() < jScrollPane1.getWidth() && dragKey!=null) {
      Point ppp = SwingUtilities.convertPoint(jScrollPane2, p, jp);
      Component cc = jp.getComponentAt(ppp.x,ppp.y);
      GridBagLayout gbl = (GridBagLayout) jp.getLayout();
      c = gbl.getConstraints(cc);
      
      int n_nr = c.gridy-1;
      if (n_nr == -1) {
        Nachricht n;
        if (dragKey.isSigned()) {
          n = new Nachricht(jTFName.getText(),"",dragKey.toString(jTFName.getText()).getBytes(),dragKey.getHashCode());
        } else {
          n = new Nachricht(jTFName.getText(),"",dragKey.toString(jTFName.getText()));
        } // end of if-else
        nachrichten.add(0,n);
        this.updateNachrichtenListe();
        
        
      }
      if (n_nr >= 0 && n_nr < nachrichten.size() && c.gridx==1) {
        byte[] klar = nachrichten.get(n_nr).getInhalt();
        if (dragKey.calcBlocksize()<klar.length) {
          JOptionPane.showMessageDialog(this,
          "Die Nachricht ist zu lang für diesen Schlüssel. Es können nur "+dragKey.calcBlocksize()+" Zeichen verschlüsselt werden.",
          "Fehler",
          JOptionPane.WARNING_MESSAGE);
          
        } else {
          // end of if
          byte[] krypt = dragKey.encrypt(klar);
          Nachricht n = nachrichten.get(n_nr).copy();
          n.setInhalt(krypt);
          n.addKey(dragKey);
          nachrichten.add(n_nr+1,n);
          this.updateNachrichtenListe();
        }
      }
      if (n_nr >= 0 && n_nr < nachrichten.size() && c.gridx==2) {
        byte[] klar = nachrichten.get(n_nr).getHashCode();
        if (dragKey.calcBlocksize()<klar.length) {
          JOptionPane.showMessageDialog(this,
          "Der Schlüssel ist zu kurz für diesen Hashcode. Es können nur "+dragKey.calcBlocksize()+" Zeichen verschlüsselt werden.",
          "Fehler",
          JOptionPane.WARNING_MESSAGE);
          
        } else {
          // end of if
          byte[] krypt = dragKey.encrypt(klar);
          Nachricht n = nachrichten.get(n_nr).copy();
          n.setHashCode(krypt);
          n.addHashKey(dragKey);
          nachrichten.add(n_nr+1,n);
          this.updateNachrichtenListe();
        }
      }
      
    }
    dragKey = null;
    
    Cursor dc = new Cursor(Cursor.DEFAULT_CURSOR);
    setCursor (dc);
  } // end of jLabel5_MouseClicked
    
    
  public void keyMessageCached(MouseEvent evt) {
    Point p = evt.getPoint();
    Point pp = SwingUtilities.convertPoint(jScrollPane1, p, jp);
    
    GridBagLayout gbl = (GridBagLayout) jp.getLayout();
    Component cp = jp.getComponentAt(evt.getX(),evt.getY());
    GridBagConstraints gbc = gbl.getConstraints(cp);
    System.out.println(evt.getY());
    
    if (evt.getButton() == evt.BUTTON1 && gbc.gridx == 1) {
      if (gbc.gridy-1< nachrichten.size() && gbc.gridy-1 >= 0) {
        int n_nr = gbc.gridy-1;
        String[] teile = nachrichten.get(n_nr).getInhaltString().split(",|=");
        
        if (teile[0].equals("Key:Name")) {
          
          BigInteger e = new BigInteger(teile[3]);
          BigInteger n = new BigInteger(teile[5]);
          
          dragKey = new Key(teile[1],e,n) ;
          dragKey.setImage("key"+keyColor);
          
          Toolkit toolkit = Toolkit.getDefaultToolkit();
          Cursor c = toolkit.createCustomCursor(dragKey.getImage() , new Point(0, 0), "img");
          setCursor (c);
        } // end of if
        
      }
      
    }  // end of if
    if (evt.getButton() == evt.BUTTON3  && gbc.gridx == 1) {
      int n_nr = gbc.gridy-1;
      makeMessagePopup(evt,nachrichten.get(n_nr));  
      
    } // end of if
    
  }
    
    
  protected void makeMessagePopup(MouseEvent evt, Nachricht n) {
    new MessagePopup(evt, this, n);
  }  
    
  public void keyMessageDropped(MouseEvent evt) {
    
    
    Point p = evt.getPoint();
    Point pp = SwingUtilities.convertPoint(jp, p, jScrollPane2);
    
    if (pp.getY()>0 && pp.getY() < jScrollPane2.getHeight() && pp.getX()>=0 && pp.getX() < jScrollPane2.getWidth() && dragKey!=null) {
      this.addKey(dragKey);      
    }
    dragKey = null;
    
    Cursor dc = new Cursor(Cursor.DEFAULT_CURSOR);
    setCursor (dc);
  } // end of jLabel5_MouseClicked
    
    
    
  public void updateNachrichtenListe() {
    
    //jp.setBounds(jScrollPane1.getViewport().getBounds());
    GridBagConstraints c = new GridBagConstraints();
    while (jp.getComponentCount()>1) {
      jp.remove(1);
    } // end of while
    //jp.removeAll();
    
    if (jp.getComponentCount()==0) {
      JPanel eingabe = new JPanel();
      eingabe.setBorder(BorderFactory.createBevelBorder(0));
      eingabe.setLayout(null);
      eingabe.setBackground(new Color(250,255,200));
      eingabe.setOpaque(true);
      eingabe.setPreferredSize(new Dimension(150,HOEHE));
      
      c.gridx=0; c.gridy = 0; 
      c.gridwidth=5;
      c.fill = GridBagConstraints.BOTH;
      c.weightx = 1.0;
      jTFNachricht.setBounds(100,20,800,20);
      JLabel nn = new JLabel();
      nn.setText("Neue Nachricht");
      nn.setBounds(2,20,90,20);
      eingabe.add(nn);
      eingabe.add(jTFNachricht);
      jp.add(eingabe,c);
      
    } // end of if
    c.weightx=0;
    c.gridwidth=1;
    int xx = 1;
    JLabel name = null;
    for (Nachricht n : nachrichten) {
      
      if (n.getKeys().size()>0 && name != null) {
        GridBagConstraints c2 = gbl.getConstraints(name);
        c2.gridheight++;
        gbl.setConstraints(name,c2);
      } else {
        name = new JLabel();
        name.setText("<html>Von "+n.getAbsender()+"</html>");
        name.setBorder(BorderFactory.createBevelBorder(0));
        name.setBackground(new Color(250,255,200));
        name.setOpaque(true);
        c.gridx=0; c.gridy = xx; 
        c.fill = GridBagConstraints.BOTH;
        jp.add(name,c);
        
      } // end of if-else
      JPanel nnn = new JPanel();
      nnn.setLayout(new GridBagLayout());
      nnn.setBorder(BorderFactory.createBevelBorder(0));
      nnn.setBackground(new Color(250,255,200));
      nnn.setOpaque(true);
      
      PicPanel pp = new PicPanel(n.getKeys());
      pp.setPreferredSize(new Dimension(n.getKeys().size()*32,HOEHE));
      pp.setBackground(new Color(250,255,200));
      
      JMyLabel nachricht = new JMyLabel(n,"");
      if (n.getInhaltString().trim().length()>65) {
        nachricht.setText("<html>"+n.getInhaltString().trim().substring(0,62)+"...</html>");
      } else {
        nachricht.setText("<html>"+n.getInhaltString().trim()+"</html>");
      } // end of if-else
      
      c.gridx=0; c.gridy = 0; 
      c.weightx=1.0; c.weighty= 0;
      
      nnn.add(nachricht,c);
      c.gridx=1; c.gridy = 0; 
      c.weightx=0.0; c.weighty= 0;
      
      nnn.add(pp,c);
      c.gridx=1; c.gridy = xx; 
      c.weightx = 1.0;
      jp.add(nnn,c);
      
      nnn = new JPanel();
      nnn.setLayout(new GridBagLayout());
      nnn.setBorder(BorderFactory.createBevelBorder(0));
      nnn.setBackground(new Color(250,255,200));
      nnn.setOpaque(true);
      
      pp = new PicPanel(n.getHashKeys());
      pp.setPreferredSize(new Dimension(n.getHashKeys().size()*32,HOEHE));
      pp.setBackground(new Color(250,255,200));
      
      String hc = new String(n.getHashCode());
      if (hc.trim().length()>15) {
        hc = hc.substring(0,15)+"...";
      } //end of if-else
      
      JMyLabel hash = new JMyLabel(n, hc);
      
      
      
      c.gridx=0; c.gridy = 0; 
      c.weightx=1.0; c.weighty= 0;
      
      nnn.add(hash,c);
      c.gridx=1; c.gridy = 0; 
      c.weightx=0.0; c.weighty= 0;
      
      nnn.add(pp,c);
      c.gridx=2; c.gridy = xx; 
      c.weightx = 0.0;
      jp.add(nnn,c);
      
      JPanel abspanel = new JPanel();
      abspanel.setPreferredSize(new Dimension(150,HOEHE));
      abspanel.setBorder(BorderFactory.createBevelBorder(0));
      abspanel.setBackground(new Color(250,255,200));
      abspanel.setOpaque(true);
      abspanel.setLayout(new FlowLayout());
      
      if (n.getEmpfaenger().equals("")) {
        String[] stringlist = new String[100];
        JComboBox cb = new JComboBox(kontakte.toArray(stringlist));
        
        abspanel.add(cb);
        
        ImageIcon water = new ImageIcon("arrow.png");
        JMyButton button = new JMyButton(n, cb, water);
        button.addActionListener(new ActionListener() { 
          public void actionPerformed(ActionEvent evt) { 
            jBSend_ActionPerformed(evt);
          }
        });        
        abspanel.add(button);
        
      } else {
        abspanel.add(new JLabel("an "+n.getEmpfaenger()));
      }
      c.gridx=4; c.gridy = xx; 
      c.weightx = 0.0;
      jp.add(abspanel,c);
      xx++;
    }
    JPanel dummy = new JPanel();
    dummy.setBorder(BorderFactory.createBevelBorder(0));
    dummy.setLayout(null);
    dummy.setBackground(new Color(250,255,200));
    dummy.setOpaque(true);
    dummy.setPreferredSize(new Dimension(150,HOEHE));
    
    c.gridx=0; c.gridy = xx; 
    c.gridwidth=5;
    c.fill = GridBagConstraints.BOTH;
    c.weightx = 1.0;
    c.weighty = 1.0;
    jp.add(dummy,c);
    
    jp.revalidate();
    jp.repaint();
  }
    
  public void ChatClient_WindowClosing(WindowEvent evt) {
    // TODO hier Quelltext einfügen
  } // end of ChatClient_WindowClosing

    // Ende Methoden
    
  public static void main(String[] args) {
    new ChatClient("ChatClient");
  } // end of main
    
} // end of class ChatClient
  
