/**
 * 
 * @authors yutent (yutent@doui.cc)
 * @date    2016-11-26 16:35:45
 *
 */

"use strict";

define(function(){

    var _Promise = function(callback){
            this.callback = []
            var _this = this

            if(typeof this !== 'object')
                throw new TypeError('Promises must be constructed via new')

            if(typeof callback !== 'function')
                throw new TypeError('Argument must be a function')

            callback(function(val){
                _resolve(_this, val)
            }, function(val){
                _reject(_this, val)
            })
        }
    var self = {
        _state: 1,
        _fired: 1,
        _val: 1,
        callback: 1
    }

    _Promise.prototype = {
        constructor: _Promise,
        _state: 'pending',
        _fired: false,
        _fire: function(yes, no){
            if(this._state === 'rejected'){
                if(typeof no === 'function')
                    no(this._val)
                else
                    throw this._val
            }else{
                if(typeof yes === 'function')
                    yes(this._val)
            }
        },
        _then: function(yes, no){
            if(this._fired){
                var _this = this
                fireCallback(_this, function(){
                    _this._fire(yes, no)
                })
            }else{
                this.callback.push({yes: yes, no: no})
            }
        },
        then: function(yes, no){
            yes = typeof yes === 'function' ? yes : _yes
            no = typeof no === 'function' ? no : _no
            var _this = this
            var next = new _Promise(function(resolve, reject){

                    _this._then(function(val){
                        try{
                            val = yes(val)
                        }catch(err){
                            return reject(err)
                        }
                    }, function(val){
                        try{
                            val = no(val)
                        }catch(err){
                            return reject(err)
                        }
                        resolve(val)
                    })
                })
            for(var i in _this){
                if(!self[i])
                    next[i] = _this[i]
            }
            return next
        },
        done: done,
        catch: fail,
        fail: fail
    }

    _Promise.all = function(arr){
        return _some(false, arr)
    }

    _Promise.race = function(arr){
        return _some(true, arr)
    }

    _Promise.defer = defer

// -----------------------------------------------------------

    

    function _yes(val){
        return val
    }

    function _no(err){
        throw err
    }


    function done(callback){
        return this.then(callback, _no)
    }

    function fail(callback){
        return this.then(_yes, callback)
    }

    function defer(){
        var obj = {}
        obj.promise = new this(function(yes, no){
            obj.resolve = yes
            obj.reject = no
        })
        return obj
    }

    
    //成功的回调
    function _resolve(obj, val){
        if(obj._state !== 'pending')
            return

        if(val && typeof val.then === 'function'){
            var method = val instanceof _Promise ? '_then' : 'then'
            val[method](function(v){
                _transmit(obj, v, true)
            }, function(v){
                _transmit(obj, v, false)
            })
        }else{
            _transmit(obj, val, true)
        }
    }

    //失败的回调
    function _reject(obj, val){
        if(obj._state !== 'pending')
            return

        _transmit(obj, val, false)
    }



    // 改变Promise的_fired值，并保持用户传参，触发所有回调
    function _transmit(obj, val, isResolved){
        obj._fired = true
        obj._val = val
        obj._state = isResolved ? 'fulfilled' : 'rejected'

        fireCallback(obj, function(){
            for(var i in obj.callback){
                obj._fire(obj.callback[i].yes, obj.callback[i].no)
            }
        })
    }


    function fireCallback(obj, callback){
        var isAsync = false

        if(typeof obj.async === 'boolean')
            isAsync = obj.async
        else
            isAsync = obj.async = true

        if(isAsync)
            setTimeout(callback, 0)
        else
            callback()
    }


    function _some(bool, iterable){
        iterable = Array.isArray(iterable) ? iterable : []

        var n = 0
        var res = []
        var end = false

        return new _Promise(function(yes, no){
            if(!iterable.length)
                no(res)

            function loop(obj, idx){
                obj.then(function(val){
                    if(!end){
                        res[idx] = val
                        n++
                        if(bool || n >= iterable.length){
                            yes(bool ? val : res)
                            end = true
                        }
                    }
                }, function(val){
                    end = true
                    no(val)
                })
            }

            for(var i = 0, len = iterable.length; i < len; i++){
                loop(iterable[i], i)
            }
        })
    }


// ---------------------------------------------------------------


    var nativePromise = window.Promise
    if(/native code/.test(nativePromise)){
        nativePromise.prototype.done = done
        nativePromise.prototype.fail = fail
        if(!nativePromise.defer)
            nativePromise.defer = defer
    }

    return window.Promise = nativePromise || _Promise


})
