/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.phoenix.util;

/**
 * Utility for converting a base 10 number to string that represents a base 62 number
 */
public class Base62Encoder {

  // All possible chars for representing a number as a base 62 encoded String
  public static final char[] digits =
    "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();

  private static final char[] DigitTens = new char[3844];
  private static final char[] DigitOnes = new char[3844];

  static {
    for (byte i = 0; i < 62; ++i) {
      for (byte j = 0; j < 62; ++j) {
        DigitTens[i * 62 + j] = digits[i];
        DigitOnes[i * 62 + j] = digits[j];
      }
    }
  }

  final static long[] pow62 = { 62, 3844, 238328, 14776336, 916132832, 56800235584L, 3521614606208L,
    218340105584896L, 13537086546263552L, 839299365868340224L };

  /**
   * Returns the length of the base 62 encoded string required to represent num must be a positive
   * number
   */
  static int stringSize(long num) {
    for (int i = 0; i < 10; i++) {
      if (num < pow62[i]) return i + 1;
    }
    return 11;
  }

  /**
   * Fills the given buffer with a string representing the given number in base 62. The characters
   * are placed into the buffer backwards starting with the least significant digit and working
   * backwards from there. number to convert, should be > Long.MIN_VALUE size of the buffer buffer
   * to place encoded string
   */
  static void getChars(long num, int size, char[] buf) {
    long q;
    int r;
    int charPos = size;
    char sign = 0;

    if (num < 0) {
      sign = '-';
      num = -num;
    }

    // Get 2 digits per iteration using longs until quotient fits into an int
    while (num > Integer.MAX_VALUE) {
      q = num / 3844;
      r = (int) (num - (q * 3844));
      num = q;
      buf[--charPos] = DigitOnes[r];
      buf[--charPos] = DigitTens[r];
    }

    // Get 2 digits per iteration using ints
    int q2;
    int i2 = (int) num;
    while (i2 >= 65536) {
      q2 = i2 / 3844;
      r = i2 - (q2 * 3844);
      i2 = q2;
      buf[--charPos] = DigitOnes[r];
      buf[--charPos] = DigitTens[r];
    }

    // Fall through to fast mode for smaller numbers
    // assert(i2 <= 65536, i2);
    for (;;) {
      // this evaluates to i2/62
      // see "How to optimize for the Pentium family of microprocessors", Agner Fog, section 18.7
      q2 = ((i2 + 1) * 33825) >>> (16 + 5);
      r = i2 - (q2 * 62);
      buf[--charPos] = digits[r];
      i2 = q2;
      if (i2 == 0) break;
    }
    if (sign != 0) {
      buf[--charPos] = sign;
    }
  }

  /**
   * Returns a String object representing the specified long encoded in base 62. number to be
   * converted
   * @return a string representation of the number encoded in base 62
   */
  public static String toString(long num) {
    if (num == Long.MIN_VALUE) return "-AzL8n0Y58m8";
    int size = (num < 0) ? stringSize(-num) + 1 : stringSize(num);
    char[] buf = new char[size];
    getChars(num, size, buf);
    return new String(buf);
  }
}
