import { Disposable, Event } from 'vscode';
import { combinedDisposable } from './disposableUtils';

/**
 * Wrap an event in an event that only fire when a certain condition is true
 * @param event The event that will be wrapped
 * @param filter A predicate function whhich will be run on the event result to determine if the event should be propagated.
 */
export function filterEvent<T> (event: Event<T>, filter: (e: T) => boolean): Event<T> {
  return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables);
}

/**
 * Returns an event that fires when any of the given events fire
 * @param events An array of events that will trigger the returned event
 */
export function anyEvent<T> (...events: Array<Event<T>>): Event<T> {
  return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => {
    const result = combinedDisposable(events.map(event => event(i => listener.call(thisArgs, i))));

    if (disposables) {
      disposables.push(result);
    }

    return result;
  };
}

/**
 * Create a new event that wraps an event and fires the first time the wrapped event
 * fires, then disposes of itself.
 * @param event The event to wrap.
 */
export function onceEvent<T> (event: Event<T>): Event<T> {
  return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => {
    const result = event(e => {
      result.dispose();
      return listener.call(thisArgs, e);
    }, null, disposables);

    return result;
  };
}

/**
 * Wraps an event in an event that prevents it from firing too frequently
 * @param event The event that will be wrapped
 * @param delay The minimum amount of time between event fires
 */
export function debounceEvent<T> (event: Event<T>, delay: number): Event<T> {
  return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => {
    let timer: NodeJS.Timer;
    return event(e => {
      clearTimeout(timer);
      timer = setTimeout(() => listener.call(thisArgs, e), delay);
    }, null, disposables);
  };
}

/**
 * Convert an event into an awaitable promise
 * @param event The event that can be awaited
 */
export async function eventToPromise<T> (event: Event<T>): Promise<T> {
  return await new Promise<T>(resolve => onceEvent(event)(resolve));
}