const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

const codes = new Uint8Array(256)

for (let i = 0; i < alphabet.length; i++) {
  codes[alphabet.charCodeAt(i)] = i
}

codes[/* - */ 0x2d] = 62
codes[/* _ */ 0x5f] = 63

function byteLength (string) {
  let len = string.length

  if (string.charCodeAt(len - 1) === 0x3d) len--
  if (len > 1 && string.charCodeAt(len - 1) === 0x3d) len--

  return (len * 3) >>> 2
}

function toString (buffer) {
  const len = buffer.byteLength

  let result = ''

  for (let i = 0; i < len; i += 3) {
    result += (
      alphabet[buffer[i] >> 2] +
      alphabet[((buffer[i] & 3) << 4) | (buffer[i + 1] >> 4)] +
      alphabet[((buffer[i + 1] & 15) << 2) | (buffer[i + 2] >> 6)] +
      alphabet[buffer[i + 2] & 63]
    )
  }

  if (len % 3 === 2) {
    result = result.substring(0, result.length - 1) + '='
  } else if (len % 3 === 1) {
    result = result.substring(0, result.length - 2) + '=='
  }

  return result
};

function write (buffer, string, offset = 0, length = byteLength(string)) {
  const len = Math.min(length, buffer.byteLength - offset)

  for (let i = 0, j = 0; i < len; i += 4) {
    const a = codes[string.charCodeAt(i)]
    const b = codes[string.charCodeAt(i + 1)]
    const c = codes[string.charCodeAt(i + 2)]
    const d = codes[string.charCodeAt(i + 3)]

    buffer[j++] = (a << 2) | (b >> 4)
    buffer[j++] = ((b & 15) << 4) | (c >> 2)
    buffer[j++] = ((c & 3) << 6) | (d & 63)
  }

  return len
};

module.exports = {
  byteLength,
  toString,
  write
}
