/*
 * Partial port of http://www.unicode.org/Public/PROGRAMS/CVTUTF/ConvertUTF.c
 *
 * Copyright 2001-2004 Unicode, Inc.
 *
 * Disclaimer
 *
 * This source code is provided as is by Unicode, Inc. No claims are
 * made as to fitness for any particular purpose. No warranties of any
 * kind are expressed or implied. The recipient agrees to determine
 * applicability of information provided. If this file has been
 * purchased on magnetic or optical media from Unicode, Inc., the
 * sole remedy for any claim will be exchange of defective media
 * within 90 days of receipt.
 *
 * Limitations on Rights to Redistribute This Code
 *
 * Unicode, Inc. hereby grants the right to freely use the information
 * supplied in this file in the creation of products supporting the
 * Unicode Standard, and to make copies of this file in any form
 * for internal or external distribution as long as this notice
 * remains attached.
 */

var UTFSTR = {
    halfShift : 10,
    halfBase : 0x0010000,
    halfMask : 0x3FF,
    UNI_SUR_HIGH_START : 0xD800,
    UNI_SUR_HIGH_END : 0xDBFF,
    UNI_SUR_LOW_START : 0xDC00,
    UNI_SUR_LOW_END : 0xDFFF,
    UNI_REPLACEMENT_CHAR : 0x0000FFFD,
    firstByteMark : [0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC],
    offsetsFromUTF8 : [0x00000000, 0x00003080, 0x000E2080, 0x03C82080, 0xFA082080, 0x82082080],
    UNI_MAX_BMP : 0x0000FFFF,
    UNI_MAX_UTF16 : 0x0010FFFF,
    trailingBytesForUTF8 : [
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5],

    isLegalUTF8 : function(arr, start, length) {
        var a;
        var srcptr = start + length;
        switch (length) {
            default: return false;
            // falls through if true
            case 4: if ((a = (arr[--srcptr])) < 0x80 || a > 0xBF) return false;
            case 3: if ((a = (arr[--srcptr])) < 0x80 || a > 0xBF) return false;
            case 2: if ((a = (arr[--srcptr])) > 0xBF) return false;

            switch (arr[start]) {
                case 0xE0: if (a < 0xA0) return false; break;
                case 0xED: if (a > 0x9F) return false; break;
                case 0xF0: if (a < 0x90) return false; break;
                case 0xF4: if (a > 0x8F) return false; break;
                default:   if (a < 0x80) return false;
            }

            case 1: if (arr[start] >= 0x80 && arr[start] < 0xC2) return false;
        }
        if (arr[start] > 0xF4) return false;
        return true;
    },

    UTF8ByteArrayFromUTF16String : function (s, strict) {
        if (typeof strict != "boolean") {
            strict = true;
        }
        var byteArray = [];
        var i = 0;
        while (i < s.length) {
            var bytesToWrite = 0;
            var byteMask = 0xBF;
            var byteMark = 0x80;
            var ch = s.charCodeAt(i++);
            if (ch >= this.UNI_SUR_HIGH_START && ch <= this.UNI_SUR_HIGH_END) {
                if (i < s.length) {
                    var ch2 = s.charCodeAt(i);
                    if (ch2 >= this.UNI_SUR_LOW_START && ch2 <= this.UNI_SUR_LOW_END) {
                        ch = ((ch - this.UNI_SUR_HIGH_START) << this.halfShift) + (ch2 - this.UNI_SUR_LOW_START) + this.halfBase;
                        ++i;
                    } else if (strict) {
                        break;
                    }
                } else {
                    break;
                }
            } else if (strict) {
                if (ch >= this.UNI_SUR_LOW_START && ch <= this.UNI_SUR_LOW_END) {
                    break;
                }
            }
            if (ch < 0x80) {
                bytesToWrite = 1;
            } else if (ch < 0x800) {
                bytesToWrite = 2;
            } else if (ch < 0x10000) {
                bytesToWrite = 3;
            } else if (ch < 0x110000) {
                bytesToWrite = 4;
            } else {
                bytesToWrite = 3;
                ch = this.UNI_REPLACEMENT_CHAR;
            }
            var seq = [];
            switch (bytesToWrite) {
                // falls through
                case 4: seq[seq.length] = (ch | byteMark) & byteMask; ch >>= 6;
                case 3: seq[seq.length] = (ch | byteMark) & byteMask; ch >>= 6;
                case 2: seq[seq.length] = (ch | byteMark) & byteMask; ch >>= 6;
                case 1: seq[seq.length] = ch | this.firstByteMark[bytesToWrite];
            }
            for (var z = seq.length - 1; z > -1; --z) {
                byteArray[byteArray.length] = seq[z];
            }
        }
        return byteArray;
    },

    UTF16StringFromUTF8ByteArray : function(byteArray, strict) {
        if (typeof strict != "boolean") {
            strict = true;
        }
        var s = "";
        var i = 0;
        while (i < byteArray.length) {
            var ch = 0;
            var extraBytesToRead = this.trailingBytesForUTF8[byteArray[i]];
            if (i + extraBytesToRead >= byteArray.length) {
                break;
            }
            if (!this.isLegalUTF8(byteArray, i, extraBytesToRead + 1) ) {
                break;
            }
            switch (extraBytesToRead) {
                // falls through
                case 5: ch += byteArray[i++]; ch <<= 6;
                case 4: ch += byteArray[i++]; ch <<= 6;
                case 3: ch += byteArray[i++]; ch <<= 6;
                case 2: ch += byteArray[i++]; ch <<= 6;
                case 1: ch += byteArray[i++]; ch <<= 6;
                case 0: ch += byteArray[i++];
            }
            ch -= this.offsetsFromUTF8[extraBytesToRead];
            if (ch <= this.UNI_MAX_BMP) {
                if (ch >= this.UNI_SUR_HIGH_START && ch <= this.UNI_SUR_LOW_END) {
                    if (strict) {
                        break;
                    } else {
                        s += String.fromCharCode(this.UNI_REPLACEMENT_CHAR);
                    }
                } else {
                    s += String.fromCharCode(ch);
                }
            } else if (ch > this.UNI_MAX_UTF16) {
                if (strict) {
                    break;
                } else {
                    s += String.fromCharCode(this.UNI_REPLACEMENT_CHAR);
                }
            } else {
                ch -= this.halfBase;
                s += String.fromCharCode(((ch >> this.halfShift) + this.UNI_SUR_HIGH_START));
                s += String.fromCharCode(((ch & this.halfMask) + this.UNI_SUR_LOW_START));
            }
        }
        return s;
    }
};