package com.thaiopensource.datatype.xsd;

import org.relaxng.datatype.ValidationContext;

class Base64BinaryDatatype extends BinaryDatatype {
  static private final byte[] weightTable = makeWeightTable();
  static private final byte INVALID = (byte)-1;
  static private final byte WHITESPACE = (byte)-2;
  static private final byte PADDING = (byte)-3;

  // for efficiency, don't assume whitespace normalized
  boolean lexicallyAllows(String str) {
    return byteCount(str) >= 0;
  }

  String getLexicalSpaceKey() {
    return "base64";
  }

  private static int byteCount(String str) {
    int nChars = 0;
    int nPadding = 0;
    int lastCharWeight = -1;
    for (int i = 0, len = str.length(); i < len; i++) {
      char c = str.charAt(i);
      if (c >= 128)
        return -1;
      int w = weightTable[c];
      switch (w) {
      case WHITESPACE:
        break;
      case PADDING:
        if (++nPadding > 2)
          return -1;
        break;
      case INVALID:
        return -1;
      default:
        if (nPadding > 0)
          return -1;
        lastCharWeight = w;
        nChars++;
        break;
      }
    }
    if (((nChars + nPadding) & 0x3) != 0)
      return -1;
    switch (nPadding) {
    case 1:
      // 1 padding char; last quartet specifies 2 bytes = 16 bits = 6 + 6 + 4 bits
      // lastChar must have 6 - 4 = 2 unused bits
      if ((lastCharWeight & 0x3) != 0)
        return -1;
      break;
    case 2:
      // 2 padding chars; last quartet specifies 1 byte = 8 bits = 6 + 2 bits
      // lastChar must have 6 - 2 = 4 unused bits
      if ((lastCharWeight & 0xF) != 0)
        return -1;
      break;
    }
    return ((nChars + nPadding) >> 2)*3 - nPadding;
  }

  Object getValue(String str, ValidationContext vc) {
    int nBytes = byteCount(str);
    byte[] value = new byte[nBytes];
    int valueIndex = 0;
    int nBytesAccum = 0;
    int accum = 0;
    for (int i = 0, len = str.length(); i < len; i++) {
      int w = weightTable[str.charAt(i)];
      if (w != WHITESPACE) {
        accum <<= 6;
        if (w != PADDING)
          accum |= w;
        if (++nBytesAccum == 4) {
          for (int shift = 16; shift >= 0; shift -= 8) {
            if (valueIndex < nBytes)
              value[valueIndex++] = (byte)((accum >> shift) & 0xFF);
          }
          nBytesAccum = 0;
          accum = 0;
        }
      }
    }
    return value;
  }

  static private byte[] makeWeightTable() {
    byte[] w = new byte[128];
    byte n = INVALID;
    for (int i = 0; i < 128; i++)
      w[i] = n;
    n = 0;
    for (int i = 'A'; i <= 'Z'; i++, n++)
      w[i] = n;
    for (int i = 'a'; i <= 'z'; i++, n++)
      w[i] = n;
    for (int i = '0'; i <= '9'; i++, n++)
      w[i] = n;
    w['+'] = n++;
    w['/'] = n++;
    w[' '] = w['\t'] = w['\r'] = w['\n'] = WHITESPACE;
    w['='] = PADDING;
    return w;
  }

}
