import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';

declare type SubscriptionArray = Subscription[];

export { Subscription } from 'rxjs/Subscription';

export interface Handler {
  (...args: any[]): void;
}

export class EventEmitter {
  private subjects: Map<string, Subject<any>>;
  private subscriptions: Map<string, Map<Handler, SubscriptionArray>>;

  constructor() {
    this.subjects = new Map<string, Subject<any>>();
    this.subscriptions = new Map<string, Map<Handler, SubscriptionArray>>();
  }

  public dispose() {
    this.subjects.forEach((subject: Subject<any>) => {
      subject.unsubscribe();
    });

    this.subjects.clear();
  }

  public addListener(name: string, handler: Handler) {
    return this.on(name, handler);
  }

  public emit(fnName: string, ...data: any[]) {
    let subject = this.getSubject(fnName);
    subject.next(data);
  }

  public listen(name: string, handler: Handler): Subscription {
     let subscription = this.getSubject(name).subscribe(handler);
     this.registerSubscription(name, handler, subscription);
     return subscription;
  }

  public off(name: string, handler: Handler) {
    let subs = this.getSubscriptions(name, handler);
    subs.forEach(sub => sub.unsubscribe());
  }

  public on(name: string, handler: Handler) {
    let subscription = this.getSubject(name).subscribe(val => {
      if (Array.isArray(val)) {
        handler(...val);
      } else {
        handler(val);
      }
    });
    this.registerSubscription(name, handler, subscription);
  }

  public once(name: string, handler: Handler) {
    let onceHandler = (value: any) => {
      this.off(name, onceHandler);
      handler(value);
    };

    return this.on(name, onceHandler);
  }

  public removeListener(name: string, handler: Handler) {
    return this.off(name, handler);
  }

  private registerSubscription(name: string, handler: Handler, sub: Subscription) {
    let handlerMap = this.subscriptions.get(name);
    if (!handlerMap) {
      handlerMap = new Map<Handler, SubscriptionArray>();
      this.subscriptions.set(name, handlerMap);
    }

    let subArr = handlerMap.get(handler);
    if (!subArr) {
      subArr = [];
      handlerMap.set(handler, subArr);
    }

    subArr.push(sub);
  }

  private getSubscriptions(name: string, handler: Handler): SubscriptionArray {
    let handlerMap = this.subscriptions.get(name);
    if (!handlerMap) {
      return [];
    }

    return handlerMap.get(handler) || [];
  }

  private getSubject(fnName: string): Subject<any> {
    let subject = this.subjects.get(fnName);
    if (!subject) {
      subject = new Subject<any>();
      this.subjects.set(fnName, subject);
    }

    return subject;
  }
 }
