"use strict";

var utf8 = exports;
var replacementChar = "\ufffd";

function utf8Read(buffer, start, end, fatal) {
    var str = "";
    for (var i = start; i < end;) {
        var first = buffer[i++];
        if (first <= 0x7F) {
            str += String.fromCharCode(first);
            continue;
        }

        var needed = 0;
        var codePoint = 0;
        var minCodePoint = 0;

        if (first >= 0xC2 && first <= 0xDF) {
            needed = 1;
            codePoint = first & 0x1F;
            minCodePoint = 0x80;
        } else if (first >= 0xE0 && first <= 0xEF) {
            needed = 2;
            codePoint = first & 0x0F;
            minCodePoint = 0x800;
        } else if (first >= 0xF0 && first <= 0xF4) {
            needed = 3;
            codePoint = first & 0x07;
            minCodePoint = 0x10000;
        } else {
            if (fatal) throw Error("invalid UTF-8 encoding");
            str += replacementChar;
            continue;
        }

        if (i + needed > end) {
            if (fatal) throw Error("invalid UTF-8 encoding");
            str += replacementChar;
            break;
        }

        var valid = true;
        for (var j = 0; j < needed; ++j) {
            var next = buffer[i + j];
            if ((next & 0xC0) !== 0x80) {
                valid = false;
                break;
            }
            codePoint = codePoint << 6 | next & 0x3F;
        }

        if (
            !valid ||
            codePoint < minCodePoint ||
            codePoint > 0x10FFFF ||
            codePoint >= 0xD800 && codePoint <= 0xDFFF
        ) {
            if (fatal) throw Error("invalid UTF-8 encoding");
            str += replacementChar;
            continue;
        }

        i += needed;
        if (codePoint <= 0xFFFF) {
            str += String.fromCharCode(codePoint);
        } else {
            codePoint -= 0x10000;
            str += String.fromCharCode(0xD800 + (codePoint >> 10));
            str += String.fromCharCode(0xDC00 + (codePoint & 0x3FF));
        }
    }
    return str;
}

utf8.length = function utf8_length(string) {
    var len = 0,
        c = 0;
    for (var i = 0; i < string.length; ++i) {
        c = string.charCodeAt(i);
        if (c < 128)
            len += 1;
        else if (c < 2048)
            len += 2;
        else if ((c & 0xFC00) === 0xD800 && (string.charCodeAt(i + 1) & 0xFC00) === 0xDC00) {
            ++i;
            len += 4;
        } else
            len += 3;
    }
    return len;
};

utf8.read = function utf8_read(buffer, start, end) {
    if (end - start < 1)
        return "";
    return utf8Read(buffer, start, end, false);
};

utf8.readStrict = function utf8_read_strict(buffer, start, end) {
    if (end - start < 1)
        return "";
    return utf8Read(buffer, start, end, true);
};

utf8.write = function utf8_write(string, buffer, offset) {
    var start = offset,
        c1,
        c2;
    for (var i = 0; i < string.length; ++i) {
        c1 = string.charCodeAt(i);
        if (c1 < 128) {
            buffer[offset++] = c1;
        } else if (c1 < 2048) {
            buffer[offset++] = c1 >> 6 | 192;
            buffer[offset++] = c1 & 63 | 128;
        } else if ((c1 & 0xFC00) === 0xD800 && ((c2 = string.charCodeAt(i + 1)) & 0xFC00) === 0xDC00) {
            c1 = 0x10000 + ((c1 & 0x03FF) << 10) + (c2 & 0x03FF);
            ++i;
            buffer[offset++] = c1 >> 18 | 240;
            buffer[offset++] = c1 >> 12 & 63 | 128;
            buffer[offset++] = c1 >> 6 & 63 | 128;
            buffer[offset++] = c1 & 63 | 128;
        } else {
            buffer[offset++] = c1 >> 12 | 224;
            buffer[offset++] = c1 >> 6 & 63 | 128;
            buffer[offset++] = c1 & 63 | 128;
        }
    }
    return offset - start;
};
