function byteLength (string) {
  let length = 0

  for (let i = 0, n = string.length; i < n; i++) {
    const code = string.charCodeAt(i)

    if (code >= 0xd800 && code <= 0xdbff && i + 1 < n) {
      const code = string.charCodeAt(i + 1)

      if (code >= 0xdc00 && code <= 0xdfff) {
        length += 4
        i++
        continue
      }
    }

    if (code <= 0x7f) length += 1
    else if (code <= 0x7ff) length += 2
    else length += 3
  }

  return length
}

let toString

if (typeof TextDecoder !== 'undefined') {
  const decoder = new TextDecoder()

  toString = function toString (buffer) {
    return decoder.decode(buffer)
  }
} else {
  toString = function toString (buffer) {
    const len = buffer.byteLength

    let output = ''
    let i = 0

    while (i < len) {
      let byte = buffer[i]

      if (byte <= 0x7f) {
        output += String.fromCharCode(byte)
        i++
        continue
      }

      let bytesNeeded = 0
      let codePoint = 0

      if (byte <= 0xdf) {
        bytesNeeded = 1
        codePoint = byte & 0x1f
      } else if (byte <= 0xef) {
        bytesNeeded = 2
        codePoint = byte & 0x0f
      } else if (byte <= 0xf4) {
        bytesNeeded = 3
        codePoint = byte & 0x07
      }

      if (len - i - bytesNeeded > 0) {
        let k = 0

        while (k < bytesNeeded) {
          byte = buffer[i + k + 1]
          codePoint = (codePoint << 6) | (byte & 0x3f)
          k += 1
        }
      } else {
        codePoint = 0xfffd
        bytesNeeded = len - i
      }

      output += String.fromCodePoint(codePoint)
      i += bytesNeeded + 1
    }

    return output
  }
}

let write

if (typeof TextEncoder !== 'undefined') {
  const encoder = new TextEncoder()

  write = function write (buffer, string, offset = 0, length = byteLength(string)) {
    const len = Math.min(length, buffer.byteLength - offset)
    encoder.encodeInto(string, buffer.subarray(offset, offset + len))
    return len
  }
} else {
  write = function write (buffer, string, offset = 0, length = byteLength(string)) {
    const len = Math.min(length, buffer.byteLength - offset)

    buffer = buffer.subarray(offset, offset + len)

    let i = 0
    let j = 0

    while (i < string.length) {
      const code = string.codePointAt(i)

      if (code <= 0x7f) {
        buffer[j++] = code
        i++
        continue
      }

      let count = 0
      let bits = 0

      if (code <= 0x7ff) {
        count = 6
        bits = 0xc0
      } else if (code <= 0xffff) {
        count = 12
        bits = 0xe0
      } else if (code <= 0x1fffff) {
        count = 18
        bits = 0xf0
      }

      buffer[j++] = bits | (code >> count)
      count -= 6

      while (count >= 0) {
        buffer[j++] = 0x80 | ((code >> count) & 0x3f)
        count -= 6
      }

      i += code >= 0x10000 ? 2 : 1
    }

    return len
  }
}

module.exports = {
  byteLength,
  toString,
  write
}
