// @tag enterprise 
/**
 * @class Ext.data.amf.Packet
 * This class represents an Action Message Format (AMF) Packet.  It contains all
 * the logic required to decode an AMF Packet from a byte array.
 * To decode a Packet, first construct a Packet:
 *
 *     var packet = Ext.create('Ext.data.amf.Packet');
 *
 * Then use the decode method to decode an AMF byte array:
 *
 *     packet.decode(bytes);
 *
 * where "bytes" is a Uint8Array or an array of numbers representing the binary
 * AMF data.
 *
 * To access the decoded data use the #version, #headers, and #messages properties:
 *
 *     console.log(packet.version, packet.headers, packet.messages);
 *
 * For more information on working with AMF data please refer to the
 * [AMF Guide](../guides/backend_connectors/amf.html).
 */
Ext.define('Ext.data.amf.Packet', function() {
    var twoPowN52 = Math.pow(2, -52),
        twoPow8 = Math.pow(2, 8),
        pos = 0,
        bytes, strings, objects, traits;
 
    return {
        /**
         * @property {Array} headers 
         * @readonly
         * The decoded headers. Each header has the following properties:
         *
         * - `name`: String
         * The header name. Typically identifies a remote operation or method to
         * be invoked by this context header.
         * - `mustUnderstand`: Boolean
         * If `true` this flag instructs the endpoint to abort and generate an
         * error if the header is not understood.
         * - `byteLength`: Number
         * If the byte-length of a header is known it can be specified to optimize
         * memory allocation at the remote endpoint.
         * - `value`: Mixed
         * The header value
         */
 
        /**
         * @property {Array} messages 
         * @readonly
         * The decoded messages. Each message has the following properties:
         *
         * - `targetURI`: String
         * Describes which operation, function, or method is to be remotely
         * invoked.
         * - `responseURI`: String
         * A unique operation name
         * - `byteLength`: Number
         * Optional byte-length of the message body
         * - `body`: Mixed
         * The message body
         */
 
        /**
         * @property {Number} version 
         * @readonly
         * The AMF version number (0 or 3)
         */
 
        /**
         * Mapping of AMF data types to the names of the methods responsible for
         * reading them.
         * @private
         */
        typeMap: {
            // AMF0 mapping 
            0: {
                0: 'readDouble',
                1: 'readBoolean',
                2: 'readAmf0String',
                3: 'readAmf0Object',
                5: 'readNull',
                6: 'readUndefined',
                7: 'readReference',
                8: 'readEcmaArray',
                10: 'readStrictArray',
                11: 'readAmf0Date',
                12: 'readLongString',
                13: 'readUnsupported',
                15: 'readAmf0Xml',
                16: 'readTypedObject'
            },
            // AMF3 mapping 
            3: {
                0: 'readUndefined',
                1: 'readNull',
                2: 'readFalse',
                3: 'readTrue',
                4: 'readUInt29',
                5: 'readDouble',
                6: 'readAmf3String',
                7: 'readAmf3Xml',
                8: 'readAmf3Date',
                9: 'readAmf3Array',
                10: 'readAmf3Object',
                11: 'readAmf3Xml',
                12: 'readByteArray'
            }
        },
 
        /**
         * Decodes an AMF btye array and sets the decoded data as the
         * Packet's #version, #headers, and #messages properties
         * @param {Array} byteArray A byte array containing the encoded AMF data.
         * @return {Ext.data.amf.Packet} this AMF Packet
         */
        decode: function(byteArray) {
            var me = this,
                headers = me.headers = [],
                messages = me.messages = [],
                headerCount, messageCount;
 
            pos = 0;
 
            bytes = me.bytes = byteArray;
 
            // The strings array holds references to all of the deserialized 
            // AMF3 strings for a given header value or message body so that 
            // repeat instances of the same string can be deserialized by 
            // reference 
            strings = me.strings = [];
 
            // The objects array holds references to deserialized objects so 
            // that repeat occurrences of the same object instance in the byte 
            // array can be deserialized by reference. 
            // If version is AMF0 this array holds anonymous objects, typed 
            // objects, arrays, and ecma-arrays. 
            // If version is AMF3 this array holds instances of Object, Array, XML, 
            // XMLDocument, ByteArray, Date, and instances of user defined Classes 
            objects = me.objects = [];
 
            // The traits array holds references to the "traits" (the 
            // characteristics of objects that define a strong type such as the 
            // class name and public member names) of deserialized AMF3 objects 
            // so that if they are repeated they can be deserialized by reference. 
            traits = me.traits = [];
 
            // The first two bytes of an AMF packet contain the AMF version 
            // as an unsigned 16 bit integer. 
            me.version = me.readUInt(2);
 
            // the next 2 bytes contain the header count 
            for (headerCount = me.readUInt(2); headerCount--;) {
                headers.push({
                    name: me.readAmf0String(),
                    mustUnderstand: me.readBoolean(),
                    byteLength: me.readUInt(4),
                    value: me.readValue()
                });
                // reset references (reference indices are local to each header) 
                strings = me.strings = [];
                objects = me.objects = [];
                traits = me.traits = [];
            }
 
            // The 2 bytes immediately after the header contain the message count. 
            for (messageCount = me.readUInt(2); messageCount--;) {
                messages.push({
                    targetURI: me.readAmf0String(),
                    responseURI: me.readAmf0String(),
                    byteLength: me.readUInt(4),
                    body: me.readValue()
                });
                // reset references (reference indices are local to each message) 
                strings = me.strings = [];
                objects = me.objects = [];
                traits = me.traits = [];
            }
 
            // reset the pointer 
            pos = 0;
            // null the bytes array and reference arrays to free up memory. 
            bytes = strings = objects = traits =
                me.bytes = me.strings = me.objects = me.traits = null;
 
            return me;
        },
 
 
        /**
         * Decodes an AMF3 byte array and that has one value and returns it.
         * Note: Destroys previously stored data in this Packet.
         * @param {Array} byteArray A byte array containing the encoded AMF data.
         * @return {Object} the decoded object
         */
        decodeValue: function(byteArray) {
            var me = this;
 
            bytes = me.bytes = byteArray;
 
            // reset the pointer 
            pos = 0;
 
            // The first two bytes of an AMF packet contain the AMF version 
            // as an unsigned 16 bit integer. 
            me.version = 3;
 
            // The strings array holds references to all of the deserialized 
            // AMF3 strings for a given header value or message body so that 
            // repeat instances of the same string can be deserialized by 
            // reference 
            strings = me.strings = [];
 
            // The objects array holds references to deserialized objects so 
            // that repeat occurrences of the same object instance in the byte 
            // array can be deserialized by reference. 
            // If version is AMF0 this array holds anonymous objects, typed 
            // objects, arrays, and ecma-arrays. 
            // If version is AMF3 this array holds instances of Object, Array, XML, 
            // XMLDocument, ByteArray, Date, and instances of user defined Classes 
            objects = me.objects = [];
 
            // The traits array holds references to the "traits" (the 
            // characteristics of objects that define a strong type such as the 
            // class name and public member names) of deserialized AMF3 objects 
            // so that if they are repeated they can be deserialized by reference. 
            traits = me.traits = [];
 
            // read one value and return it 
            return me.readValue();
        },
 
 
 
        /**
         * Parses an xml string and returns an xml document
         * @private
         * @param {String} xml 
         */
        parseXml: function(xml) {
            var doc;
 
            if (window.DOMParser) {
                doc = (new DOMParser()).parseFromString(xml, "text/xml");
            } else {
                doc = new ActiveXObject("Microsoft.XMLDOM");
                doc.loadXML(xml);
            }
 
            return doc;
        },
 
        /**
         * Reads an AMF0 date from the byte array
         * @private
         */
        readAmf0Date: function() {
            var date = new Date(this.readDouble());
            // An AMF0 date type ends with a 16 bit integer time-zone, but 
            // according to the spec time-zone is "reserved, not supported, 
            // should be set to 0x000". 
            pos += 2; // discard the time zone 
            return date;
        },
 
        /**
         * Reads an AMF0 Object from the byte array
         * @private
         */
        readAmf0Object: function(obj) {
            var me = this,
                key;
 
            obj = obj || {};
 
            // add the object to the objects array so that the AMF0 reference 
            // type decoder can refer to it by index if needed. 
            objects.push(obj);
 
            // An AMF0 object consists of a series of string keys and variable- 
            // type values.  The end of the series is marked by an empty string 
            // followed by the object-end marker (9). 
            while ((key = me.readAmf0String()) || bytes[pos] !== 9) {
                obj[key] = me.readValue();
            }
 
            // move the pointer past the object-end marker 
            pos++;
 
            return obj;
        },
 
        /**
         * Reads an AMF0 string from the byte array
         * @private
         */
        readAmf0String: function() {
            // AMF0 strings begin with a 16 bit byte-length header. 
            return this.readUtf8(this.readUInt(2));
        },
 
        readAmf0Xml: function() {
            return this.parseXml(this.readLongString());
        },
 
        readAmf3Array: function() {
            var me = this,
                header = me.readUInt29(),
                count, key, array, i;
 
            // AMF considers Arrays in two parts, the dense portion and the 
            // associative portion. The binary representation of the associative 
            // portion consists of name/value pairs (potentially none) terminated 
            // by an empty string. The binary representation of the dense portion 
            // is the size of the dense portion (potentially zero) followed by an 
            // ordered list of values (potentially none). 
            if (header & 1) {
                // If the first (low) bit is a 1 read an array instance.  The 
                // remaining 1-28 bits are used to encode the length of the 
                // dense portion of the array. 
                count = (header >> 1);
                // First read the associative portion of the array (if any).  If 
                // there is an associative portion, the array will be read as a 
                // javascript object, otherwise it will be a javascript array. 
                key = me.readAmf3String();
                if (key) {
                    // First key is not an empty string - this is an associative 
                    // array.  Read keys and values from the byte array until 
                    // we get to an empty string key 
                    array = {};
                    objects.push(array);
                    do {
                        array[key] = me.readValue();
                    } while((key = me.readAmf3String()));
                    // The dense portion of the array is then read into the 
                    // associative object, keyed by ordinal index. 
                    for (= 0; i < count; i++) {
                        array[i] = me.readValue();
                    }
                } else {
                    // First key is an empty string - this is an array with 
                    // ordinal indices. 
                    array = [];
                    objects.push(array);
                    for (= 0; i < count; i++) {
                        array.push(me.readValue());
                    }
                }
            } else {
                // If the first (low) bit is a 0 read an array reference. The 
                // remaining 1-28 bits are used to encode the reference index 
                array = objects[header >> 1];
            }
 
            return array;
        },
 
        /**
         * Reads an AMF3 date from the byte array
         * @private
         */
        readAmf3Date: function() {
            var me = this,
                header = me.readUInt29(),
                date;
 
            if (header & 1) {
                // If the first (low) bit is a 1, this is a date instance. 
                date = new Date(me.readDouble());
                objects.push(date);
            } else {
                // If the first (low) bit is a 0, this is a date reference. 
                // The remaining 1-28 bits encode the reference index 
                date = objects[header >> 1];
            }
 
            return date;
        },
 
        /**
         * Reads an AMF3 object from the byte array
         * @private
         */
        readAmf3Object: function() {
            var me = this,
                header = me.readUInt29(),
                members = [],
                headerLast3Bits, memberCount, className,
                dynamic, objectTraits, obj, key, klass, i;
 
            // There are 3 different types of object headers, distinguishable 
            // by the 1-3 least significant bits.  All object instances have 
            // a 1 in the low bit position, while references have a 0: 
            // 
            // 0    : object reference 
            // 011  : traits 
            // 01   : traits-ref 
            // 111  : traits-ext 
            if (header & 1) {
                // first (low) bit of 1, denotes an encoded object instance 
                // The next string is the class name. 
                headerLast3Bits = (header & 0x07);
                if (headerLast3Bits === 3) {
                    // If the 3 least significant bits of the header are "011" 
                    // then trait information follows. 
                    className = me.readAmf3String();
                    // A 1 in the header's 4th least significant byte position 
                    // indicates that dynamic members may follow the sealed 
                    // members. 
                    dynamic = !!(header & 0x08);
                    // Shift off the 4 least significant bits, and the remaining 
                    // 1-25 bits encode the number of sealed member names. Read 
                    // as many strings from the byte array as member names. 
                    memberCount = (header >> 4);
                    for (= 0; i < memberCount; i++) {
                        members.push(me.readAmf3String());
                    }
                    objectTraits = {
                        className: className,
                        dynamic: dynamic,
                        members: members
                    };
                    // An objects traits are cached in the traits array enabling 
                    // the traits for a given class to only be encoded once for 
                    // a series of instances. 
                    traits.push(objectTraits);
                } else if ((header & 0x03) === 1) {
                    // If the 2 least significant bits are "01", then a traits 
                    // reference follows.  The remaining 1-27 bits are used 
                    // to encode the trait reference index. 
                    objectTraits = traits[header >> 2];
                    className = objectTraits.className;
                    dynamic = objectTraits.dynamic;
                    members = objectTraits.members;
                    memberCount = members.length;
                } else if (headerLast3Bits === 7) {
                    // if the 3 lease significant bits are "111" then 
                    // externalizable trait data follows 
 
                    // TODO: implement externalizable traits 
                }
 
                if (className) {
                    klass = Ext.ClassManager.getByAlias('amf.' + className);
                    obj = klass ? new klass() : {$className: className};
                } else {
                    obj = {};
                }
                objects.push(obj);
 
                // read the sealed member values 
                for (= 0; i < memberCount; i++) {
                    obj[members[i]] = me.readValue();
                }
 
                if (dynamic) {
                    // If the dynamic flag is set, dynamic members may follow 
                    // the sealed members. Read key/value pairs until we 
                    // encounter an empty string key signifying the end of the 
                    // dynamic members. 
                    while ((key = me.readAmf3String())) {
                        obj[key] = me.readValue();
                    }
                }
 
                // finally, check if we need to convert this class 
                if ((!klass) && this.converters[className]) {
                    obj = this.converters[className](obj);
                }
 
            } else {
                // If the first (low) bit of the header is a 0, this is an 
                // object reference. The remaining 1-28 significant bits are 
                // used to encode an object reference index. 
                obj = objects[header >> 1];
            }
 
            return obj;
        },
 
        /**
         * Reads an AMF3 string from the byte array
         * @private
         */
        readAmf3String: function() {
            var me = this,
                header = me.readUInt29(),
                value;
 
            if (header & 1) {
                // If the first (low) bit is a 1, this is a string literal. 
                // Discard the low bit.  The remaining 1-28 bits are used to 
                // encode the string's byte-length. 
                value = me.readUtf8(header >> 1);
                if (value) {
                    // the emtpy string is never encoded by reference 
                    strings.push(value);
                }
                return value;
            } else {
                // If the first (low) bit is a 0, this is a string reference. 
                // Discard the low bit, then look up and return the reference 
                // from the strings array using the remaining 1-28 bits as the 
                // index. 
                return strings[header >> 1];
            }
        },
 
        /**
         * Reads an AMF3 XMLDocument type or XML type from the byte array
         * @private
         */
        readAmf3Xml: function() {
            var me = this,
                header = me.readUInt29(),
                doc;
 
            if (header & 1) {
                // If the first (low) bit is a 1, this is an xml instance. The 
                // remaining 1-28 bits encode the byte-length of the xml string. 
                doc = me.parseXml(me.readUtf8(header >> 1));
                objects.push(doc);
            } else {
                // if the first (low) bit is a 1, this is an xml reference. The 
                // remaining 1-28 bits encode the reference index. 
                doc = objects[header >> 1];
            }
 
            return doc;
        },
 
        /**
         * Reads an AMF0 boolean from the byte array
         * @private
         */
        readBoolean: function() {
            return !!bytes[pos++];
        },
 
        /**
         * Reads an AMF3 ByteArray type from the byte array
         * @private
         */
        readByteArray: function() {
            var header = this.readUInt29(),
                byteArray, end;
 
            if (header & 1) {
                // If the first (low) bit is a 1, this is a ByteArray instance. 
                // The remaining 1-28 bits encode the ByteArray's byte-length. 
                end = pos + (header >> 1);
                // Depending on the browser, "bytes" may be either a Uint8Array 
                // or an Array.  Uint8Arrays don't have Array methods, so 
                // we have to use Array.prototype.slice to get the byteArray 
                byteArray = Array.prototype.slice.call(bytes, pos, end);
                objects.push(byteArray);
                // move the pointer to the first byte after the byteArray that 
                // was just read 
                pos = end;
            } else {
                // if the first (low) bit is a 1, this is a ByteArray reference. 
                // The remaining 1-28 bits encode the reference index. 
                byteArray = objects[header >> 1];
            }
 
            return byteArray;
        },
 
        /**
         * Reads a IEEE 754 double-precision binary floating-point number
         * @private
         */
        readDouble: function() {
            var byte1 = bytes[pos++],
                byte2 = bytes[pos++],
                // the first bit of byte1 is the sign (0 = positive, 1 = negative. 
                // We read this bit by shifting the 7 least significant bits of 
                // byte1 off to the right. 
                sign = (byte1 >> 7) ? -1 : 1,
                // the exponent takes up the next 11 bits. 
                exponent =
                    // extract the 7 least significant bits from byte1 and then 
                    // shift them left by 4 bits to make room for the 4 remaining 
                    // bits from byte 2 
                    (((byte1 & 0x7F) << 4)
                     // add the 4 most significant bits from byte 2 to complete 
                     // the exponent 
                     | (byte2 >> 4)),
                // the remaining 52 bits make up the significand. read the 4 
                // least significant bytes of byte 2 to begin the significand 
                significand = (byte2 & 0x0F),
                // The most significant bit of the significand is always 1 for 
                // a normalized number, therefore it is not stored. This bit is 
                // referred to as the "hidden bit". The true bit width of the 
                // significand is 53 if you include the hidden bit. An exponent 
                // of 0 indicates that this is a subnormal number, and subnormal 
                // numbers always have a 0 hidden bit. 
                hiddenBit = exponent ? 1 : 0,
                i = 6;
 
            // The operands of all bitwise operators in javascript are converted 
            // to signed 32 bit integers.  It is therefore impossible to construct 
            // the 52 bit significand by repeatedly shifting its bits and then 
            // bitwise OR-ing the result with the the next byte. To work around 
            // this issue, we repeatedly multiply the significand by 2^8 which 
            // produces the same result as (significand << 8), then we add the 
            // next byte, which has the same result as a bitwise OR. 
            while (i--) {
                significand = (significand * twoPow8) + bytes[pos++];
            }
 
            if (!exponent) {
                if (!significand) {
                    // if both exponent and significand are 0, the number is 0 
                    return 0;
                }
                // If the exponent is 0, but the significand is not 0, this 
                // is a subnormal number. Subnormal numbers are encoded with a 
                // biased exponent of 0, but are interpreted with the value of 
                // the smallest allowed exponent, which is one greater. 
                exponent = 1;
            }
 
            // 0x7FF (2047) is a special exponent value that represents infinity 
            // if the significand is 0, and NaN if the significand is not 0 
            if (exponent === 0x7FF) {
                return significand ? NaN : (Infinity * sign);
            }
 
            return sign *
                // The exponent is encoded using an offset binary 
                // representation with the zero offset being 0x3FF (1023), 
                // so we have to subtract 0x3FF to get the true exponent 
                Math.pow(2, exponent - 0x3FF) *
                // convert the significand to its decimal value by multiplying 
                // it by 2^52 and then add the hidden bit 
                (hiddenBit + twoPowN52 * significand);
        },
 
        /**
         * Reads an AMF0 ECMA Array from the byte array
         * @private
         */
        readEcmaArray: function() {
            // An ecma array type is encoded exactly like an anonymous object 
            // with the exception that it has a 32 bit "count" at the beginning. 
            // We handle emca arrays by just throwing away the count and then 
            // letting the object decoder handle the rest. 
            pos += 4;
            return this.readAmf0Object();
        },
 
        /**
         * Returns false.  Used for reading the false type
         * @private
         */
        readFalse: function() {
            return false;
        },
 
        /**
         * Reads a long string (longer than 65535 bytes) from the byte array
         * @private
         */
        readLongString: function() {
            // long strings begin with a 32 bit byte-length header. 
            return this.readUtf8(this.readUInt(4));
        },
 
        /**
         * Returns null.  Used for reading the null type
         * @private
         */
        readNull: function() {
            return null;
        },
 
        /**
         * Reads a reference from the byte array.  Reference types are used to
         * avoid duplicating data if the same instance of a complex object (which
         * is defined in AMF0 as an anonymous object, typed object, array, or
         * ecma-array) is included in the data more than once.
         * @private
         */
        readReference: function() {
            // a reference type contains a single 16 bit integer that represents 
            // the index of an already deserialized object in the objects array 
            return objects[this.readUInt(2)];
        },
 
        /**
         * Reads an AMF0 strict array (an array with ordinal indices)
         * @private
         */
        readStrictArray: function() {
            var me = this,
                len = me.readUInt(4),
                arr = [];
 
            objects.push(arr);
 
            while (len--) {
                arr.push(me.readValue());
            }
 
            return arr;
        },
 
        /**
         * Returns true.  Used for reading the true type
         * @private
         */
        readTrue: Ext.returnTrue,
 
        /**
         * Reads an AMF0 typed object from the byte array
         * @private
         */
        readTypedObject: function() {
            var me = this,
                className = me.readAmf0String(),
                klass, instance, modified;
 
            klass = Ext.ClassManager.getByAlias('amf.' + className);
            instance = klass ? new klass() : {$className: className}; // if there is no klass, mark the classname for easier parsing of returned results 
 
            modified = me.readAmf0Object(instance);
 
            // check if we need to convert this class 
            if ((!klass) && this.converters[className]) {
                modified = this.converters[className](instance);
            }
            return modified;
        },
 
        /**
         * Reads an unsigned integer from the byte array
         * @private
         * @param {Number} byteCount the number of bytes to read, e.g. 2 to read
         * a 16 bit integer, 4 to read a 32 bit integer, etc.
         * @return {Number}
         */
        readUInt: function(byteCount) {
            var i = 1,
                result;
 
            // read the first byte 
            result = bytes[pos++];
            // if this is a multi-byte int, loop over the remaining bytes 
            for (; i < byteCount; ++i) {
                // shift the result 8 bits to the left and add the next byte. 
                result = (result << 8) | bytes[pos++];
            }
 
            return result;
        },
 
        /**
         * Reads an unsigned 29-bit integer from the byte array.
         * AMF 3 makes use of a special compact format for writing integers to
         * reduce the number of bytes required for encoding. As with a normal
         * 32-bit integer, up to 4 bytes are required to hold the value however
         * the high bit of the first 3 bytes are used as flags to determine
         * whether the next byte is part of the integer. With up to 3 bits of
         * the 32 bits being used as flags, only 29 significant bits remain for
         * encoding an integer. This means the largest unsigned integer value
         * that can be represented is 2^29-1.
         *
         *           (hex)         :                (binary)
         * 0x00000000 - 0x0000007F :  0xxxxxxx
         * 0x00000080 - 0x00003FFF :  1xxxxxxx 0xxxxxxx
         * 0x00004000 - 0x001FFFFF :  1xxxxxxx 1xxxxxxx 0xxxxxxx
         * 0x00200000 - 0x3FFFFFFF :  1xxxxxxx 1xxxxxxx 1xxxxxxx xxxxxxxx
         * @private
         * @return {Number}
         */
        readUInt29: function() {
            var value = bytes[pos++],
                nextByte;
 
            if (value & 0x80) {
                // if the high order bit of the first byte is a 1, the next byte 
                // is also part of this integer. 
                nextByte = bytes[pos++];
                // remove the high order bit from both bytes before combining them 
                value = ((value & 0x7F) << 7) | (nextByte & 0x7F);
                if (nextByte & 0x80) {
                    // if the high order byte of the 2nd byte is a 1, then 
                    // there is a 3rd byte 
                    nextByte = bytes[pos++];
                    // remove the high order bit from the 3rd byte before 
                    // adding it to the value 
                    value = (value << 7) | (nextByte & 0x7F);
                    if (nextByte & 0x80) {
                        // 4th byte is also part of the integer 
                        nextByte = bytes[pos++];
                        // use all 8 bits of the 4th byte 
                        value = (value << 8) | nextByte;
                    }
 
                }
            }
 
            return value;
        },
 
        /**
         * @method
         * Returns undefined.  Used for reading the undefined type
         * @private
         */
        readUndefined: Ext.emptyFn,
 
        /**
         * @method
         * Returns undefined.  Used for reading the unsupported type
         * @private
         */
        readUnsupported: Ext.emptyFn,
 
        /**
         * Reads a UTF-8 string from the byte array
         * @private
         * @param {Number} byteLength The number of bytes to read
         * @return {String}
         */
        readUtf8: function(byteLength) {
            var end = pos + byteLength, // the string's end position 
                chars = [],
                charCount = 0,
                maxCharCount = 65535,
                charArrayCount = 1,
                result = [],
                i = 0,
                charArrays, byteCount, charCode;
 
            charArrays = [chars];
 
            // UTF-8 characters may be encoded using 1-4 bytes. The number of 
            // bytes that a character consumes is determined by reading the 
            // leading byte.  Values 0-127 in the leading byte indicate a single- 
            // byte ASCII-compatible character. Values 192-223 (bytes with "110" 
            // in the high-order position) indicate a 2-byte character, values 
            // 224-239 (bytes with "1110" in the high-order position) indicate a 
            // 3-byte character, and values 240-247 (bytes with "11110" in the 
            // high-order position) indicate a 4-byte character. The remaining 
            // bits of the leading byte hold bits of the encoded character, with 
            // leading zeros if necessary. 
            // 
            // The continuation bytes all have "10" in the high-order position, 
            // which means only the 6 least significant bits of continuation 
            // bytes are available to hold the bits of the encoded character. 
            // 
            // The following table illustrates the binary format of UTF-8 
            // characters: 
            // 
            // Bits     Byte 1      Byte 2      Byte 3      Byte 4 
            // ----------------------------------------------------- 
            //  7       0xxxxxxx 
            // 11       110xxxxx    10xxxxxx 
            // 16       1110xxxx    10xxxxxx    10xxxxxx 
            // 21       11110xxx    10xxxxxx    10xxxxxx    10xxxxxx 
            while (pos < end) {
                // read a byte from the byte array - if the byte's value is less 
                // than 128 we are dealing with a single byte character 
                charCode = bytes[pos++];
                if (charCode > 127) {
                    // if the byte's value is greater than 127 we are dealing 
                    // with a multi-byte character. 
                    if (charCode > 239) {
                        // a leading-byte value greater than 239 means this is a 
                        // 4-byte character 
                        byteCount = 4;
                        // Use only the 3 least-significant bits of the leading 
                        // byte of a 4-byte character. This is achieved by 
                        // applying the following bit mask: 
                        // (charCode & 0x07) 
                        // which is equivalent to: 
                        //     11110xxx (the byte) 
                        // AND 00000111 (the mask) 
                        charCode = (charCode & 0x07);
                    } else if (charCode > 223) {
                        // a leading-byte value greater than 223 but less than 
                        // 240 means this is a 3-byte character 
                        byteCount = 3;
                        // Use only the 4 least-significant bits of the leading 
                        // byte of a 3-byte character. This is achieved by 
                        // applying the following bit mask: 
                        // (charCode & 0x0F) 
                        // which is equivalent to: 
                        //     1110xxxx (the byte) 
                        // AND 00001111 (the mask) 
                        charCode = (charCode & 0x0F);
                    } else {
                        // a leading-byte value less than 224 but (implicitly) 
                        // greater than 191 means this is a 2-byte character 
                        byteCount = 2;
                        // Use only the 5 least-significant bits of the first 
                        // byte of a 2-byte character. This is achieved by 
                        // applying the following bit mask: 
                        // (charCode & 0x1F) 
                        // which is equivalent to: 
                        //     110xxxxx (the byte) 
                        // AND 00011111 (the mask) 
                        charCode = (charCode & 0x1F);
                    }
 
                    while (--byteCount) {
                        // get one continuation byte. then strip off the leading 
                        // "10" by applying the following bit mask: 
                        // (b & 0x3F) 
                        // which is equialent to: 
                        //     10xxxxxx (the byte) 
                        // AND 00111111 (the mask) 
                        // That leaves 6 remaining bits on the continuation byte 
                        // which are concatenated onto the character's bits 
                        charCode = ((charCode << 6) | (bytes[pos++] & 0x3F));
                    }
                }
 
                chars.push(charCode);
 
                if (++charCount === maxCharCount) {
                    charArrays.push(chars = []);
                    charCount = 0;
                    charArrayCount ++;
                }
            }
 
            // At this point we end up with an array of char arrays, each char 
            // array being no longer than 65,535 characters, the fastest way to 
            // turn these char arrays into strings is to pass them as the 
            // arguments to fromCharCode (fortunately all currently supported 
            // browsers can handle at least 65,535 function arguments). 
            for (; i < charArrayCount; i++) {
                // create a result array containing the strings converted from 
                // the individual character arrays. 
                result.push(String.fromCharCode.apply(String, charArrays[i]));
            }
 
            return result.join('');
        },
 
        /**
         * Reads an AMF "value-type" from the byte array.  Automatically detects
         * the data type by reading the "type marker" from the first byte after
         * the pointer.
         * @private
         */
        readValue: function() {
            var me = this,
                marker = bytes[pos++];
 
            // With the introduction of AMF3, a special type marker was added to 
            // AMF0 to signal a switch to AMF3 serialization. This allows a packet 
            // to start out in AMF 0 and switch to AMF 3 on the first complex type 
            // to take advantage of the more the efficient encoding of AMF 3. 
            if (marker === 17) {
                // change the version to AMF3 when we see a 17 marker 
                me.version = 3;
                marker = bytes[pos++];
            }
 
            return me[me.typeMap[me.version][marker]]();
        },
 
        /**
         * Converters used in converting specific typed Flex classes to JavaScript usable form.
         * @private
         */
        converters: {
            'flex.messaging.io.ArrayCollection': function(obj) {
                return obj.source || []; // array collections have a source var that contains the actual data 
            }
        }
 
    };
});