/**
 * @fileOverview Transport
 * @todo 支持chunked传输，优势：
 * 可以将大文件分成小块，挨个传输，可以提高大文件成功率，当失败的时候，也只需要重传那小部分，
 * 而不需要重头再传一次。另外断点续传也需要用chunked方式。
 */
define([
    '../../base',
    './runtime'
], function( Base, Html5Runtime ) {

    var noop = Base.noop,
        $ = Base.$;

    return Html5Runtime.register( 'Transport', {
        init: function() {
            this._status = 0;
            this._response = null;
        },

        send: function() {
            var owner = this.owner,
                opts = this.options,
                xhr = this._initAjax(),
                blob = owner._blob,
                server = opts.server,
                formData, binary, fr;

            if ( opts.sendAsBinary ) {
                server += (/\?/.test( server ) ? '&' : '?') +
                        $.param( owner._formData );

                binary = blob.getSource();
            } else {
                formData = new FormData();
                $.each( owner._formData, function( k, v ) {
                    formData.append( k, v );
                });

                formData.append( opts.fileVal, blob.getSource(),
                        opts.filename || owner._formData.name || '' );
            }

            if ( opts.withCredentials && 'withCredentials' in xhr ) {
                xhr.open( opts.method, server, true );
                xhr.withCredentials = true;
            } else {
                xhr.open( opts.method, server );
            }

            this._setRequestHeader( xhr, opts.headers );

            if ( binary ) {
                // 强制设置成 content-type 为文件流。
                xhr.overrideMimeType &&
                        xhr.overrideMimeType('application/octet-stream');

                // android直接发送blob会导致服务端接收到的是空文件。
                // bug详情。
                // https://code.google.com/p/android/issues/detail?id=39882
                // 所以先用fileReader读取出来再通过arraybuffer的方式发送。
                if ( Base.os.android ) {
                    fr = new FileReader();

                    fr.onload = function() {
                        xhr.send( this.result );
                        fr = fr.onload = null;
                    };

                    fr.readAsArrayBuffer( binary );
                } else {
                    xhr.send( binary );
                }
            } else {
                xhr.send( formData );
            }
        },

        getResponse: function() {
            return this._response;
        },

        getResponseAsJson: function() {
            return this._parseJson( this._response );
        },

        getStatus: function() {
            return this._status;
        },

        abort: function() {
            var xhr = this._xhr;

            if ( xhr ) {
                xhr.upload.onprogress = noop;
                xhr.onreadystatechange = noop;
                xhr.abort();

                this._xhr = xhr = null;
            }
        },

        destroy: function() {
            this.abort();
        },

        _initAjax: function() {
            var me = this,
                xhr = new XMLHttpRequest(),
                opts = this.options;

            if ( opts.withCredentials && !('withCredentials' in xhr) &&
                    typeof XDomainRequest !== 'undefined' ) {
                xhr = new XDomainRequest();
            }

            xhr.upload.onprogress = function( e ) {
                var percentage = 0;

                if ( e.lengthComputable ) {
                    percentage = e.loaded / e.total;
                }

                return me.trigger( 'progress', percentage );
            };

            xhr.onreadystatechange = function() {

                if ( xhr.readyState !== 4 ) {
                    return;
                }

                xhr.upload.onprogress = noop;
                xhr.onreadystatechange = noop;
                me._xhr = null;
                me._status = xhr.status;

                if ( xhr.status >= 200 && xhr.status < 300 ) {
                    me._response = xhr.responseText;
                    return me.trigger('load');
                } else if ( xhr.status >= 500 && xhr.status < 600 ) {
                    me._response = xhr.responseText;
                    return me.trigger( 'error', 'server' );
                }


                return me.trigger( 'error', me._status ? 'http' : 'abort' );
            };

            me._xhr = xhr;
            return xhr;
        },

        _setRequestHeader: function( xhr, headers ) {
            $.each( headers, function( key, val ) {
                xhr.setRequestHeader( key, val );
            });
        },

        _parseJson: function( str ) {
            var json;

            try {
                json = JSON.parse( str );
            } catch ( ex ) {
                json = {};
            }

            return json;
        }
    });
});