package com.sun.identity.shared.encode;

import java.util.Arrays;

public final class Base64 {
   private static final int DEFAULTMAXQUARTET = 19;
   private static final int BASELENGTH = 255;
   private static final int LOOKUPLENGTH = 64;
   private static final int TWENTYFOURBITGROUP = 24;
   private static final int EIGHTBIT = 8;
   private static final int SIXTEENBIT = 16;
   private static final int FOURBYTE = 4;
   private static final int SIGN = -128;
   private static final char PAD = '=';
   private static final byte[] BASE_64_ALPHABET = new byte[255];
   private static final char[] LOOK_UP_BASE_64_ALPHABET = new char[64];

   private static boolean isWhiteSpace(char octect) {
      return octect == ' ' || octect == '\r' || octect == '\n' || octect == '\t';
   }

   private static boolean isPad(char octect) {
      return octect == '=';
   }

   private static boolean isData(char octect) {
      return BASE_64_ALPHABET[octect] != -1;
   }

   public static String encode(byte[] binaryData) {
      return encode(binaryData, 0);
   }

   public static String encode(byte[] binaryData, int maxCharsPerLine) {
      int numQuartetPerLine = 19;
      if (maxCharsPerLine == 0) {
         numQuartetPerLine = 19;
      } else if (maxCharsPerLine % 4 == 0) {
         numQuartetPerLine = maxCharsPerLine / 4;
      } else {
         numQuartetPerLine = 19;
      }

      if (binaryData == null) {
         return null;
      }

      int lengthDataBits = binaryData.length * 8;
      if (lengthDataBits == 0) {
         return "";
      }

      int fewerThan24bits = lengthDataBits % 24;
      int numberTriplets = lengthDataBits / 24;
      int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets;
      int numberLines = (numberQuartet - 1) / numQuartetPerLine + 1;
      char[] encodedData = null;
      if (maxCharsPerLine == 0) {
         encodedData = new char[numberQuartet * 4];
      } else {
         encodedData = new char[numberQuartet * 4 + numberLines];
      }

      byte k = 0;
      byte l = 0;
      byte b1 = 0;
      byte b2 = 0;
      byte b3 = 0;
      int encodedIndex = 0;
      int dataIndex = 0;
      int i = 0;

      for (int line = 0; line < numberLines - 1; line++) {
         for (int quartet = 0; quartet < numQuartetPerLine; quartet++) {
            b1 = binaryData[dataIndex++];
            b2 = binaryData[dataIndex++];
            b3 = binaryData[dataIndex++];
            l = (byte)(b2 & 15);
            k = (byte)(b1 & 3);
            byte val1 = (b1 & -128) == 0 ? (byte)(b1 >> 2) : (byte)(b1 >> 2 ^ 192);
            byte val2 = (b2 & -128) == 0 ? (byte)(b2 >> 4) : (byte)(b2 >> 4 ^ 240);
            byte val3 = (b3 & -128) == 0 ? (byte)(b3 >> 6) : (byte)(b3 >> 6 ^ 252);
            encodedData[encodedIndex++] = LOOK_UP_BASE_64_ALPHABET[val1];
            encodedData[encodedIndex++] = LOOK_UP_BASE_64_ALPHABET[val2 | k << 4];
            encodedData[encodedIndex++] = LOOK_UP_BASE_64_ALPHABET[l << 2 | val3];
            encodedData[encodedIndex++] = LOOK_UP_BASE_64_ALPHABET[b3 & 63];
            i++;
         }

         if (maxCharsPerLine != 0) {
            encodedData[encodedIndex++] = '\n';
         }
      }

      while (i < numberTriplets) {
         b1 = binaryData[dataIndex++];
         b2 = binaryData[dataIndex++];
         b3 = binaryData[dataIndex++];
         l = (byte)(b2 & 15);
         k = (byte)(b1 & 3);
         byte val1 = (b1 & -128) == 0 ? (byte)(b1 >> 2) : (byte)(b1 >> 2 ^ 192);
         byte val2 = (b2 & -128) == 0 ? (byte)(b2 >> 4) : (byte)(b2 >> 4 ^ 240);
         byte val3 = (b3 & -128) == 0 ? (byte)(b3 >> 6) : (byte)(b3 >> 6 ^ 252);
         encodedData[encodedIndex++] = LOOK_UP_BASE_64_ALPHABET[val1];
         encodedData[encodedIndex++] = LOOK_UP_BASE_64_ALPHABET[val2 | k << 4];
         encodedData[encodedIndex++] = LOOK_UP_BASE_64_ALPHABET[l << 2 | val3];
         encodedData[encodedIndex++] = LOOK_UP_BASE_64_ALPHABET[b3 & 63];
         i++;
      }

      if (fewerThan24bits == 8) {
         b1 = binaryData[dataIndex];
         k = (byte)(b1 & 3);
         byte val1 = (b1 & -128) == 0 ? (byte)(b1 >> 2) : (byte)(b1 >> 2 ^ 192);
         encodedData[encodedIndex++] = LOOK_UP_BASE_64_ALPHABET[val1];
         encodedData[encodedIndex++] = LOOK_UP_BASE_64_ALPHABET[k << 4];
         encodedData[encodedIndex++] = '=';
         encodedData[encodedIndex++] = '=';
      } else if (fewerThan24bits == 16) {
         b1 = binaryData[dataIndex];
         b2 = binaryData[dataIndex + 1];
         l = (byte)(b2 & 15);
         k = (byte)(b1 & 3);
         byte val1 = (b1 & -128) == 0 ? (byte)(b1 >> 2) : (byte)(b1 >> 2 ^ 192);
         byte val2 = (b2 & -128) == 0 ? (byte)(b2 >> 4) : (byte)(b2 >> 4 ^ 240);
         encodedData[encodedIndex++] = LOOK_UP_BASE_64_ALPHABET[val1];
         encodedData[encodedIndex++] = LOOK_UP_BASE_64_ALPHABET[val2 | k << 4];
         encodedData[encodedIndex++] = LOOK_UP_BASE_64_ALPHABET[l << 2];
         encodedData[encodedIndex++] = '=';
      }

      if (maxCharsPerLine != 0) {
         encodedData[encodedIndex] = '\n';
      }

      return new String(encodedData);
   }

   public static byte[] decode(String encoded) {
      if (encoded == null) {
         return null;
      }

      char[] base64Data = encoded.toCharArray();
      int len = removeWhiteSpace(base64Data);
      if (len % 4 != 0) {
         return null;
      }

      int numberQuadruple = len / 4;
      if (numberQuadruple == 0) {
         return new byte[0];
      }

      byte[] decodedData = null;
      byte b1 = 0;
      byte b2 = 0;
      byte b3 = 0;
      byte b4 = 0;
      char d1 = '\u0000';
      char d2 = '\u0000';
      char d3 = '\u0000';
      char d4 = '\u0000';
      int i = 0;
      int encodedIndex = 0;
      int dataIndex = 0;
      decodedData = new byte[numberQuadruple * 3];

      while (i < numberQuadruple - 1) {
         if (!isData(d1 = base64Data[dataIndex++])
            || !isData(d2 = base64Data[dataIndex++])
            || !isData(d3 = base64Data[dataIndex++])
            || !isData(d4 = base64Data[dataIndex++])) {
            return null;
         }

         b1 = BASE_64_ALPHABET[d1];
         b2 = BASE_64_ALPHABET[d2];
         b3 = BASE_64_ALPHABET[d3];
         b4 = BASE_64_ALPHABET[d4];
         decodedData[encodedIndex++] = (byte)(b1 << 2 | b2 >> 4);
         decodedData[encodedIndex++] = (byte)((b2 & 15) << 4 | b3 >> 2 & 15);
         decodedData[encodedIndex++] = (byte)(b3 << 6 | b4);
         i++;
      }

      if (isData(d1 = base64Data[dataIndex++]) && isData(d2 = base64Data[dataIndex++])) {
         b1 = BASE_64_ALPHABET[d1];
         b2 = BASE_64_ALPHABET[d2];
         d3 = base64Data[dataIndex++];
         d4 = base64Data[dataIndex++];
         if (isData(d3) && isData(d4)) {
            b3 = BASE_64_ALPHABET[d3];
            b4 = BASE_64_ALPHABET[d4];
            decodedData[encodedIndex++] = (byte)(b1 << 2 | b2 >> 4);
            decodedData[encodedIndex++] = (byte)((b2 & 15) << 4 | b3 >> 2 & 15);
            decodedData[encodedIndex++] = (byte)(b3 << 6 | b4);
            return decodedData;
         }

         if (isPad(d3) && isPad(d4)) {
            if ((b2 & 15) != 0) {
               return null;
            }

            byte[] tmp = new byte[i * 3 + 1];
            System.arraycopy(decodedData, 0, tmp, 0, i * 3);
            tmp[encodedIndex] = (byte)(b1 << 2 | b2 >> 4);
            return tmp;
         } else if (!isPad(d3) && isPad(d4)) {
            b3 = BASE_64_ALPHABET[d3];
            if ((b3 & 3) != 0) {
               return null;
            }

            byte[] tmp = new byte[i * 3 + 2];
            System.arraycopy(decodedData, 0, tmp, 0, i * 3);
            tmp[encodedIndex++] = (byte)(b1 << 2 | b2 >> 4);
            tmp[encodedIndex] = (byte)((b2 & 15) << 4 | b3 >> 2 & 15);
            return tmp;
         } else {
            return null;
         }
      } else {
         return null;
      }
   }

   private static int removeWhiteSpace(char[] data) {
      if (data == null) {
         return 0;
      }

      int newSize = 0;
      int len = data.length;

      for (int i = 0; i < len; i++) {
         if (!isWhiteSpace(data[i])) {
            data[newSize++] = data[i];
         }
      }

      return newSize;
   }

   static {
      Arrays.fill(BASE_64_ALPHABET, (byte)-1);

      for (int i = 90; i >= 65; i--) {
         BASE_64_ALPHABET[i] = (byte)(i - 65);
      }

      for (int i = 122; i >= 97; i--) {
         BASE_64_ALPHABET[i] = (byte)(i - 97 + 26);
      }

      for (int i = 57; i >= 48; i--) {
         BASE_64_ALPHABET[i] = (byte)(i - 48 + 52);
      }

      BASE_64_ALPHABET[43] = 62;
      BASE_64_ALPHABET[47] = 63;

      for (int i = 0; i <= 25; i++) {
         LOOK_UP_BASE_64_ALPHABET[i] = (char)(65 + i);
      }

      int i = 26;

      for (int j = 0; i <= 51; j++) {
         LOOK_UP_BASE_64_ALPHABET[i] = (char)(97 + j);
         i++;
      }

      i = 52;

      for (int j = 0; i <= 61; j++) {
         LOOK_UP_BASE_64_ALPHABET[i] = (char)(48 + j);
         i++;
      }

      LOOK_UP_BASE_64_ALPHABET[62] = '+';
      LOOK_UP_BASE_64_ALPHABET[63] = '/';
   }
}
