package org.jruby.util;

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

import org.jruby.*;
import org.jruby.runtime.builtin.IRubyObject;

public class ConvertBytes {

    private final Ruby runtime;
    private final ByteList str;
    private int beg;
    private int end;
    private byte[] data;
    private int base;
    private final boolean badcheck;

    public ConvertBytes(Ruby runtime, ByteList str, int base, boolean badcheck) {
        this.runtime = runtime;
        this.str = str;
        this.beg = str.getBegin();
        this.data = str.getUnsafeBytes();
        this.end = beg + str.getRealSize();
        this.badcheck = badcheck;
        this.base = base;
    }

    ConvertBytes(Ruby runtime, ByteList str, int off, int end, int base, boolean badcheck) {
        this.runtime = runtime;
        this.str = str;
        this.beg = off + str.getBegin();
        this.data = str.getUnsafeBytes();
        this.end = str.getBegin() + end;
        this.badcheck = badcheck;
        this.base = base;
    }

    @Deprecated
    public ConvertBytes(Ruby runtime, ByteList str, int base, boolean badcheck, boolean is19) {
        this(runtime, str, base, badcheck);
    }

    public static final byte[] intToBinaryBytes(int i) {
        return intToUnsignedByteList(i, 1, LOWER_DIGITS).bytes();
    }

    public static final byte[] intToOctalBytes(int i) {
        return intToUnsignedByteList(i, 3, LOWER_DIGITS).bytes();
    }

    public static final byte[] intToHexBytes(int i) {
        return intToUnsignedByteList(i, 4, LOWER_DIGITS).bytes();
    }

    public static final byte[] intToHexBytes(int i, boolean upper) {
        return intToUnsignedByteList(i, 4, upper ? UPPER_DIGITS : LOWER_DIGITS).bytes();
    }

    public static final ByteList intToBinaryByteList(int i) {
        return new ByteList(intToBinaryBytes(i));
    }
    public static final ByteList intToOctalByteList(int i) {
        return new ByteList(intToOctalBytes(i));
    }
    public static final ByteList intToHexByteList(int i) {
        return new ByteList(intToHexBytes(i));
    }
    public static final ByteList intToHexByteList(int i, boolean upper) {
        return new ByteList(intToHexBytes(i, upper));
    }

    public static final byte[] intToByteArray(int i, int radix, boolean upper) {
        return longToByteArray(i, radix, upper);
    }

    public static final byte[] intToCharBytes(int i) {
        return longToByteList(i, 10, LOWER_DIGITS).bytes();
    }

    public static final byte[] longToBinaryBytes(long i) {
        return longToUnsignedByteList(i, 1, LOWER_DIGITS).bytes();
    }

    public static final byte[] longToOctalBytes(long i) {
        return longToUnsignedByteList(i, 3, LOWER_DIGITS).bytes();
    }

    public static final byte[] longToHexBytes(long i) {
        return longToUnsignedByteList(i, 4, LOWER_DIGITS).bytes();
    }

    public static final byte[] longToHexBytes(long i, boolean upper) {
        return longToUnsignedByteList(i, 4, upper ? UPPER_DIGITS : LOWER_DIGITS).bytes();
    }

    public static final ByteList longToBinaryByteList(long i) {
        return longToByteList(i, 2, LOWER_DIGITS);
    }
    public static final ByteList longToOctalByteList(long i) {
        return longToByteList(i, 8, LOWER_DIGITS);
    }
    public static final ByteList longToHexByteList(long i) {
        return longToByteList(i, 16, LOWER_DIGITS);
    }
    public static final ByteList longToHexByteList(long i, boolean upper) {
        return longToByteList(i, 16, upper ? UPPER_DIGITS : LOWER_DIGITS);
    }

    public static final byte[] longToByteArray(long i, int radix, boolean upper) {
        return longToByteList(i, radix, upper ? UPPER_DIGITS : LOWER_DIGITS).bytes();
    }

    public static final byte[] longToCharBytes(long i) {
        return longToByteList(i, 10, LOWER_DIGITS).bytes();
    }

    public static final ByteList longToByteList(long i) {
        return longToByteList(i, 10, LOWER_DIGITS);
    }

    public static final ByteList longToByteList(long i, int radix) {
        return longToByteList(i, radix, LOWER_DIGITS);
    }

    public static final ByteList longToByteList(long i, int radix, byte[] digitmap) {
        if (i == 0) return new ByteList(ZERO_BYTES);

        if (i == Long.MIN_VALUE) return new ByteList(MIN_VALUE_BYTES[radix]);

        boolean neg = false;
        if (i < 0) {
            i = -i;
            neg = true;
        }

        // max 64 chars for 64-bit 2's complement integer
        int len = 64;
        byte[] buf = new byte[len];

        int pos = len;
        do {
            buf[--pos] = digitmap[(int)(i % radix)];
        } while ((i /= radix) > 0);
        if (neg) buf[--pos] = (byte)'-';

        return new ByteList(buf, pos, len - pos);
    }

    private static final ByteList intToUnsignedByteList(int i, int shift, byte[] digitmap) {
        byte[] buf = new byte[32];
        int charPos = 32;
        int radix = 1 << shift;
        long mask = radix - 1;
        do {
            buf[--charPos] = digitmap[(int)(i & mask)];
            i >>>= shift;
        } while (i != 0);
        return new ByteList(buf, charPos, (32 - charPos), false);
    }

    private static final ByteList longToUnsignedByteList(long i, int shift, byte[] digitmap) {
        byte[] buf = new byte[64];
        int charPos = 64;
        int radix = 1 << shift;
        long mask = radix - 1;
        do {
            buf[--charPos] = digitmap[(int)(i & mask)];
            i >>>= shift;
        } while (i != 0);
        return new ByteList(buf, charPos, (64 - charPos), false);
    }

    public static final byte[] twosComplementToBinaryBytes(byte[] in) {
        return twosComplementToUnsignedBytes(in, 1, false);
    }
    public static final byte[] twosComplementToOctalBytes(byte[] in) {
        return twosComplementToUnsignedBytes(in, 3, false);
    }
    public static final byte[] twosComplementToHexBytes(byte[] in, boolean upper) {
        return twosComplementToUnsignedBytes(in, 4, upper);
    }

    private static final byte[] ZERO_BYTES = new byte[] {(byte)'0'};

    private static final byte[][] MIN_VALUE_BYTES;
    static {
        MIN_VALUE_BYTES = new byte[37][];
        for (int i = 2; i <= 36; i++) {
            MIN_VALUE_BYTES[i] =  ByteList.plain(Long.toString(Long.MIN_VALUE, i));
        }
    }

    private static final byte[] LOWER_DIGITS = {
        '0' , '1' , '2' , '3' , '4' , '5' ,
        '6' , '7' , '8' , '9' , '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'
        };

    private static final byte[] UPPER_DIGITS = {
        '0' , '1' , '2' , '3' , '4' , '5' ,
        '6' , '7' , '8' , '9' , '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'
        };

    public static final byte[] twosComplementToUnsignedBytes(byte[] in, int shift, boolean upper) {
        if (shift < 1 || shift > 4) {
            throw new IllegalArgumentException("shift value must be 1-4");
        }
        int ilen = in.length;
        int olen = (ilen * 8 + shift - 1 ) / shift;
        byte[] out = new byte[olen];
        int mask = (1 << shift) - 1;
        byte[] digits = upper ? UPPER_DIGITS : LOWER_DIGITS;
        int bitbuf = 0;
        int bitcnt = 0;
        for(int i = ilen, o = olen; --o >= 0; ) {
            if(bitcnt < shift) {
                bitbuf |= ((int)in[--i] & (int)0xff) << bitcnt;
                bitcnt += 8;
            }
            out[o] = digits[bitbuf & mask];
            bitbuf >>= shift;
            bitcnt -= shift;
        }
        return out;
    }

    
rb_cstr_to_inum
/** rb_cstr_to_inum * */
public static RubyInteger byteListToInum(Ruby runtime, ByteList str, int base, boolean badcheck) { return new ConvertBytes(runtime, str, base, badcheck).byteListToInum(); } public static RubyInteger byteListToInum(Ruby runtime, ByteList str, int off, int end, int base, boolean badcheck) { return new ConvertBytes(runtime, str, off, end, base, badcheck).byteListToInum(); } @Deprecated public static RubyInteger byteListToInum19(Ruby runtime, ByteList str, int base, boolean badcheck) { return byteListToInum(runtime, str, base, badcheck); } private final static byte[] conv_digit = new byte[128]; private final static boolean[] digit = new boolean[128]; private final static boolean[] space = new boolean[128]; private final static boolean[] spaceOrUnderscore = new boolean[128]; static { Arrays.fill(conv_digit, (byte)-1); Arrays.fill(digit, false); for(char c = '0'; c <= '9'; c++) { conv_digit[c] = (byte)(c - '0'); digit[c] = true; } for(char c = 'a'; c <= 'z'; c++) { conv_digit[c] = (byte)(c - 'a' + 10); } for(char c = 'A'; c <= 'Z'; c++) { conv_digit[c] = (byte)(c - 'A' + 10); } Arrays.fill(space, false); space['\t'] = true; space['\n'] = true; space[11] = true; // \v space['\f'] = true; space['\r'] = true; space[' '] = true; Arrays.fill(spaceOrUnderscore, false); spaceOrUnderscore['\t'] = true; spaceOrUnderscore['\n'] = true; spaceOrUnderscore[11] = true; // \v spaceOrUnderscore['\f'] = true; spaceOrUnderscore['\r'] = true; spaceOrUnderscore[' '] = true; spaceOrUnderscore['_'] = true; } public static byte[] bytesToUUIDBytes(byte[] randBytes, boolean upper) { ByteBuffer bytes = ByteBuffer.wrap(randBytes); long N0 = bytes.getInt() & 0xFFFFFFFFL; int n1 = bytes.getShort() & 0xFFFF; int n2 = bytes.getShort() & 0xFFFF; n2 = n2 & 0x0FFF | 0x4000; int n3 = bytes.getShort() & 0xFFFF; n3 = n3 & 0x3FFF | 0x8000; int n4 = bytes.getShort() & 0xFFFF; long N5 = bytes.getInt() & 0xFFFFFFFFL; byte[] convert = upper ? UPPER_DIGITS : LOWER_DIGITS; return new byte[]{ convert[(int)((N0 >> 28) & 0xF)], convert[(int)((N0 >> 24) & 0xF)], convert[(int)((N0 >> 20) & 0xF)], convert[(int)((N0 >> 16) & 0xF)], convert[(int)((N0 >> 12) & 0xF)], convert[(int)((N0 >> 8) & 0xF)], convert[(int)((N0 >> 4) & 0xF)], convert[(int)(N0 & 0xF)], (byte)'-', convert[(n1 >> 12) & 0xF], convert[(n1 >> 8) & 0xF], convert[(n1 >> 4) & 0xF], convert[n1 & 0xF], (byte)'-', convert[(n2 >> 12) & 0xF], convert[(n2 >> 8) & 0xF], convert[(n2 >> 4) & 0xF], convert[n2 & 0xF], (byte)'-', convert[(n3 >> 12) & 0xF], convert[(n3 >> 8) & 0xF], convert[(n3 >> 4) & 0xF], convert[n3 & 0xF], (byte)'-', convert[(n4 >> 12) & 0xF], convert[(n4 >> 8) & 0xF], convert[(n4 >> 4) & 0xF], convert[n4 & 0xF], convert[(int)((N5 >> 28) & 0xF)], convert[(int)((N5 >> 24) & 0xF)], convert[(int)((N5 >> 20) & 0xF)], convert[(int)((N5 >> 16) & 0xF)], convert[(int)((N5 >> 12) & 0xF)], convert[(int)((N5 >> 8) & 0xF)], convert[(int)((N5 >> 4) & 0xF)], convert[(int)(N5 & 0xF)] }; }
conv_digit
/** conv_digit * */
private byte convertDigit(byte c) { if(c < 0) return -1; return conv_digit[c]; }
ISSPACE
/** ISSPACE * */
private boolean isSpace(int str) { byte c; if(str == end || (c = data[str]) < 0) { return false; } return space[c]; } private boolean getSign() { boolean sign = true; if(beg < end) { if(data[beg] == '+') { beg++; } else if(data[beg] == '-') { beg++; sign = false; } } return sign; } private void ignoreLeadingWhitespace() { while(isSpace(beg)) beg++; } private void figureOutBase() { if(base <= 0) { if(beg < end && data[beg] == '0') { if(beg + 1 < end) { switch(data[beg +1]) { case 'x': case 'X': base = 16; break; case 'b': case 'B': base = 2; break; case 'o': case 'O': base = 8; break; case 'd': case 'D': base = 10; break; default: base = 8; } } else { base = 8; } } else if(base < -1) { base = -base; } else { base = 10; } } } private int calculateLength() { int len; byte second = ((beg +1 < end) && data[beg] == '0') ? data[beg +1] : (byte)0; switch(base) { case 2: len = 1; if(second == 'b' || second == 'B') { beg +=2; } break; case 3: len = 2; break; case 8: if(second == 'o' || second == 'O') { beg +=2; } case 4: case 5: case 6: case 7: len = 3; break; case 10: if(second == 'd' || second == 'D') { beg +=2; } case 9: case 11: case 12: case 13: case 14: case 15: len = 4; break; case 16: len = 4; if(second == 'x' || second == 'X') { beg +=2; } break; default: if(base < 2 || 36 < base) { throw runtime.newArgumentError("illegal radix " + base); } if(base <= 32) { len = 5; } else { len = 6; } break; } return len; } private void squeezeZeroes() { byte c; if(beg < end && data[beg] == '0') { beg++; int us = 0; while((beg < end) && ((c = data[beg]) == '0' || c == '_')) { if(c == '_') { if(++us >= 2) { break; } } else { us += 0; } beg++; } if(beg == end || isSpace(beg)) { beg--; } } } private long stringToLong(int nptr, int[] endptr, int base) { if(base < 0 || base == 1 || base > 36) { return 0; } int save = nptr; int s = nptr; boolean overflow = false; while(isSpace(s)) { s++; } if(s != end) { boolean negative = false; if(data[s] == '-') { negative = true; s++; } else if(data[s] == '+') { negative = false; s++; } save = s; byte c; long i = 0; final long cutoff = Long.MAX_VALUE / (long)base; final long cutlim = Long.MAX_VALUE % (long)base; while(s < end) { c = convertDigit(data[s]); if(c == -1 || c >= base) { break; } s++; if(i > cutoff || (i == cutoff && c > cutlim)) { overflow = true; } else { i *= base; i += c; } } if(s != save) { if(endptr != null) { endptr[0] = s; } if(overflow) { throw new ERange(negative ? ERange.Kind.Underflow : ERange.Kind.Overflow); } if(negative) { return -i; } else { return i; } } } if(endptr != null) { if(save - nptr >= 2 && (data[save-1] == 'x' || data[save-1] == 'X') && data[save-2] == '0') { endptr[0] = save-1; } else { endptr[0] = nptr; } } return 0; } public RubyInteger byteListToInum() { if (str == null) { if (badcheck) invalidString("Integer"); return RubyFixnum.zero(runtime); } ignoreLeadingWhitespace(); boolean sign = getSign(); if (beg < end) { if(data[beg] == '+' || data[beg] == '-') { if (badcheck) invalidString("Integer"); return RubyFixnum.zero(runtime); } } figureOutBase(); int len = calculateLength(); squeezeZeroes(); byte c = 0; if (beg < end) { c = data[beg]; } c = convertDigit(c); if (c < 0 || c >= base) { if (badcheck) invalidString("Integer"); return RubyFixnum.zero(runtime); } if (base <= 10) { len *= trailingLength(); } else { len *= (end - beg); } if (len < Long.SIZE-1) { int[] endPlace = new int[] { beg }; long val = stringToLong(beg, endPlace, base); if (endPlace[0] < end && data[endPlace[0]] == '_') { return bigParse(len, sign); } if (badcheck) { if (endPlace[0] == beg) { invalidString("Integer"); // no number } while (isSpace(endPlace[0])) { endPlace[0]++; } if (endPlace[0] < end) { invalidString("Integer"); // trailing garbage } } return sign ? runtime.newFixnum(val) : runtime.newFixnum(-val); } return bigParse(len, sign); } private int trailingLength() { int newLen = 0; for (int i = beg; i < end; i++) { if (Character.isDigit(data[i])) newLen++; else return newLen; } return newLen; } private RubyInteger bigParse(int len, boolean sign) { if (badcheck && beg < end && data[beg] == '_') { invalidString("Integer"); } char[] result = new char[end - beg]; int resultIndex = 0; byte nondigit = -1; // str2big_scan_digits { while(beg < end) { byte c = data[beg++]; char cx = (char) c; if (c == '_') { if (nondigit != -1) { if (badcheck) invalidString("Integer"); break; } nondigit = c; continue; } else if((c = convertDigit(c)) < 0) { break; } if (c >= base) break; nondigit = -1; result[resultIndex++] = cx; } if (resultIndex == 0) return RubyFixnum.zero(runtime); int tmpStr = beg; if (badcheck) { // no beg-- here because we don't null-terminate strings if (str.getBegin()+1 < tmpStr && data[tmpStr-1] == '_') invalidString("Integer"); while (tmpStr < end && Character.isWhitespace(data[tmpStr])) tmpStr++; if (tmpStr < end) invalidString("Integer"); } } String s = new String(result, 0, resultIndex); BigInteger z = (base == 10) ? stringToBig(s) : new BigInteger(s, base); if (!sign) z = z.negate(); if (badcheck) { if (str.getBegin() + 1 < beg && data[beg -1] == '_') { invalidString("Integer"); } while(beg < end && isSpace(beg)) beg++; if (beg < end) invalidString("Integer"); } return RubyBignum.bignorm(runtime, z); } private BigInteger stringToBig(String str) { str = StringSupport.delete(str, '_'); int size = str.length(); int nDigits = 512; if (size < nDigits) nDigits = size; int j = size - 1; int i = j - nDigits + 1; BigInteger digits[] = new BigInteger[j / nDigits + 1]; for(int z = 0; j >= 0; z++) { digits[z] = new BigInteger(str.substring(i, j + 1).trim()); j = i - 1; i = j - nDigits + 1; if(i < 0) { i = 0; } } BigInteger b10x = BigInteger.TEN.pow(nDigits); int n = digits.length; while(n > 1) { i = 0; j = 0; while(i < n / 2) { digits[i] = digits[j].add(digits[j + 1].multiply(b10x)); i += 1; j += 2; } if(j == n-1) { digits[i] = digits[j]; i += 1; } n = i; b10x = b10x.multiply(b10x); } return digits[0]; } public static class ERange extends RuntimeException { public enum Kind { Overflow, Underflow } private final Kind kind; public ERange(Kind kind) { super(); this.kind = kind; } public Kind getKind() { return kind; } }
rb_invalid_str
/** rb_invalid_str * */
private void invalidString(String type) { IRubyObject s = RubyString.newString(runtime, str).inspect(); throw runtime.newArgumentError("invalid value for " + type + "(): " + s); } }