/*
 * File    : Encryption.java
 * Created : 03-jul-2001 09:51
 * By      : allastar
 *
 * JClic - Authoring and playing system for educational activities
 *
 * Copyright (C) 2000 - 2018 Francesc Busquets & Departament
 * d'Educacio de la Generalitat de Catalunya
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details (see the LICENSE file).
 */

package edu.xtec.util;

/**
 * Utilities to encrypt and decrypt strings using simple methods, just to avoid
 * write passwords in plain text in data and configuration files. Do not use it
 * as a secure cryptographic system!
 *
 * @author Albert Llastarri (allastar@xtec.cat)
 * @version 13.08.08
 */
public final class Encryption {

  private static final String BLANK = "___blank___##";

  public static String Encrypt(String txt) {
    if (txt == null || txt.length() == 0)
      txt = BLANK;

    String result = null;
    try {
      result = codify(txt);
    } catch (Exception ex) {
      System.err.println("Error encripting text!");
    }
    return result;
  }

  public static String Decrypt(String txt) {

    if (txt == null || txt.length() == 0)
      return null;

    String s = decodify(txt);
    if (BLANK.equals(s))
      s = new String();

    return s;
  }

  private static char hexCharArrayToChar(char[] cA, int fromIndex) {
    char[] hex = new char[4];
    int n = 0;
    for (int i = 0; i <= 3; i++) {
      int j = Character.digit(cA[fromIndex + i], 16);
      n = (n * 16) + j;
    }
    return (char) n;
  }

  private static char[] charToHexCharArray(char c) {
    char[] hex = new char[4];
    int j = (int) c;
    for (int i = 3; i >= 0; i--) {
      hex[i] = Character.forDigit(j % 16, 16);
      j /= 16;
    }
    return hex;
  }

  private static char[] intToHexCharArray(int c) {
    char[] hex = new char[2];
    int j = (int) c;
    for (int i = 1; i >= 0; i--) {
      hex[i] = Character.forDigit(j % 16, 16);
      j /= 16;
    }
    return hex;
  }

  private static int hexCharArrayToInt(char[] cA, int fromIndex) {
    int n = 0;
    for (int i = 0; i <= 1; i++) {
      int j = Character.digit(cA[fromIndex + i], 16);
      n = (n * 16) + j;
    }
    return n;
  }

  private static StringBuilder compressZeros(char[] cA) {
    int total = 0;
    StringBuilder sb = new StringBuilder(cA.length);
    int[] zeros = new int[(cA.length + 7) / 8]; // it will be better to use bytes, but
    // then it takes the first bit as a sign
    int j;
    for (j = 0; total < cA.length; j++) {
      char b = 0;
      for (int i = 0; i <= 7; i++) {
        b <<= 1;
        if (total < cA.length) {
          if (cA[total] == '0')
            b += 1;
          else
            sb.append(cA[total]);
        }
        total++;
      }
      zeros[j] = (int) b;
    }
    return codifyZerosField(zeros, j).append(sb.substring(0));
  }

  private static StringBuilder codifyZerosField(int[] zeros, int length) {
    String hexZeros = codifyToHex(zeros, length); // hexZeros size is always odd
    StringBuilder codified = new StringBuilder();
    if (hexZeros.length() > 1) {
      char c1 = hexZeros.charAt(0);
      char c2 = hexZeros.charAt(1);
      int num = 1;
      int currentChar = 2;
      while (currentChar < hexZeros.length()) {
        if (c1 == hexZeros.charAt(currentChar) && c2 == hexZeros.charAt(currentChar + 1) && num < 32)
          num++;
        else { // New sequence
          codified.append(Character.forDigit(num, 32));
          codified.append(c1);
          codified.append(c2);
          num = 1;
          c1 = hexZeros.charAt(currentChar);
          c2 = hexZeros.charAt(currentChar + 1);
        }
        currentChar += 2;
      }
      codified.append(Character.forDigit(num, 32));
      codified.append(c1);
      codified.append(c2);
      codified.append("0");
    }
    return codified;
  }

  private static String decodifyZerosField(char[] cA) {
    StringBuilder sb = new StringBuilder();
    int num = Character.digit(cA[0], 32);
    int k = 0;
    int i;
    for (i = 0; num != 0; i++) {
      while (num > 0) {
        sb.append(cA[(i * 3) + 1]);
        sb.append(cA[(i * 3) + 2]);
        num--;
        k++;
      }
      if (cA.length > ((i * 3) + 3))
        num = Character.digit(cA[(i * 3) + 3], 32);
      else
        num = 0;
    }
    for (int j = (i * 3) + 1; j < cA.length; j++)
      sb.append(cA[j]);
    char c = Character.forDigit(k, 32);
    return c + sb.toString();
  }

  private static StringBuilder decompressZeros(char[] cA) {
    cA = decodifyZerosField(cA).toCharArray();
    int numBytesZeros = Character.digit(cA[0], 32);
    int iniNoZeros = (numBytesZeros * 2) + 1;
    boolean bFi = false;
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < numBytesZeros && !bFi; i++) {
      int zeros = hexCharArrayToInt(cA, 1 + (i * 2));
      String s = Integer.toBinaryString(zeros);
      while (s.length() < 8)
        s = "0" + s;
      for (int j = 0; j <= 7 && !bFi; j++) {
        if (s.charAt(j) == '1') {
          sb.append('0');
        } else if (iniNoZeros < cA.length) {
          sb.append(cA[iniNoZeros]);
          iniNoZeros++;
        } else
          bFi = true;
      }
    }
    return sb;
  }

  private static String codifyToHex(int[] bA, int length) {
    // char [] cA=s.toCharArray();
    char[] cA = new char[length * 2];
    int j = 0;
    for (int i = 0; i < length; i++) {
      char[] hex = intToHexCharArray(bA[i]);
      for (int k = 0; k < 2; k++) {
        cA[j] = hex[k];
        j++;
      }
    }
    String st = new String(cA);
    return st;
  }

  private static char[] codifyToHex(String s) {
    char[] cA = new char[s.length() * 4];
    int j = 0;
    for (int i = 0; i < s.length(); i++) {
      char[] hex = charToHexCharArray(s.charAt(i));
      for (int k = 0; k < 4; k++) {
        cA[j] = hex[k];
        j++;
      }
    }
    return cA;
  }

  private static String decodifyFromHex(StringBuilder sb1) {
    StringBuilder sb = new StringBuilder();
    char[] cA = sb1.toString().toCharArray();
    int j = 0;
    for (int i = 0; j < sb1.length(); i++) {
      char c = hexCharArrayToChar(cA, j);
      sb.append(c);
      j += 4;
    }
    return sb.toString();
  }

  private static char[] changeOrder(StringBuilder s) {
    int m = 0;
    int n = s.length() - 1;
    char[] cA = new char[s.length()]; // =s.toCharArray();
    for (int i = 0; i < s.length(); i++) {
      if ((i % 2) == 0) {
        cA[m] = s.charAt(i);
        m++;
      } else {
        cA[n] = s.charAt(i);
        n--;
      }
    }
    return cA;
  }

  private static char[] unchangeOrder(String s) {
    int m = 0;
    int n = s.length() - 1;
    char[] cA = new char[s.length()];
    for (int i = 0; i < s.length(); i++) {
      if ((i % 2) == 0) {
        cA[i] = s.charAt(m);
        m++;
      } else {
        cA[i] = s.charAt(n);
        n--;
      }
    }
    return cA;
  }

  static class TooLargePasswordException extends Exception {
    @Override
    public String toString() {
      return "Password mustn't contain over 24 characters!!!";
    }
  }

  private static String codify(String word) throws TooLargePasswordException {
    if (word.length() > 24)
      throw new TooLargePasswordException();
    return new String(changeOrder(compressZeros(codifyToHex(word))));
  }

  private static String decodify(String word) {
    try {
      return decodifyFromHex(decompressZeros(unchangeOrder(word)));
    } catch (Exception e) { // The supplied word was not codified using this system
      return "";
    }
  }
}
