/**
 * Created by Aaron on 7/3/2015.
 */

import assert from 'assert';
import Promise from 'bluebird';

let yieldHandlers = [];

export function isThenable( obj ) {
    return obj !== void 0 && obj !== null && (obj instanceof Promise || typeof obj.then === 'function');
}

export function isGenerator( obj ) {
    return 'function' === typeof obj.next && 'function' === typeof obj.throw;
}

export function isGeneratorFunction( obj ) {
    if( !obj.constructor ) {
        return false;

    } else if( 'GeneratorFunction' === obj.constructor.name ||
               'GeneratorFunction' === obj.constructor.displayName ) {
        return true;

    } else {
        return isGenerator( obj.constructor.prototype );
    }
}

class YieldException extends TypeError {
}

function objectToPromise( obj ) {
    var results = new obj.constructor();
    var promises = [];

    for( let key of Object.keys( obj ) ) {
        let promise = toPromise.call( this, obj[key] );

        if( promise && isThenable( promise ) ) {
            results[key] = void 0;

            promises.push( promise.then( function( res ) {
                results[key] = res;
            } ) );

        } else {
            results[key] = obj[key];
        }
    }

    return Promise.all( promises ).return( results );
}

function resolveGenerator( gen ) {
    return new Promise( ( resolve, reject ) => {

        //Just in case
        if( typeof gen === 'function' ) {
            gen = gen();
        }

        if( !gen || !isGenerator( gen ) ) {
            return Promise.resolve( gen );

        } else {
            let next = ret => {
                if( ret.done ) {
                    return resolve( ret.value );

                } else {
                    var value = toPromise.call( this, ret.value );

                    if( isThenable( value ) ) {
                        return value.then( onFulfilled ).catch( onRejected );

                    } else {
                        let err = new TypeError( `You may only yield a function, promise, generator, array, or object, but the following object was passed: "${ret.value}"` );

                        return onRejected( err );
                    }
                }
            };

            function onFulfilled( res ) {
                try {
                    next( gen.next( res ) );

                } catch( e ) {
                    return reject( e );
                }
            }

            function onRejected( err ) {
                try {
                    next( gen.throw( err ) );

                } catch( e ) {
                    return reject( e );
                }
            }

            onFulfilled();
        }
    } );
}

function toPromise( value ) {
    try {
        return toPromiseTop( value );

    } catch( err ) {
        if( err instanceof YieldException ) {
            return Promise.resolve( value );

        } else {
            return Promise.reject( err );
        }
    }
}

function toPromiseTop( value ) {
    if( isThenable( value ) ) {
        return value;

    } else if( Array.isArray( value ) ) {
        return Promise.all( value.map( toPromise, this ) );

    } else if( typeof value === 'object' && value !== null ) {
        if( isGenerator( value ) ) {
            return resolveGenerator.call( this, value );

        } else {
            return objectToPromise.call( this, value );
        }

    } else if( typeof value === 'function' ) {
        if( isGeneratorFunction( value ) ) {
            return Promise.coroutine( value )();

        } else {
            //Thunks
            return new Promise( ( resolve, reject ) => {
                try {
                    value.call( this, ( err, res ) => {
                        if( err ) {
                            reject( err );

                        } else {
                            resolve( res );
                        }
                    } );

                } catch( err ) {
                    reject( err );
                }
            } );
        }

    } else if( yieldHandlers.length > 0 ) {
        for( let handler of yieldHandlers ) {
            let res = handler.call( this, value );

            if( isThenable( res ) ) {
                return res;
            }
        }

    } else {
        throw new YieldException( `You may only yield a function, promise, generator, array, or object, but the following object was passed: "${value}"` );
    }
}

export function addYieldHandler( handler ) {
    assert.strictEqual( typeof handler, 'function', 'handler must be a function' );

    yieldHandlers.push( handler );
}

let addedYieldHandler = false;

if( !addedYieldHandler ) {
    Promise.coroutine.addYieldHandler( function( value ) {
        try {
            return toPromiseTop.call( this, value );

        } catch( err ) {
            if( err instanceof YieldException ) {
                return void 0;

            } else {
                return Promise.reject( err );
            }
        }
    } );

    addedYieldHandler = true;
}

export default {
    addYieldHandler,
    isThenable,
    isGenerator,
    isGeneratorFunction
};

@@ -2730,281 +2730,16 @@
 alue
- ) %7B%0A    try %7B%0A        return toPromiseTop( value );%0A%0A    %7D catch( err ) %7B%0A        if( err instanceof YieldException ) %7B%0A            return Promise.resolve( value );%0A%0A        %7D else %7B%0A            return Promise.reject( err );%0A        %7D%0A    %7D%0A%7D%0A%0Afunction toPromiseTop( value
+, strict
  ) %7B
@@ -2879,23 +2879,74 @@
 ap( 
-toPromise, this
+val =%3E %7B%0A            return toPromise.call( this, val );%0A        %7D
  ) )
@@ -4063,32 +4063,45 @@
    %7D%0A%0A    %7D else
+ if( strict )
  %7B%0A        throw
@@ -4242,16 +4242,71 @@
 ue%7D%22%60 );
+%0A%0A    %7D else %7B%0A        return Promise.resolve( value );
 %0A    %7D%0A%7D
@@ -4639,11 +4639,8 @@
 mise
-Top
 .cal
@@ -4645,32 +4645,38 @@
 all( this, value
+, true
  );%0A%0A        %7D c
@@ -4922,16 +4922,27 @@
 fault %7B%0A
+           
     addY
@@ -4954,16 +4954,27 @@
 andler,%0A
+           
     isTh
@@ -4991,19 +4991,68 @@
   is
-Generator,%0A
+Promise: isThenable,%0A               isGenerator,%0A           
     

Simplify strict evaluation on the top level, and alias isThenable as isPromise