import Component from 'ember-component';
import Controller from 'ember-controller';
import Service from 'ember-service';
import Route from 'ember-route';
import Mixin from 'ember-metal/mixin';

function functionCallsSuper(func) {
  return func.toString().indexOf('this._super') > -1;
}

function checkMixinForSuper(mixin, methodNameToCheck) {
  if (mixin.properties) {
    let func = mixin.properties[methodNameToCheck];

    if (func && !functionCallsSuper(func)) {
      return false;
    }
  }

  if (mixin.mixins) {
    for (let i = 0; i < mixin.mixins.length; i++) {
      let childMixin = mixin.mixins[i];

      if (!checkMixinForSuper(childMixin, methodNameToCheck)) {
        return false;
      }
    }
  }

  return true;
}

function ensureCallsSuper(klass, ...methodNames) {
  klass.reopenClass({
    extend(...mixins) {
      for (let k = 0; k < methodNames.length; k++) {
        let methodName = methodNames[k];

        for (let i = 0; i < mixins.length; i++) {
          let object = mixins[i];

          if (object instanceof Mixin) {
            let superCalled = checkMixinForSuper(object, methodName);

            if (superCalled) {
              continue;
            }
          } else {
            // liquid-fire does not properly call `_super` in
            // the TransitionMap services `init`, this check excludes
            // liquid-fire from this assertion/error until
            // https://github.com/ember-animation/liquid-fire/pull/459 lands
            if (object.transitionFor) {
              continue;
            }
            let method = object[methodName];
            if (!method) {
              continue;
            }

            if (functionCallsSuper(method)) {
              continue;
            }
          }

          throw new Error(`Must call \`this._super(...arguments)\` when overriding \`${methodName}\`.`);
        }
      }

      return this._super(...arguments);
    }
  });
}

ensureCallsSuper(Component, 'init', 'willDestroy');
ensureCallsSuper(Controller, 'init', 'willDestroy');
ensureCallsSuper(Route, 'init', 'willDestroy', 'beforeModel', 'afterModel');
ensureCallsSuper(Service, 'init', 'willDestroy');
