package com.alibaba.druid.util;

import java.nio.ByteBuffer;
import java.util.Arrays;

public class Base64 {
   private static final char[] intToBase64 = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
   private static final char[] intToAltBase64 = new char[]{'!', '"', '#', '$', '%', '&', '\'', '(', ')', ',', '-', '.', ':', ';', '<', '>', '@', '[', ']', '^', '`', '_', '{', '|', '}', '~', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '?'};
   private static final byte[] base64ToInt = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51};
   private static final byte[] altBase64ToInt = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, 62, 9, 10, 11, -1, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 12, 13, 14, -1, 15, 63, 16, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 17, -1, 18, 19, 21, 20, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 22, 23, 24, 25};

   public static String byteArrayToBase64(byte[] a) {
      return byteArrayToBase64(a, false);
   }

   public static String byteArrayToAltBase64(byte[] a) {
      return byteArrayToBase64(a, true);
   }

   private static String byteArrayToBase64(byte[] a, boolean alternate) {
      int aLen = a.length;
      int numFullGroups = aLen / 3;
      int numBytesInPartialGroup = aLen - 3 * numFullGroups;
      int resultLen = 4 * ((aLen + 2) / 3);
      StringBuilder result = new StringBuilder(resultLen);
      char[] intToAlpha = alternate ? intToAltBase64 : intToBase64;
      int inCursor = 0;

      for(int i = 0; i < numFullGroups; ++i) {
         int byte0 = a[inCursor++] & 255;
         int byte1 = a[inCursor++] & 255;
         int byte2 = a[inCursor++] & 255;
         result.append(intToAlpha[byte0 >> 2]);
         result.append(intToAlpha[byte0 << 4 & 63 | byte1 >> 4]);
         result.append(intToAlpha[byte1 << 2 & 63 | byte2 >> 6]);
         result.append(intToAlpha[byte2 & 63]);
      }

      if (numBytesInPartialGroup != 0) {
         int byte0 = a[inCursor++] & 255;
         result.append(intToAlpha[byte0 >> 2]);
         if (numBytesInPartialGroup == 1) {
            result.append(intToAlpha[byte0 << 4 & 63]);
            result.append("==");
         } else {
            int byte1 = a[inCursor++] & 255;
            result.append(intToAlpha[byte0 << 4 & 63 | byte1 >> 4]);
            result.append(intToAlpha[byte1 << 2 & 63]);
            result.append('=');
         }
      }

      return result.toString();
   }

   public static byte[] base64ToByteArray(String s) {
      return base64ToByteArray(s, false);
   }

   public static byte[] altBase64ToByteArray(String s) {
      return base64ToByteArray(s, true);
   }

   private static byte[] base64ToByteArray(String s, boolean alternate) {
      byte[] alphaToInt = alternate ? altBase64ToInt : base64ToInt;
      int sLen = s.length();
      int numGroups = sLen / 4;
      if (4 * numGroups != sLen) {
         throw new IllegalArgumentException("String length must be a multiple of four.");
      } else {
         int missingBytesInLastGroup = 0;
         int numFullGroups = numGroups;
         if (sLen != 0) {
            if (s.charAt(sLen - 1) == '=') {
               ++missingBytesInLastGroup;
               numFullGroups = numGroups - 1;
            }

            if (s.charAt(sLen - 2) == '=') {
               ++missingBytesInLastGroup;
            }
         }

         byte[] result = new byte[3 * numGroups - missingBytesInLastGroup];
         int inCursor = 0;
         int outCursor = 0;

         for(int i = 0; i < numFullGroups; ++i) {
            int ch0 = base64toInt(s.charAt(inCursor++), alphaToInt);
            int ch1 = base64toInt(s.charAt(inCursor++), alphaToInt);
            int ch2 = base64toInt(s.charAt(inCursor++), alphaToInt);
            int ch3 = base64toInt(s.charAt(inCursor++), alphaToInt);
            result[outCursor++] = (byte)(ch0 << 2 | ch1 >> 4);
            result[outCursor++] = (byte)(ch1 << 4 | ch2 >> 2);
            result[outCursor++] = (byte)(ch2 << 6 | ch3);
         }

         if (missingBytesInLastGroup != 0) {
            int ch0 = base64toInt(s.charAt(inCursor++), alphaToInt);
            int ch1 = base64toInt(s.charAt(inCursor++), alphaToInt);
            result[outCursor++] = (byte)(ch0 << 2 | ch1 >> 4);
            if (missingBytesInLastGroup == 1) {
               int ch2 = base64toInt(s.charAt(inCursor++), alphaToInt);
               result[outCursor++] = (byte)(ch1 << 4 | ch2 >> 2);
            }
         }

         return result;
      }
   }

   private static int base64toInt(char c, byte[] alphaToInt) {
      int result = alphaToInt[c];
      if (result < 0) {
         throw new IllegalArgumentException("Illegal character " + c);
      } else {
         return result;
      }
   }

   public static Encoder getEncoder() {
      return Encoder.RFC4648;
   }

   public static class Encoder {
      private final byte[] newline;
      private final int linemax;
      private final boolean isURL;
      private final boolean doPadding;
      private static final char[] toBase64 = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
      private static final char[] toBase64URL = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'};
      private static final int MIMELINEMAX = 76;
      private static final byte[] CRLF = new byte[]{13, 10};
      static final Encoder RFC4648 = new Encoder(false, (byte[])null, -1, true);
      static final Encoder RFC4648_URLSAFE = new Encoder(true, (byte[])null, -1, true);
      static final Encoder RFC2045;

      private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) {
         this.isURL = isURL;
         this.newline = newline;
         this.linemax = linemax;
         this.doPadding = doPadding;
      }

      private final int outLength(int srclen) {
         int len = 0;
         if (this.doPadding) {
            len = 4 * ((srclen + 2) / 3);
         } else {
            int n = srclen % 3;
            len = 4 * (srclen / 3) + (n == 0 ? 0 : n + 1);
         }

         if (this.linemax > 0) {
            len += (len - 1) / this.linemax * this.newline.length;
         }

         return len;
      }

      public byte[] encode(byte[] src) {
         int len = this.outLength(src.length);
         byte[] dst = new byte[len];
         int ret = this.encode0(src, 0, src.length, dst);
         return ret != dst.length ? Arrays.copyOf(dst, ret) : dst;
      }

      public int encode(byte[] src, byte[] dst) {
         int len = this.outLength(src.length);
         if (dst.length < len) {
            throw new IllegalArgumentException("Output byte array is too small for encoding all input bytes");
         } else {
            return this.encode0(src, 0, src.length, dst);
         }
      }

      public String encodeToString(byte[] src) {
         byte[] encoded = this.encode(src);
         return new String(encoded, 0, 0, encoded.length);
      }

      public ByteBuffer encode(ByteBuffer buffer) {
         int len = this.outLength(buffer.remaining());
         byte[] dst = new byte[len];
         int ret = 0;
         if (buffer.hasArray()) {
            ret = this.encode0(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.arrayOffset() + buffer.limit(), dst);
            buffer.position(buffer.limit());
         } else {
            byte[] src = new byte[buffer.remaining()];
            buffer.get(src);
            ret = this.encode0(src, 0, src.length, dst);
         }

         if (ret != dst.length) {
            dst = Arrays.copyOf(dst, ret);
         }

         return ByteBuffer.wrap(dst);
      }

      public Encoder withoutPadding() {
         return !this.doPadding ? this : new Encoder(this.isURL, this.newline, this.linemax, false);
      }

      private int encode0(byte[] src, int off, int end, byte[] dst) {
         char[] base64 = this.isURL ? toBase64URL : toBase64;
         int sp = off;
         int slen = (end - off) / 3 * 3;
         int sl = off + slen;
         if (this.linemax > 0 && slen > this.linemax / 4 * 3) {
            slen = this.linemax / 4 * 3;
         }

         int dp = 0;

         while(sp < sl) {
            int sl0 = Math.min(sp + slen, sl);
            int sp0 = sp;

            int bits;
            for(int dp0 = dp; sp0 < sl0; dst[dp0++] = (byte)base64[bits & 63]) {
               bits = (src[sp0++] & 255) << 16 | (src[sp0++] & 255) << 8 | src[sp0++] & 255;
               dst[dp0++] = (byte)base64[bits >>> 18 & 63];
               dst[dp0++] = (byte)base64[bits >>> 12 & 63];
               dst[dp0++] = (byte)base64[bits >>> 6 & 63];
            }

            sp0 = (sl0 - sp) / 3 * 4;
            dp += sp0;
            sp = sl0;
            if (sp0 == this.linemax && sl0 < end) {
               for(byte b : this.newline) {
                  dst[dp++] = b;
               }
            }
         }

         if (sp < end) {
            int b0 = src[sp++] & 255;
            dst[dp++] = (byte)base64[b0 >> 2];
            if (sp == end) {
               dst[dp++] = (byte)base64[b0 << 4 & 63];
               if (this.doPadding) {
                  dst[dp++] = 61;
                  dst[dp++] = 61;
               }
            } else {
               int b1 = src[sp++] & 255;
               dst[dp++] = (byte)base64[b0 << 4 & 63 | b1 >> 4];
               dst[dp++] = (byte)base64[b1 << 2 & 63];
               if (this.doPadding) {
                  dst[dp++] = 61;
               }
            }
         }

         return dp;
      }

      static {
         RFC2045 = new Encoder(false, CRLF, 76, true);
      }
   }
}
