import { ArrayRepeatStrategy } from './array-repeat-strategy';
import { MapRepeatStrategy } from './map-repeat-strategy';
import { NullRepeatStrategy } from './null-repeat-strategy';
import { NumberRepeatStrategy } from './number-repeat-strategy';
import { Repeat } from './repeat';
import { SetRepeatStrategy } from './set-repeat-strategy';

/**
 * A strategy is for repeating a template over an iterable or iterable-like object.
 */
export interface RepeatStrategy {
  instanceChanged(repeat: Repeat, items: any): void;
  instanceMutated(repeat: Repeat, items: any, changes: any): void;
  getCollectionObserver(observerLocator: any, items: any): any;
}

/**
 * Locates the best strategy to best repeating a template over different types of collections.
 * Custom strategies can be plugged in as well.
 */
export class RepeatStrategyLocator {

  /**@internal*/
  matchers: any[];
  /**@internal*/
  strategies: any[];

  /**
   * Creates a new RepeatStrategyLocator.
   */
  constructor() {
    this.matchers = [];
    this.strategies = [];

    this.addStrategy(items => items === null || items === undefined, new NullRepeatStrategy() as RepeatStrategy);
    this.addStrategy(items => items instanceof Array, new ArrayRepeatStrategy());
    this.addStrategy(items => items instanceof Map, new MapRepeatStrategy());
    this.addStrategy(items => items instanceof Set, new SetRepeatStrategy());
    this.addStrategy(items => typeof items === 'number', new NumberRepeatStrategy() as Partial<RepeatStrategy> as RepeatStrategy);
  }

  /**
   * Adds a repeat strategy to be located when repeating a template over different collection types.
   * @param strategy A repeat strategy that can iterate a specific collection type.
   */
  addStrategy(matcher: (items: any) => boolean, strategy: RepeatStrategy) {
    this.matchers.push(matcher);
    this.strategies.push(strategy);
  }

  /**
   * Gets the best strategy to handle iteration.
   */
  getStrategy(items: any): RepeatStrategy {
    let matchers = this.matchers;

    for (let i = 0, ii = matchers.length; i < ii; ++i) {
      if (matchers[i](items)) {
        return this.strategies[i];
      }
    }

    return null;
  }
}
