package javajs.util;

/**
 * byte converter
 * 
 * 
 * @author Bob Hanson hansonr@stolaf.edu
 * 
 */
public class BC {

  public BC() {
    // unnecessary to instantialize unless subclassed
  }
  
  public static float bytesToFloat(byte[] bytes, int j, boolean isBigEndian) throws Exception {
    return intToFloat(bytesToInt(bytes, j, isBigEndian));
  }

  public static int bytesToShort(byte[] bytes, int j, boolean isBigEndian) {
    int n = (isBigEndian ? (bytes[j + 1] & 0xff) | (bytes[j] & 0xff) << 8
        : (bytes[j++] & 0xff) | (bytes[j++] & 0xff) << 8);
      return (n > 0x7FFF ? n - 0x10000 : n);
  }

  public static int bytesToInt(byte[] bytes, int j, boolean isBigEndian) {
    int n = (isBigEndian ? (bytes[j + 3] & 0xff) | (bytes[j + 2] & 0xff) << 8
        | (bytes[j + 1] & 0xff) << 16 | (bytes[j] & 0xff) << 24
        : (bytes[j++] & 0xff) | (bytes[j++] & 0xff) << 8
            | (bytes[j++] & 0xff) << 16 | (bytes[j++] & 0xff) << 24);
    /**
     * @j2sNative
     * 
     * return (n > 0x7FFFFFFF ? n - 0x100000000 : n);
     *   
     */
    {
      return n;
    }
  }

  public static int intToSignedInt(int n) {
    /**
     * @j2sNative
     * 
     * return (n > 0x7FFFFFFF ? n - 0x100000000 : n);
     *   
     */
    {
      return n;
    }    
  }
  public static float intToFloat(int x) throws Exception {
    /**
     * see http://en.wikipedia.org/wiki/Binary32
     * 
     * [sign]      [8 bits power] [23 bits fraction]
     * 0x80000000  0x7F800000      0x7FFFFF
     * 
     * (untested)
     * 
     * @j2sNative
     * 
     *       if (x == 0) return 0;
     *       var o = javajs.util.BC;
     *       if (o.fracIEEE == null)
     *         o.setFracIEEE();
     *       var m = ((x & 0x7F800000) >> 23);
     *       return ((x & 0x80000000) == 0 ? 1 : -1) * o.shiftIEEE((x & 0x7FFFFF) | 0x800000, m - 149);
     *  
     */
    {
    return Float.intBitsToFloat(x);
    }
  }

  /**
   * see http://en.wikipedia.org/wiki/Binary64
   *  
   * not concerning ourselves with very small or very large numbers and getting
   * this exactly right. Just need a float here.
   * 
   * @param bytes
   * @param j
   * @param isBigEndian
   * @return float
   */
  public static float bytesToDoubleToFloat(byte[] bytes, int j, boolean isBigEndian) {
    {
      // IEEE754: sign (1 bit), exponent (11 bits), fraction (52 bits).
      // seeeeeee eeeeffff ffffffff ffffffff ffffffff xxxxxxxx xxxxxxxx xxxxxxxx
      //     b1      b2       b3       b4       b5    ---------float ignores----

        if (fracIEEE == null)
           setFracIEEE();
        
      /**
       * @j2sNative
       *       var o = javajs.util.BC;
       *       var b1, b2, b3, b4, b5;
       *       
       *       if (isBigEndian) {
       *       b1 = bytes[j] & 0xFF;
       *       b2 = bytes[j + 1] & 0xFF;
       *       b3 = bytes[j + 2] & 0xFF;
       *       b4 = bytes[j + 3] & 0xFF;
       *       b5 = bytes[j + 4] & 0xFF;
       *       } else {
       *       b1 = bytes[j + 7] & 0xFF;
       *       b2 = bytes[j + 6] & 0xFF;
       *       b3 = bytes[j + 5] & 0xFF;
       *       b4 = bytes[j + 4] & 0xFF;
       *       b5 = bytes[j + 3] & 0xFF;
       *       }
       *       var s = ((b1 & 0x80) == 0 ? 1 : -1);
       *       var e = (((b1 & 0x7F) << 4) | (b2 >> 4)) - 1026;
       *       b2 = (b2 & 0xF) | 0x10;
       *       return s * (o.shiftIEEE(b2, e) + o.shiftIEEE(b3, e - 8) + o.shiftIEEE(b4, e - 16)
       *         + o.shiftIEEE(b5, e - 24));
       */
      {
        double d;
        
        if (isBigEndian)
          d = Double.longBitsToDouble((((long) bytes[j]) & 0xff) << 56
             | (((long) bytes[j + 1]) & 0xff) << 48
             | (((long) bytes[j + 2]) & 0xff) << 40
             | (((long) bytes[j + 3]) & 0xff) << 32
             | (((long) bytes[j + 4]) & 0xff) << 24
             | (((long) bytes[j + 5]) & 0xff) << 16
             | (((long) bytes[j + 6]) & 0xff) << 8 
             | (((long) bytes[7]) & 0xff));
        else
          d = Double.longBitsToDouble((((long) bytes[j + 7]) & 0xff) << 56
             | (((long) bytes[j + 6]) & 0xff) << 48
             | (((long) bytes[j + 5]) & 0xff) << 40
             | (((long) bytes[j + 4]) & 0xff) << 32
             | (((long) bytes[j + 3]) & 0xff) << 24
             | (((long) bytes[j + 2]) & 0xff) << 16
             | (((long) bytes[j + 1]) & 0xff) << 8 
             | (((long) bytes[j]) & 0xff));
        return (float) d;
      }

    }
  }

  private static float[] fracIEEE;

  private static void setFracIEEE() {
    fracIEEE = new float[270];
    for (int i = 0; i < 270; i++)
      fracIEEE[i] = (float) Math.pow(2, i - 141);
    //    System.out.println(fracIEEE[0] + "  " + Parser.FLOAT_MIN_SAFE);
    //    System.out.println(fracIEEE[269] + "  " + Float.MAX_VALUE);
  }

  /**
   * only concerned about reasonable float values here -- private but not designated; called by JavaScript
   * 
   * @param f
   * @param i
   * @return f * 2^i
   */
  static double shiftIEEE(double f, int i) {
    if (f == 0 || i < -140)
      return 0;
    if (i > 128)
      return Float.MAX_VALUE;
    return f * fracIEEE[i + 140];
  }

//  static {
//    setFracIEEE();
//    for (int i = -50; i < 50; i++) {
//      float f = i * (float) (Math.random() * Math.pow(2, Math.random() * 100 - 50));
//      int x = Float.floatToIntBits(f);
//      int m = ((x & 0x7F800000) >> 23);
//      float f1 = (float) (f == 0 ? 0 : ((x & 0x80000000) == 0 ? 1 : -1) * shiftIEEE((x & 0x7FFFFF) | 0x800000, m - 149));
//      System.out.println(f + "  " + f1);
//    }
//    System.out.println("binarydo");
//  }



}
