'use strict';

var legendary = require('./legendary');
var nextTurn = require('./trampoline').nextTurn;
var promises = require('./promises');
var ResolutionPropagator = require('./ResolutionPropagator');
var CancellationError = require('./CancellationError');

function adoptState(value, asFulfilled, asRejected) {
  var cancelAdoption;

  if (!value || typeof value !== 'object' && typeof value !== 'function') {
    asFulfilled(value);
  } else if (promises.Promise.isInstance(value)) {
    var state = value.inspectState();
    if (state.isFulfilled) {
      asFulfilled(state.value);
    } else if (state.isRejected) {
      asRejected(state.reason);
    } else {
      cancelAdoption = value.then(function(value) {
        adoptState(value, asFulfilled, asRejected);
      }, asRejected).cancel;
    }
  } else {
    var called = false;
    try {
      var then = value.then;
      if (typeof then !== 'function') {
        asFulfilled(value);
      } else {
        then.call(value, function(value) {
          if (!called) {
            called = true;
            adoptState(value, asFulfilled, asRejected);
          }
        }, function(reason) {
          if (!called) {
            called = true;
            asRejected(reason);
          }
        });

        cancelAdoption = function() {
          if (!called) {
            called = true;
            asRejected(new CancellationError());
          }
        };
      }
    } catch (exception) {
      if (!called) {
        called = true;
        asRejected(exception);
      }
    }
  }

  return cancelAdoption;
}

function be(promise, resolver, cancellable, propagationConstructor) {
  if (!propagationConstructor) {
    propagationConstructor = promise.constructor;
  }
  var shortcutEmptyThen = propagationConstructor === promise.constructor;
  var pending = [];
  var resolved = false, fulfilled = false;
  var result, signalHandled, cancelAdoption, onCancelled;

  var cancel = promise.cancel;

  function markFulfilled(value) {
    fulfilled = true;
    result = value;
    cancelAdoption = null;
    var propagators = pending;
    pending = null;
    for (var i = 0, l = propagators.length; i < l; i++) {
      nextTurn(propagators[i], false, value);
    }
  }

  function markRejected(reason) {
    result = reason;
    signalHandled = legendary.unhandledRejection(reason);
    cancelAdoption = null;
    var propagators = pending;
    pending = null;
    for (var i = 0, l = propagators.length; i < l; i++) {
      nextTurn(propagators[i], true, reason, signalHandled);
    }
  }

  function resolve(value) {
    if (!resolved) {
      resolved = true;
      onCancelled = null;
      cancelAdoption = adoptState(value, markFulfilled, markRejected);
    }
  }

  function reject(reason) {
    if (!resolved) {
      resolved = true;
      onCancelled = null;
      markRejected(reason);
    }
  }

  function then(onFulfilled, onRejected) {
    if (
      shortcutEmptyThen &&
      typeof onFulfilled !== 'function' && typeof onRejected !== 'function'
    ) {
      return promise;
    }

    var propagator = new ResolutionPropagator(
        propagationConstructor, [onFulfilled, onRejected],
        cancellable, resolved ? null : cancel);
    if (pending) {
      pending.push(propagator);
    } else {
      nextTurn(propagator, !fulfilled, result, signalHandled);
    }
    return propagator.promise();
  }

  function inspectState() {
    if (pending) {
      return {
        isFulfilled: false,
        isRejected: false
      };
    } else if (fulfilled) {
      return {
        isFulfilled: true,
        isRejected: false,
        value: result
      };
    } else {
      return {
        isFulfilled: false,
        isRejected: true,
        reason: result
      };
    }
  }

  if (cancellable) {
    cancel = promise.cancel = function() {
      if (!resolved) {
        if (typeof onCancelled === 'function') {
          try {
            onCancelled();
          } catch (exception) {
            reject(exception);
          }
        }
        reject(new CancellationError());
      } else if (pending) {
        cancelAdoption();
      }
    };
  }

  promise.then = then;
  promise.inspectState = inspectState;

  try {
    onCancelled = resolver(resolve, reject);
  } catch (exception) {
    reject(exception);
  }
}

exports.be = be;


function extended(constructor, Base) {
  if (!Base) {
    Base = promises.Promise;
  }
  constructor.prototype = new Base(be);
  constructor.prototype.constructor = constructor;
  constructor.prototype.then = function() {
    return new constructor(function() {});
  };

  constructor.isInstance = function(x) { return x instanceof constructor; };
  constructor.from = promises.Promise.from;
  constructor.rejected = promises.Promise.rejected;
  constructor.all = promises.Promise.all;
  constructor.any = promises.Promise.any;
  constructor.some = promises.Promise.some;
  constructor.join = promises.Promise.join;

  return constructor;
}

exports.extended = extended;

@@ -4350,17 +4350,17 @@
 xtended(
-c
+C
 onstruct
@@ -4421,17 +4421,17 @@
 ;%0A  %7D%0A  
-c
+C
 onstruct
@@ -4457,25 +4457,25 @@
 Base(be);%0A  
-c
+C
 onstructor.p
@@ -4497,17 +4497,17 @@
 uctor = 
-c
+C
 onstruct
@@ -4508,25 +4508,25 @@
 structor;%0A  
-c
+C
 onstructor.p
@@ -4569,17 +4569,17 @@
 urn new 
-c
+C
 onstruct
@@ -4605,17 +4605,17 @@
   %7D;%0A%0A  
-c
+C
 onstruct
@@ -4664,17 +4664,17 @@
 tanceof 
-c
+C
 onstruct
@@ -4678,25 +4678,25 @@
 uctor; %7D;%0A  
-c
+C
 onstructor.f
@@ -4722,25 +4722,25 @@
 ise.from;%0A  
-c
+C
 onstructor.r
@@ -4774,25 +4774,25 @@
 rejected;%0A  
-c
+C
 onstructor.a
@@ -4816,25 +4816,25 @@
 mise.all;%0A  
-c
+C
 onstructor.a
@@ -4858,25 +4858,25 @@
 mise.any;%0A  
-c
+C
 onstructor.s
@@ -4906,17 +4906,17 @@
 some;%0A  
-c
+C
 onstruct
@@ -4958,17 +4958,17 @@
  return 
-c
+C
 onstruct

Fix lint