import ObjectProxy from '@ember/object/proxy';
import ArrayProxy from '@ember/array/proxy';

import createListeners from 'consul-ui/utils/dom/create-listeners';

import EventTarget from 'consul-ui/utils/dom/event-target/rsvp';

import cacheFactory from 'consul-ui/utils/dom/event-source/cache';
import proxyFactory from 'consul-ui/utils/dom/event-source/proxy';
import firstResolverFactory from 'consul-ui/utils/dom/event-source/resolver';

import CallableEventSourceFactory from 'consul-ui/utils/dom/event-source/callable';
import OpenableEventSourceFactory from 'consul-ui/utils/dom/event-source/openable';
import BlockingEventSourceFactory from 'consul-ui/utils/dom/event-source/blocking';
import StorageEventSourceFactory from 'consul-ui/utils/dom/event-source/storage';

import EmberObject from '@ember/object';
import { task } from 'ember-concurrency';

import { env } from 'consul-ui/env';

let runner;
switch (env('CONSUL_UI_REALTIME_RUNNER')) {
  case 'ec':
    runner = function(target, configuration, isClosed) {
      return EmberObject.extend({
        task: task(function* run() {
          while (!isClosed(target)) {
            yield target.source.bind(target)(configuration);
          }
        }),
      })
        .create()
        .get('task')
        .perform();
    };
    break;
  case 'generator':
    runner = async function(target, configuration, isClosed) {
      const run = function*() {
        while (!isClosed(target)) {
          yield target.source.bind(target)(configuration);
        }
      };
      let step = run().next();
      let res;
      while (!step.done) {
        res = await step.value;
        step = run().next();
      }
      return res;
    };
    break;
  case 'async':
    runner = async function(target, configuration, isClosed) {
      const run = function() {
        return target.source.bind(target)(configuration);
      };
      let res;
      while (!isClosed(target)) {
        res = await run();
      }
      return res;
    };
    break;
  default:
  // use the default runner
}

// All The EventSource-i
export const CallableEventSource = CallableEventSourceFactory(EventTarget, Promise, runner);
export const OpenableEventSource = OpenableEventSourceFactory(CallableEventSource);
export const BlockingEventSource = BlockingEventSourceFactory(OpenableEventSource);
export const StorageEventSource = StorageEventSourceFactory(EventTarget, Promise);

// various utils
export const proxy = proxyFactory(ObjectProxy, ArrayProxy, createListeners);
export const resolve = firstResolverFactory(Promise);

export const source = function(source) {
  // create API needed for conventional promise blocked, loading, Routes
  // i.e. resolve/reject on first response
  return resolve(source, createListeners()).then(function(data) {
    // create API needed for conventional DD/computed and Controllers
    return proxy(source, data);
  });
};
export const cache = cacheFactory(source, BlockingEventSource, Promise);

const errorEvent = function(e) {
  return new ErrorEvent('error', {
    error: e,
    message: e.message,
  });
};
export const fromPromise = function(promise) {
  return new CallableEventSource(function(configuration) {
    const dispatch = this.dispatchEvent.bind(this);
    const close = () => {
      this.close();
    };
    return promise
      .then(function(result) {
        close();
        dispatch({ type: 'message', data: result });
      })
      .catch(function(e) {
        close();
        dispatch(errorEvent(e));
      });
  });
};
export const toPromise = function(target, cb, eventName = 'message', errorName = 'error') {
  return new Promise(function(resolve, reject) {
    // TODO: e.target.data
    const message = function(e) {
      resolve(e.data);
    };
    const error = function(e) {
      reject(e.error);
    };
    const remove = function() {
      if (typeof target.close === 'function') {
        target.close();
      }
      target.removeEventListener(eventName, message);
      target.removeEventListener(errorName, error);
    };
    target.addEventListener(eventName, message);
    target.addEventListener(errorName, error);
    cb(remove);
  });
};
export const once = function(cb, configuration, Source = OpenableEventSource) {
  return new Source(function(configuration, source) {
    return cb(configuration, source)
      .then(function(data) {
        source.dispatchEvent({ type: 'message', data: data });
        source.close();
      })
      .catch(function(e) {
        source.dispatchEvent({ type: 'error', error: e });
        source.close();
      });
  }, configuration);
};