// Decrypt.java, version 1.08, file created, July 27, 2006.
//
// Applet for decrypting secret messages with XOR decryption keys.
//
// This file last updated June 7, 2007, by Rick Wagner.
// Copyright 2004 by Rick Wagner, all rights reserved.
//
// Use of this source code is authorized for educational purposes only. No use without
// proper attribution to Rick Wagner (http://iris.usc.edu/home/iris/rwagner/
// e-mail: Richard.J.Wagner@gmail.com).
//
// The prefix naming convention used here is a modified Hungarian notation.
// "s" is string, "sf" is single precision floating point, "i" is integer, "b" is boolean,
// and "d" is dimension.

import java.applet.*;
import java.awt.*;

public class Decrypt extends Applet
{
  // Applet instance variables:
  private final String sVerNum = "1.08";                    // Only constructors can run here ("" is a constructor).
  private final String sCompiledDate = "June 7, 2007";      // Compiled date.

  private Dimension dApplet = null;                         // The applet panel size (set in calling html).
  private Image imOffScreen = null;                         // Offscreen image for double buffering.
  private Graphics grOffScreen = null;                      // Offscreen graphics for double buffering.

  private Button btnComputeKey = null;

  private TextArea taCryptText = null;
  private TextArea taPlainText = null;
  private TextArea taKeyText = null;

  private Label lblOne = null;                              // First label.
  private Label lblTwo = null;                              // Second label.
  private Label lblThree = null;                            // Third label.
  private Label lblMessage = null;                          // Message label.


  // To allow browsers to get information about the applet (not yet implemented in Netscape nor in MSIE):
  public String getAppletInfo()
  {
    return "XOR decryption applet, version " + sVerNum +
           ", by Rick Wagner, copyright 2006,\nall rights reserved.\n\n" +
           "Compiled " + sCompiledDate + ". Source code use authorized for\n" +
           "educational purposes only. No use without attribution.\n";
  }

  // Initialize the applet
  public void init()
  {
    int i = 0;
    GridBagLayout gbl = null;                               // GridBagLayout is used for the applet GUI layout.
    GridBagConstraints gbc = null;

    this.setBackground(Color.lightGray);
    dApplet = this.size();

    gbl = new GridBagLayout();
    this.setLayout(gbl);

    gbc = new GridBagConstraints();
    gbc.insets = new Insets(5, 10, 5, 10);                  // top, left, bottom, right.
    gbc.weightx = 0.0;
    gbc.weighty = 0.0;
    gbc.gridx = 0;

    // First Label
    lblOne = new Label("Encrypted Text", Label.CENTER);
    gbc.gridy = 0;
    gbl.setConstraints(lblOne, gbc);
    this.add(lblOne);

    // Add the crypt text box:
    taCryptText = new TextArea("", 6, 80);                  // 6 rows and 80 columns.
    taCryptText.setEditable(true);
    gbc.gridy = 1;
    gbl.setConstraints(taCryptText, gbc);
    this.add(taCryptText);

    // Second Label
    lblTwo = new Label("Key Text", Label.CENTER);
    gbc.gridy = 2;
    gbl.setConstraints(lblTwo, gbc);
    this.add(lblTwo);

    // Add the Key text box:
    taKeyText = new TextArea("", 6, 80);                    // 6 rows and 80 columns.
    taKeyText.setEditable(true);
    gbc.gridy = 3;
    gbl.setConstraints(taKeyText, gbc);
    this.add(taKeyText);

    // Add the decrypt button:
    btnComputeKey = new Button("Decrypt the Text");         // The compute decrypted text button.
    btnComputeKey.setForeground(Color.black);
    btnComputeKey.setBackground(Color.lightGray);
    gbc.gridy = 4;
    gbl.setConstraints(btnComputeKey, gbc);
    this.add(btnComputeKey);

    // Message Label
    lblMessage = new Label("          Welcome to the XOR decryption applet." +
                           "Type or paste text into the upper two text boxes.          ", Label.CENTER);
    gbc.gridy = 5;
    gbl.setConstraints(lblMessage, gbc);
    this.add(lblMessage);

    // Third Label
    lblThree = new Label("                    Plain (decrypted) Text                    ", Label.CENTER);
    gbc.gridy = 6;
    gbl.setConstraints(lblThree, gbc);
    this.add(lblThree);

    // Add the plain text box:
    taPlainText = new TextArea("", 6, 80);                   // 6 rows and 80 columns.
    taPlainText.setEditable(true);
    gbc.gridy = 7;
    gbl.setConstraints(taPlainText, gbc);
    this.add(taPlainText);

  } // End of init()

  // Execute this code after initialization
  public void start()
  {
    System.out.println("\n" + this.getAppletInfo());         // Identify self to the Java console-aware user
    taCryptText.requestFocus();
  }                                                          // End of start()

  // Implements double buffering 
  public void update(Graphics g)
  {
    if (imOffScreen == null)
    {
      // Make sure the offscreen and graphics exist:
      imOffScreen = this.createImage(dApplet.width, dApplet.height);
      grOffScreen = imOffScreen.getGraphics();
      grOffScreen.clearRect(0, 0, dApplet.width, dApplet.height);
    }
    this.paint(grOffScreen);
    g.drawImage(imOffScreen, 0, 0, null);
  }

  // The applet frame painting function
  public void paint(Graphics g)
  {
    // Code for displaying images or drawing in the applet frame (called by the OS).
    g.clearRect(0, 0, dApplet.width, dApplet.height);        // Needed for double buffering.
    this.setBackground(Color.lightGray);                     // Ditto.
  
    drawFrame(g);                                            // Draw the frame abound the applet.
  }                                                          // End of paint()

  private void drawFrame(Graphics g)
  {
    // Draw a recessed frame around the applet border. Designed for gray-on-gray browser background.
    g.setColor(Color.black);
    g.drawLine(0, 0, dApplet.width - 1, 0);
    g.drawLine(0, 0, 0, dApplet.height - 1);
    g.setColor(Color.white);
    g.drawLine(0, dApplet.height - 1, dApplet.width - 1, dApplet.height - 1);
    g.drawLine(dApplet.width - 1, 1, dApplet.width - 1, dApplet.height - 1);
  }

  public boolean handleEvent(Event e)
  {
    int iLength = 0;

    if (e.target == btnComputeKey && e.id == Event.ACTION_EVENT)
    {
      btnComputeKey.disable();
      if (taCryptText.getText().length() == 0)
      {
        System.out.println("Cryptext Empty");
        lblMessage.setText("Cryptext Empty");
      }
      else
      {
        if (taKeyText.getText().length() == 0)
        {
          System.out.println("Keytext Empty");
          lblMessage.setText("Keytext Empty");
        }
        else
        {
          // We can do the key computation
          decrypt();
        }
      }
      btnComputeKey.enable();
      return true;                                            // Absorb the event;
    }

    if (e.target == taCryptText)
    {
      if (e.id != Event.GOT_FOCUS)
      {
        iLength = taCryptText.getText().length();
        lblMessage.setText("Crypt text is " + iLength + " characters long.");
      }
    }

    if (e.target == taKeyText)
    {
      if (e.id != Event.GOT_FOCUS)
      {
        iLength = taKeyText.getText().length();
        lblMessage.setText("Key text is " + iLength + " characters long.");
      }
    }
    return false;
  }

  private void decrypt()
  {
    int i = 0;
    int iCryptLength = 0;
    int iCryptTextLength = 0;
    int iKeyLength = 0;
    int iKeyTextLength = 0;
    int iDelta = 0;
    int iTemp = 0;
    char cTemp = 0;
    char cTemp0 = 0;
    String sTemp = null;
    String sCryptText = null;
    String sKeyText = null;
    String sPlainText = null;
    StringBuffer sbTemp = null;
    int iCrypt[] = null;
    int iCryptBuffer[] = null;
    int iKey[] = null;
    int iKeyBuffer[] = null;
    boolean bNumericKey = false;
    boolean bNumericCrypt = false;

    sCryptText = taCryptText.getText();
    sKeyText = taKeyText.getText();

    iCryptTextLength = sCryptText.length();
    iKeyTextLength = sKeyText.length();
    System.out.println("\n");

    // Find out if the crypttext is numeric:
    if (numericP(iCryptTextLength, sCryptText))
    {
      bNumericCrypt = true;
      System.out.println("The crypt text is numeric.");
      // Fill the crypt array:
      iCrypt = new int[iCryptTextLength];                       // More than enough for the crypt integers.
      i = 0;
      while (i < iCryptTextLength)
      {
        cTemp = 0;
        sbTemp = new StringBuffer();
        while (cTemp != ' ' && i < iCryptTextLength)
        {
          try
          {
            cTemp = sCryptText.charAt(i);
          }
          catch (StringIndexOutOfBoundsException e0)
          {
            System.out.println("Caught StringIndexOutOfBoundsException in decrypt() (crypt).");
          }
          if (cTemp != ' ')
          {
            sbTemp.append(cTemp);
          }
          i++;
        }
        iCrypt[iCryptLength] = Integer.parseInt(sbTemp.toString());
        iCryptLength++;
      }
    }
    else
    {
      iCryptLength = iCryptTextLength;
      System.out.println("The crypt text is not numeric, iCryptLength = " + iCryptLength + ".");
    }

    // Find out if the key is numeric:
    if (numericP(iKeyTextLength, sKeyText))
    {
      bNumericKey = true;
      System.out.println("The key is numeric.");
      // Fill the key array:
      iKey = new int[iKeyTextLength];
      i = 0;
      while (i < iKeyTextLength)
      {
        cTemp = 0;
        sbTemp = new StringBuffer();
        while (cTemp != ' ' && i < iKeyTextLength)
        {
          try
          {
            cTemp = sKeyText.charAt(i);
          }
          catch (StringIndexOutOfBoundsException e1)
          {
            System.out.println("Caught StringIndexOutOfBoundsException in decrypt() (key).");
          }
          if (cTemp != ' ')
          {
            sbTemp.append(cTemp);
          }
          i++;
        }
        try
        {
          iTemp = Integer.parseInt(sbTemp.toString());
        }
        catch (NumberFormatException e1a)
        {
          System.out.println("Caught NumberFormatException in decrypt() (key).");
          break;
        }
        iKey[iKeyLength] = iTemp;
        iKeyLength++;
      }
    }
    else
    {
      iKeyLength = iKeyTextLength;
      System.out.println("The key is not numeric, iKeyLength = " + iKeyLength + ".");
    }

    if (iCryptLength < iKeyLength)
    {
      if (bNumericCrypt)
      {
        System.out.println("Ignoring excess key digits.");
      }
      else
      {
        System.out.println("Ignoring excess key characters.");
      }
    }
    else
    {
      if (iKeyLength < iCryptLength)
      {
        System.out.println("Key is shorter than crypttext: repeating the key.");
        lblMessage.setText("Key is shorter than crypttext: repeating the key.");
        if (bNumericKey)
        {
          // Repeat the key as necessary to make it long enough.
          iKeyBuffer = new int[iKeyLength];                                  // Need to stash the key characters someplace
          for (i = 0; i < iKeyLength; i++)                                   // while we get more memory for the array.
          {
            iKeyBuffer[i] = iKey[i];
          }
          iKey = new int[iCryptLength];                                      // Expand the key array.
          for (i = 0; i < iKeyLength; i++)
          {
            iKey[i] = iKeyBuffer[i];
          }
          for (i = iKeyLength; i < iCryptLength; i++)
          {
            iKey[i] = iKey[i - iKeyLength];
          }
        }
        else
        {
          sbTemp = new StringBuffer(sKeyText);
          for (i = iKeyLength; i < iCryptLength; i++)
          {
            try
            {
              cTemp = sbTemp.charAt(i - iKeyLength);
            }
            catch (StringIndexOutOfBoundsException e2)
            {
              System.out.println("Caught StringIndexOutOfBoundsException in decrypt() (e2).");
            }
            sbTemp.append(cTemp);
          }
          sKeyText = sbTemp.toString();
          iKeyLength = sKeyText.length();
        }
      }
      else
      {
        // They are equal in length, do nothing.
      }
    }
    // Begin the decryption.
    sbTemp = new StringBuffer();
    for (i = 0; i < iCryptLength; i++)
    {
      if (bNumericKey)
      {
        if (bNumericCrypt)
        {
          iTemp = iCrypt[i] ^ iKey[i];
        }
        else
        {
          try
          {
            cTemp = sCryptText.charAt(i);
          }
          catch (StringIndexOutOfBoundsException e3)
          {
            System.out.println("Caught StringIndexOutOfBoundsException in decrypt() (e3).");
          }
          iTemp = cTemp ^ iKey[i];
        }
      }
      else
      {
        if (bNumericCrypt)
        {
          try
          {
            cTemp = sKeyText.charAt(i);
          }
          catch (StringIndexOutOfBoundsException e4)
          {
            System.out.println("Caught StringIndexOutOfBoundsException in decrypt() (e4).");
          }
          iTemp = iCrypt[i] ^ cTemp;
        }
        else
        {
          try
          {
            cTemp = sCryptText.charAt(i);
            cTemp0 = sKeyText.charAt(i);
          }
          catch (StringIndexOutOfBoundsException e5)
          {
            System.out.println("Caught StringIndexOutOfBoundsException in decrypt() (e5) , i = " + i + ".");
          }
          iTemp = cTemp ^ cTemp0;
        }
      }
      sbTemp.append((char) iTemp);
    }
    sTemp = sbTemp.toString();
    if (unprintableP(iCryptLength, sTemp))
    {
      System.out.println("Unprintable decrypted text, writing " + iCryptLength + " characters as numeric code.");
      lblMessage.setText("Unprintable decrypted text, writing " + iCryptLength + " characters as numeric code.");
      sbTemp = new StringBuffer();
      for (i = 0; i < iCryptLength; i++)
      {
        try
        {
          iTemp = sTemp.charAt(i);
        }
        catch (StringIndexOutOfBoundsException e6)
        {
          System.out.println("Caught StringIndexOutOfBoundsException in decrypt() (e6).");
        }

        if (i == iCryptLength - 1)
        {
          sbTemp.append(iTemp);
        }
        else
        {
          sbTemp.append(iTemp + " ");
        }
      }
      taPlainText.setText(sbTemp.toString());
    }
    else
    {
      System.out.println("Printable decrypted text, writing " + iCryptLength + " characters.");
      lblMessage.setText("Printable decrypted text, writing " + iCryptLength + " characters.");
      taPlainText.setText(sTemp);
    }
  }                                                                       // End of decrypt().

  boolean numericP(int iTextLength, String sText)
  {
    boolean r = true;
    int i = 0;
    char cTemp = 0;
    int iRunLength = 0;
    int iMaxRunLength = 0;

    for (i = 0; i < iTextLength; i++)
    {
      try
      {
        cTemp = sText.charAt(i);
      }
      catch (StringIndexOutOfBoundsException e7)
      {
        System.out.println("Caught StringIndexOutOfBoundsException in decrypt() (e7).");
      }
      if (cTemp != ' ')
      {
        if (cTemp < 48 || cTemp > 57)
        {
          r = false;
          break;                                                          // Break out of for loop.
        }
        else
        {
          if (cTemp > 47 && cTemp < 58)
          {
            // We have a decimal digit.
            iRunLength++;
          }
        }
      }
      else
      {
        // We have a space:                                                // Detect delimiting spaces
        if (iRunLength > iMaxRunLength)
        {
          iMaxRunLength = iRunLength;
        }
        iRunLength = 0;
      }
    }                                                                     // End of for.

    if (iRunLength > iMaxRunLength)                                       // Catch the case where the space
    {                                                                     // comes at the end of the string.
      iMaxRunLength = iRunLength;
    }

    if (iMaxRunLength > 3)                                                // It's not text that happens to to be
    {                                                                     // a string (or strings) of digits.
      r = false;
    }

    return r;
  }

  boolean unprintableP(int iLength, String sText)
  {
    boolean r = false;
    int i = 0;
    char cTemp = 0;

    for (i = 0; i < iLength; i++)
    {
      try
      {
        cTemp = sText.charAt(i);
      }
      catch (StringIndexOutOfBoundsException e8)
      {
        System.out.println("Caught StringIndexOutOfBoundsException in decrypt() (e8).");
      }

      if (cTemp < 32 || cTemp == 127)
      {
        if (cTemp != 9 && cTemp != 10 && cTemp != 12 && cTemp != 13)
        {
          r = true;
          System.out.println("Unprintable on character " + (int) cTemp);
          break;                                                          // Break out of for loop.
        }
      }
    }

    return r;
  }

} // End of Applet class Decrypt
