import EventEmitter from 'events';
import {getPresetClassByName, IPresetStatic} from '../presets';
import {kebabCase} from '../utils';

const staticPresetCache = new Map(/* 'ClassName': <preset> */);

function getInstanceFromCache(ImplClass, params) {
  let preset = staticPresetCache.get(ImplClass.name);
  if (preset === undefined) {
    // only create one instance for IPresetStatic
    ImplClass.checkParams(params);
    ImplClass.onInit(params);
    preset = new ImplClass(params);
    staticPresetCache.set(ImplClass.name, preset);
  }
  return preset;
}

function createPreset(name, params = {}) {
  const ImplClass = getPresetClassByName(name);
  let preset = null;
  if (IPresetStatic.isPrototypeOf(ImplClass)) {
    preset = getInstanceFromCache(ImplClass, params);
  } else {
    ImplClass.checkParams(params);
    ImplClass.onInit(params);
    preset = new ImplClass(params);
  }
  return preset;
}

export const PIPE_ENCODE = 1;
export const PIPE_DECODE = -1;

/**
 * abstraction of middleware
 */
export class Middleware extends EventEmitter {

  _impl = null;

  constructor(preset) {
    super();
    this.onPresetNext = this.onPresetNext.bind(this);
    this.onPresetBroadcast = this.onPresetBroadcast.bind(this);
    this.onPresetFail = this.onPresetFail.bind(this);
    this._impl = createPreset(preset.name, preset.params || {});
    this._impl.next = this.onPresetNext;
    this._impl.broadcast = this.onPresetBroadcast;
    this._impl.fail = this.onPresetFail;
  }

  get name() {
    return this._impl.getName() || kebabCase(this._impl.constructor.name).replace(/(.*)-preset/i, '$1');
  }

  getImplement() {
    return this._impl;
  }

  hasListener(event) {
    return this.listenerCount(event) > 0;
  }

  notify(action) {
    return this._impl.onNotified(action);
  }

  onPresetNext(direction, buffer) {
    this.emit(`next_${direction}`, buffer);
  }

  onPresetBroadcast(action) {
    this.emit('broadcast', this.name, action);
  }

  onPresetFail(message) {
    this.emit('fail', this.name, message);
  }

  onDestroy() {
    // prevent destroy on static preset
    if (!(this._impl instanceof IPresetStatic)) {
      this._impl.onDestroy();
    }
    this.removeAllListeners();
  }

  /**
   * call hook functions of implement in order
   * @param direction
   * @param buffer
   * @param direct
   * @param isUdp
   */
  write({direction, buffer, direct, isUdp}) {
    const type = (direction === PIPE_ENCODE ? 'Out' : 'In') + (isUdp ? 'Udp' : '');

    // prepare args
    const broadcast = this.onPresetBroadcast;
    const fail = this.onPresetFail;
    const next = (processed, isReverse = false) => {
      // oh my nice hack to deal with reverse pipeline if haven't been created
      const hasListener = this.emit(`next_${isReverse ? -direction : direction}`, processed);
      if (!hasListener) {
        direct(processed, isReverse);
      }
    };

    // clientXXX, serverXXX
    const nextLifeCycleHook = (buf/* , isReverse = false */) => {
      const args = {buffer: buf, next, broadcast, direct, fail};
      const ret = __IS_CLIENT__ ? this._impl[`client${type}`](args) : this._impl[`server${type}`](args);
      if (ret instanceof Buffer) {
        next(ret);
      }
    };

    // beforeXXX
    // NOTE: next(buf, isReverse) is not available in beforeXXX
    const args = {buffer, next: nextLifeCycleHook, broadcast, direct, fail};
    const ret = this._impl[`before${type}`](args);
    if (ret instanceof Buffer) {
      nextLifeCycleHook(ret);
    }
  }

}

/**
 * destroy cached presets when program exit()
 */
export function cleanup() {
  for (const preset of staticPresetCache.values()) {
    preset.onDestroy();
  }
}

@@ -207,46 +207,33 @@
 ion 
-getInstanceFromCache(ImplClass
+createPreset(name
 , params
 ) %7B%0A
@@ -232,145 +232,92 @@
 rams
+ = %7B%7D
 ) %7B%0A  
-let preset = staticPresetCache.get(ImplClass.name);%0A  if (preset === undefined) %7B%0A    // only
+const ImplClass = getPresetClassByName(name);%0A  const
  create
- one instance for IPresetStatic
+One = () =%3E %7B
 %0A   
@@ -378,32 +378,30 @@
 arams);%0A    
-p
 re
-set =
+turn
  new ImplCla
@@ -418,165 +418,175 @@
 ;%0A  
-  staticPresetCache.set(ImplClass.name, preset);%0A  %7D%0A  return preset;%0A%7D%0A%0Afunction createPreset(name, params = %7B%7D) %7B%0A  const ImplClass = getPresetClassByName(
+%7D;%0A  let preset = null;%0A  if (IPresetStatic.isPrototypeOf(ImplClass)) %7B%0A    // only create one instance for IPresetStatic%0A    preset = staticPresetCache.get(ImplClass.
 name
@@ -586,36 +586,38 @@
 ss.name);%0A  
-let 
+  if (
 preset =
  null;%0A  if 
@@ -608,96 +608,79 @@
 et =
- null;%0A  if (IPresetStatic.isPrototypeOf(ImplClass)) %7B%0A    preset = getInstanceFrom
+== undefined) %7B%0A      preset = createOne();%0A      staticPreset
 Cache
+.set
 (Imp
@@ -689,128 +689,64 @@
 lass
-, params);%0A  %7D else %7B%0A    ImplClass.checkParams(params);%0A    ImplClass.onInit(params);%0A    preset = new ImplClass(params
+.name, preset);%0A    %7D%0A  %7D else %7B%0A    preset = createOne(
 );%0A 

refactor createPreset()