File

projects/wms-framework/src/lib/baseframework/collections.ts

Description

INotifyCollectionChanged interface

Index

Properties

Properties

CollectionChanged
CollectionChanged: SubscriptionEvent<void>
Type : SubscriptionEvent<void>
import { INotifyPropertyChanged } from '../basecomponentmodel/INotifyPropertyChanged';
import { supportedInterfacesCompatibilityMetadataKey } from '../decorators/ClassInfo';
import { Debugger } from '../diagnostics/Debugger';
import { CancelEventArgs } from '../helpers/CancelEventArgs';
import { SubscriptionEvent } from '../utils/SubscriptionEvent';
import { InvalidOperationException } from './Exceptions';
import { SortDescription } from './SortDescription';

/**
 * IEqualityComparer interface
 *
 * @export
 * @interface IEqualityComparer
 * @template T
 * @wInterface System.Collections.Generic.IEqualityComparer`1
 * @wNetSupport
 */
export interface IEqualityComparer<T> {
  Equals(x: T, y: T): boolean;
  GetHashCode(x: T): number;
}

/**
 * IComparer interface
 *
 * @export
 * @interface IComparer
 * @template T
 * @wInterface System.Collections.Generic.IComparer`1
 * @wNetSupport
 */
export interface IComparer<T> {
  Compare(x: T, y: T): number;
}

/**
 * IComparable interface
 *
 * @export
 * @interface IComparable
 * @template T
 * @wInterface System.IComparable`1
 * @wNetSupport
 */
export interface IComparable<T> {
  CompareTo(x: T): number;
}

/**
 * IEquatable interface
 *
 * @export
 * @interface IEquatable
 * @template T
 * @wInterface System.IEquatable`1
 * @wNetSupport
 */
export interface IEquatable<T> {
  Equals(other: T): boolean;
}

export interface ISupportCollection<T> {
  internalArray: Array<T>;
}

/**
 * IOrderedIterable interface
 *
 * @export
 * @interface IOrderedIterable
 * @extends {Iterable<V>}
 * @template V
 * @wInterface System.Linq.IOrderedEnumerable`1
 * @wNetSupport
 */
export interface IOrderedIterable<V> extends Iterable<V> {
  /**
   * Creates ordered iterable
   *
   * @template K
   * @param {(e: V) => K} keySelector
   * @param {IComparer<K>} comparer
   * @param {boolean} descending
   * @returns {IOrderedIterable<V>}
   * @memberof IOrderedIterable
   * @wMethod CreateOrderedEnumerable
   */
  CreateOrderedIterable<K>(
    keySelector: (e: V) => K,
    comparer: IComparer<K>,
    descending: boolean
  ): IOrderedIterable<V>;
}

export interface UnTypedIterable {}

export interface IUntypedCollection {}

/**
 * ICollection interface
 *
 * @export
 * @interface ICollection
 * @extends {Iterable<T>}
 * @extends {ISupportCollection<T>}
 * @template T
 * @wInterface System.Collections.Generic.ICollection`1
 * @wNetSupport
 */
export interface ICollection<T> extends Iterable<T>, ISupportCollection<T> {
  /**
   * The number of elemenst in the collection
   *
   * @type {number}
   * @memberof ICollection
   * @wProperty Count
   */
  count: number;

  /**
   * Adds a value to the collection
   *
   * @param {T} value
   * @memberof ICollection
   * @wMethod Add
   */
  add(value: T): void;

  /**
   * Clears the collection
   *
   * @memberof ICollection
   * @wMethod Clear
   */
  clear(): void;

  /**
   * Check if a element is contained in the collection
   *
   * @param {T} value
   * @returns {boolean}
   * @memberof ICollection
   * @wMethod Contains
   */
  contains(value: T): boolean;

  /**
   * Removes the element from the collection
   *
   * @param {T} value
   * @returns {boolean}
   * @memberof ICollection
   */
  remove(value: T): boolean;

  copyTo(target: Array<T>, index: number): void;
}

/**
 * IPagedCollectionView interface
 *
 * @export
 * @interface IPagedCollectionView
 * @wInterface System.ComponentModel.IPagedCollectionView
 */
export interface IPagedCollectionView {
  CanChangePage: boolean;
  IsPageChanging: boolean;
  ItemCount: number;
  PageIndex: number;
  PageSize: number;
  TotalItemCount: number;
  PageChanged: SubscriptionEvent<(e: any, args: CollectionChangeInfo) => void>;
  PageChanging: SubscriptionEvent<
    (e: any, args: PageChangingEventArgs) => void
  >;
  MoveToFirstPage(): boolean;
  MoveToLastPage(): boolean;
  MoveToNextPage(): boolean;
  MoveToPage(pageIndex: number): boolean;
  MoveToPreviousPage(): boolean;
}

/**
 * IList interface
 *
 * @export
 * @interface IList
 * @extends {ICollection<T>}
 * @template T
 * @wInterface System.Collections.Generic.IList`1
 * @wNetSupport
 */
export interface IList<T> extends ICollection<T> {
  getItem(index: number): T;
  setItem(index: number, value: T);

  /**
   * Gets the index of an element in the list
   *
   * @param {T} value
   * @returns {number}
   * @memberof IList
   * @wMethod IndexOf
   */
  indexOf(value: T): number;

  /**
   * Insert element in position
   *
   * @param {number} index
   * @param {T} value
   * @memberof IList
   * @wMethod Insert
   */
  insert(index: number, value: T);

  /**
   * Removes element at index
   *
   * @param {number} index
   * @memberof IList
   * @wMethod RemoveAt
   */
  removeAt(index: number);

  /**
   * Sorts the list
   *
   * @memberof IList
   * @wMethod Sort
   */
  sort();

  /**
   * Sorts the list by comparer
   *
   * @param {IComparer<T>} comparer
   * @memberof IList
   * @wMethod Sort
   */
  sort(comparer: IComparer<T>);
}

/**
 * SimpleList Class
 *
 * @export
 * @class SimpleList
 * @implements {IList}
 * @wType System.Collections.Generic.List`1
 * @wNetSupport
 */
export class SimpleList<T> implements IList<T> {
  protected static interfacesInitialized = false;

  protected _internalArray: T[] = [];

  protected static initializeSupportedInterfaces(): void {
    if (!SimpleList.interfacesInitialized) {
      const setMetadataInfo = Reflect.metadata(
        supportedInterfacesCompatibilityMetadataKey,
        [
          'System.Collections.Generic.ICollection`1',
          'System.Collections.Generic.IList`1',
          'System.Collections.Generic.IList',
          'System.Collections.IList',
        ]
      );
      setMetadataInfo(SimpleList);
      SimpleList.interfacesInitialized = true;
    }
  }

  constructor();
  constructor(initialValues: Iterable<T>);
  constructor(capacity: number);
  constructor(arg?: unknown) {
    const newLocal = typeof arg;
    // capacity argument still not supported
    if (newLocal != 'undefined' && newLocal != 'number') {
      const initialValues = arg as Iterable<T>;
      this.internalArray = [...initialValues];
    }
    SimpleList.initializeSupportedInterfaces();
  }

  /**
   * Gets or sets the internal array.
   *
   * @type {T[]}
   * @memberof SimpleList
   */
  get internalArray(): T[] {
    // eslint-disable-next-line no-underscore-dangle
    return this._internalArray;
  }

  set internalArray(value: T[]) {
    // eslint-disable-next-line no-underscore-dangle
    this._internalArray = value;
  }

  getItem(index: number): T {
    if (index >= this.internalArray.length || index < 0) {
      throw new Error('Invalid index');
    }
    return this.internalArray[index];
  }
  setItem(index: number, value: T) {
    if (index >= this.internalArray.length || index < 0) {
      throw new Error('Invalid index');
    }
    this.internalArray[index] = value;
  }
  /**
   * Index of value in list
   *
   * @param {T} value
   * @returns
   * @memberof SimpleList
   * @wMethod IndexOf
   */
  indexOf(value: T) {
    return this.internalArray.indexOf(value);
  }

  /**
   * Inserts element in index
   *
   * @param {number} index
   * @param {T} value
   * @memberof SimpleList
   * @wMethod Insert
   */
  insert(index: number, value: T) {
    this.internalArray.splice(index, 0, value);
  }

  /**
   * Removes element at index
   *
   * @param {number} index
   * @memberof SimpleList
   * @wMethod RemoveAt
   */
  removeAt(index: number) {
    this.internalArray.splice(index, 1);
  }

  /**
   * Gets the count of elements in list
   *
   * @readonly
   * @type {number}
   * @memberof SimpleList
   * @wProperty Count
   */
  get count(): number {
    return this.internalArray.length;
  }

  /**
   * Adds an element to the list
   *
   * @param {T} value
   * @memberof SimpleList
   * @wMethod Add
   */
  add(value: T): void {
    this.internalArray.push(value);
  }
  /**
   * Clears the list
   *
   * @memberof SimpleList
   * @wMethod Clear
   */
  clear(): void {
    this.internalArray.splice(0, this.internalArray.length);
  }

  /**
   * Check if a value is contained in the list
   *
   * @param {T} value
   * @returns {boolean}
   * @memberof SimpleList
   * @wMethod Contains
   */
  contains(value: T): boolean {
    return this.internalArray.indexOf(value) !== -1;
  }

  /**
   * Removes a value from the list
   *
   * @param {T} value
   * @returns {boolean}
   * @memberof SimpleList
   * @wMethod Remove
   */
  remove(value: T): boolean {
    const idx = this.internalArray.indexOf(value);
    if (idx !== -1) {
      this.internalArray.splice(idx, 1);
      return true;
    } else {
      return false;
    }
  }

  /**
   * Performs an action on each element of the list
   *
   * @param {(e: T) => void} action
   * @memberof SimpleList
   * @wMethod ForEach
   */
  forEach(action: (e: T) => void): void {
    for (const obj of this.internalArray) {
      action(obj);
    }
  }

  /**
   * Adds elements of an iterable to the list
   *
   * @param {Iterable<T>} range
   * @memberof SimpleList
   * @wMethod AddRange
   */
  addRange(range: Iterable<T>): void {
    for (const obj of range) {
      this.add(obj);
    }
  }

  /**
   * Inserts elements of an interable to the list in a position
   *
   * @param {number} position
   * @param {Iterable<T>} range
   * @memberof SimpleList
   * @wMethod InserRange
   */
  insertRange(position: number, range: Iterable<T>): void {
    if (!(position < this.internalArray.length && position >= 0)) {
      throw new Error('Invalid position');
    }
    this.internalArray.splice(position, 0, ...range);
  }

  /**
   * Sorts the list
   *
   * @memberof SimpleList
   * @wMethod Sort
   */
  sort();
  sort(comparer: IComparer<T>);
  sort(comparison: (x: T, y: T) => number);
  sort(p?: any) {
    if (typeof p === 'object') {
      this.internalArray = this.internalArray.sort(p.Compare);
    } else if (typeof p === 'function') {
      this.internalArray = this.internalArray.sort(p);
    } else {
      if (
        this.internalArray.length > 0 &&
        typeof this.internalArray[0] === 'object'
      ) {
        this.internalArray.sort((a: any, b: any) => {
          if (a?.CompareTo && b?.CompareTo) {
            return a.CompareTo(b);
          } else {
            return 0;
          }
        });
      } else {
        this.internalArray = this.internalArray.sort();
      }
    }
  }

  /**
   * Copies the list to a target array
   *
   * @param {T[]} target
   * @param {number} index
   * @memberof SimpleList
   * @wMethod CopyTo
   */
  copyTo(target: T[], index: number): void {
    for (let i = index; i < target.length; i++) {
      target[i] = this.internalArray[i];
    }
  }

  /**
   * Removes all elements from the collection by a predicate
   *
   * @param {(e: T) => boolean} predicate
   * @returns
   * @memberof SimpleList
   * @wMethod RemoveAll
   */
  public removeAll(predicate: (e: T) => boolean) {
    let removed = 0;
    for (let i = this.internalArray.length - 1; i >= 0; i--) {
      if (predicate(this.internalArray[i])) {
        this.removeAt(i);
        removed++;
      }
    }
    return removed;
  }

  /**
   * Converts the list to an array
   *
   * @returns
   * @memberof SimpleList
   * @wMethod ToArray
   */
  public toArray() {
    return [...this.internalArray];
  }

  [Symbol.iterator](): Iterator<T, any, undefined> {
    return this.internalArray[Symbol.iterator]();
  }
}

/**
 * A collection of sort descriptions
 *
 * @export
 * @class SortDescriptionCollection
 * @extends {SimpleList<SortDescription>}
 * @wType System.ComponentModel.SortDescriptionCollection
 */
export class SortDescriptionCollection
  extends SimpleList<SortDescription>
  implements INotifyCollectionChanged
{
  CollectionChanged: SubscriptionEvent<
    (e: any, args: CollectionChangeInfo) => void
  > = new SubscriptionEvent();

  /**
   * Raises the CollectionChanged event.
   *
   * @param {CollectionChangeInfo} info
   * @memberof SortDescriptionCollection
   */
  onCollectionChanged(info: CollectionChangeInfo): void {
    this.CollectionChanged.fire([this, info]);
  }

  /**
   * Inserts a new element at the specified position.
   *
   * @param {number} index
   * @param {T} value
   * @memberof SortDescriptionCollection
   */
  insert(index: number, value: SortDescription): void {
    super.insert(index, value);
    const info = new CollectionChangeInfo(CollectionChangeAction.Add);
    info.NewItems = new SimpleList([value]);
    info.NewStartingIndex = index;
    this.onCollectionChanged(info);
  }

  /**
   * Removes the first element from the collection which matches the given value.
   *
   * @param {T} value
   * @return {*}  {boolean}
   * @memberof SortDescriptionCollection
   */
  remove(value: SortDescription): boolean {
    const idx = this.internalArray.indexOf(value);
    if (idx === -1) {
      return false;
    }
    const result = super.remove(value);
    const info = new CollectionChangeInfo(CollectionChangeAction.Remove);
    info.OldItems = new SimpleList([value]);
    info.OldStartingIndex = idx;
    this.onCollectionChanged(info);
    return result;
  }

  /**
   * Removes the element at the given position.
   *
   * @param {number} index
   * @memberof SortDescriptionCollection
   */
  removeAt(index: number): void {
    const oldItem = super.getItem(index);
    super.removeAt(index);
    const info = new CollectionChangeInfo(CollectionChangeAction.Remove);
    info.OldItems = new SimpleList([oldItem]);
    info.OldStartingIndex = index;
    this.onCollectionChanged(info);
  }

  /**
   * Adds the given element at the end of the collection.
   *
   * @param {T} value
   * @memberof SortDescriptionCollection
   */
  add(value: SortDescription): void {
    super.add(value);
    const info = new CollectionChangeInfo(CollectionChangeAction.Add);
    info.NewItems = new SimpleList([value]);
    info.NewStartingIndex = this.count - 1;
    this.onCollectionChanged(info);
  }

  /**
   * Removes all elements from the collection.
   *
   * @memberof SortDescriptionCollection
   */
  clear(): void {
    super.clear();
    this.onCollectionChanged(
      new CollectionChangeInfo(CollectionChangeAction.Reset)
    );
  }

  /**
   * Replace the element at the given position with the given element.
   *
   * @param {number} index
   * @param {T} value
   * @memberof SortDescriptionCollection
   * @wMethod SetItem
   */
  setItem(index: number, value: SortDescription): void {
    const oldItem = super.getItem(index);
    super.setItem(index, value);
    const info = new CollectionChangeInfo(CollectionChangeAction.Replace);
    info.OldItems = new SimpleList([oldItem]);
    info.NewItems = new SimpleList([value]);
    info.NewStartingIndex = index;
    this.onCollectionChanged(info);
  }
}

/**
 * Reports changes in a collection.
 *
 * @export
 * @class CollectionChangeInfo
 * @wType System.Collections.Specialized.NotifyCollectionChangedEventArgs
 */
export class CollectionChangeInfo {
  /**
   * The items added or changed in the collection
   *
   * @type {IList<any>}
   * @memberof CollectionChangeInfo
   */
  public NewItems: IList<any> = null;

  /**
   *  The index where the change occurred.
   *
   * @type {number}
   * @memberof CollectionChangeInfo
   */
  public NewStartingIndex = -1;

  /**
   * The items that where removed from the collection.
   *
   * @type {IList<any>}
   * @memberof CollectionChangeInfo
   */
  public OldItems: IList<any> = null;

  /**
   * The index where a replace action or remove occurred.
   *
   * @type {number}
   * @memberof CollectionChangeInfo
   */
  public OldStartingIndex = -1;

  /**
   * Creates an instance of CollectionChangeInfo.
   *
   * @param {CollectionChangeAction} action
   * @memberof CollectionChangeInfo
   */
  constructor(public action: CollectionChangeAction) {}
}

/**
 * CollectionChangeAction enum
 *
 * @export
 * @enum {number}
 * @wEnum System.Collections.Specialized.NotifyCollectionChangedAction
 */
export enum CollectionChangeAction {
  Add,
  Remove,
  Replace,
  Reset,
}

/**
 * INotifyCollectionChanged interface
 *
 * @export
 * @interface INotifyCollectionChanged
 * @wInterface System.Collections.Specialized.INotifyCollectionChanged
 */
export interface INotifyCollectionChanged {
  CollectionChanged: SubscriptionEvent<
    (e: any, args: CollectionChangeInfo) => void
  >;
}

/**
 * ObjectModelCollection class
 *
 * @export
 * @class ObjectModelCollection
 * @extends {SimpleList<T>}
 * @template T
 * @wType System.Collections.ObjectModel.Collection`1
 */
export class ObjectModelCollection<T> extends SimpleList<T> {}

/**
 * ItemDataRequestedEventArgs class
 *
 * @export
 * @class ItemDataRequestedEventArgs
 * @wType Infragistics.Collections.ItemDataRequestedEventArgs
 */
export class ItemDataRequestedEventArgs {
  /**
   * FilterConditions property
   *
   * @type {*}
   * @memberof ItemDataRequestedEventArgs
   */
  FilterConditions: any = null;

  /**
   * ItemsCount property
   *
   * @type {number}
   * @memberof ItemDataRequestedEventArgs
   */
  ItemsCount: number = null;

  /**
   * SortDescriptions Property
   *
   * @type {*}
   * @memberof ItemDataRequestedEventArgs
   */
  SortDescriptions: any = null;

  /**
   * StartIndex property.
   *
   * @type {number}
   * @memberof ItemDataRequestedEventArgs
   */
  StartIndex: number = null;
}

/**
 * PageChangingEventArgs class
 *
 * @export
 * @class PageChangingEventArgs
 * @wType System.ComponentModel.PageChangingEventArgs
 */
export class PageChangingEventArgs extends CancelEventArgs {
  /**
   * New page index internal field
   *
   * @private
   * @type {number}
   * @memberof PageChangingEventArgs
   */
  private newPageIndex: number;

  /**
   *Creates an instance of PageChangingEventArgs.
   * @param {number} newPageIndex
   * @memberof PageChangingEventArgs
   */
  constructor(newPageIndex: number) {
    super();
    this.newPageIndex = newPageIndex;
  }

  /**
   * StartIndex property.
   *
   * @type {number}
   * @memberof PageChangingEventArgs
   */
  get NewPageIndex(): number {
    return this.newPageIndex;
  }
}

/**
 * Collection which reports changes to its observers.
 *
 * @export
 * @class ObservableCollection
 * @extends {ObjectModelCollection<T>}
 * @implements {INotifyPropertyChanged}
 * @template T
 * @wType System.Collections.ObjectModel.ObservableCollection`1
 */
export class ObservableCollection<T>
  extends ObjectModelCollection<T>
  implements INotifyCollectionChanged, INotifyPropertyChanged
{
  /**
   * Flag to indicate if supported interfaces have been initialized.
   *
   * @private
   * @static
   * @memberof ObservableCollection
   */
  protected static interfacesInitialized = false;

  /**
   * Event to indicate that a property have changed.
   *
   * @memberof ObservableCollection
   */
  public PropertyChanged: SubscriptionEvent<
    (e: any, args: { PropertyName: string }) => void
  > = new SubscriptionEvent();

  /**
   * Event to indicate that the collection have changed.
   *
   * @memberof ObservableCollection
   */
  public CollectionChanged: SubscriptionEvent<
    (e: any, args: CollectionChangeInfo) => void
  > = new SubscriptionEvent();

  /**
   * Event AddNewItem have changed.
   *
   * @memberof ObservableCollection
   * @wIgnore
   */
  public AddNewItem: SubscriptionEvent<
    (e: any, args: CollectionChangeInfo) => void
  > = new SubscriptionEvent();

  /**
   * Creates an instance of ObservableCollection.
   *
   * @param {Iterable<T>} [initial]
   * @memberof ObservableCollection
   */
  constructor(initial?: Iterable<T>) {
    super();
    if (typeof initial !== 'undefined' && initial !== null) {
      this.internalArray = [...initial];
    }
    ObservableCollection.initializeSupportedInterfaces();
  }

  /**
   * Initialize the interfaces supported by this collection.
   *
   * @private
   * @static
   * @memberof ObservableCollection
   */
  protected static initializeSupportedInterfaces(): void {
    if (!ObservableCollection.interfacesInitialized) {
      const setMetadataInfo = Reflect.metadata(
        supportedInterfacesCompatibilityMetadataKey,
        [
          'System.ComponentModel.INotifyPropertyChanged',
          'System.Collections.IEnumerable',
          'System.Collections.Generic.IList`1',
          'System.Collections.IList',
          'System.Collections.Specialized.INotifyCollectionChanged',
        ]
      );
      setMetadataInfo(ObservableCollection);
      ObservableCollection.interfacesInitialized = true;
    }
  }

  /**
   * Inserts a new element at the specified position.
   *
   * @param {number} index
   * @param {T} value
   * @memberof ObservableCollection
   */
  insert(index: number, value: T): void {
    super.insert(index, value);
    const changeEventInfo = new CollectionChangeInfo(
      CollectionChangeAction.Add
    );
    changeEventInfo.NewItems = new SimpleList([value]);
    changeEventInfo.NewStartingIndex = index;
    this.onCollectionChanged(changeEventInfo);
    this.onPropertyChanged({ PropertyName: 'Count' });
  }

  /**
   * Removes the first element from the collection which matches the given value.
   *
   * @param {T} value
   * @return {*}  {boolean}
   * @memberof ObservableCollection
   */
  remove(value: T): boolean {
    const idx = this.internalArray.indexOf(value);
    if (idx === -1) {
      return false;
    }
    const result = super.remove(value);
    const changeEventInfo = new CollectionChangeInfo(
      CollectionChangeAction.Remove
    );
    changeEventInfo.OldItems = new SimpleList([value]);
    changeEventInfo.OldStartingIndex = idx;
    this.onCollectionChanged(changeEventInfo);
    this.onPropertyChanged({ PropertyName: 'Count' });
    return result;
  }

  /**
   * Removes the element at the given position.
   *
   * @param {number} index
   * @memberof ObservableCollection
   */
  removeAt(index: number): void {
    const oldItem = super.getItem(index);
    super.removeAt(index);
    const changeEventInfo = new CollectionChangeInfo(
      CollectionChangeAction.Remove
    );
    changeEventInfo.OldItems = new SimpleList([oldItem]);
    changeEventInfo.OldStartingIndex = index;
    this.onCollectionChanged(changeEventInfo);
    this.onPropertyChanged({ PropertyName: 'Count' });
  }

  /**
   * Adds the given element at the end of the collection.
   *
   * @param {T} value
   * @memberof ObservableCollection
   */
  add(value: T): void {
    super.add(value);
    const changeEventInfo = new CollectionChangeInfo(
      CollectionChangeAction.Add
    );
    changeEventInfo.NewItems = new SimpleList([value]);
    changeEventInfo.NewStartingIndex = this.count - 1;
    this.onCollectionChanged(changeEventInfo);
    this.onPropertyChanged({ PropertyName: 'Count' });
  }

  /**
   * Adds elements of an iterable to the list
   *
   * @param {Iterable<T>} range
   * @memberof ObservableCollection
   * @wIgnore
   */
  silentAddRange(range: Iterable<T>) {
    for (const obj of range) {
      super.add(obj);
    }
    const changeEventInfo = new CollectionChangeInfo(
      CollectionChangeAction.Add
    );
    changeEventInfo.NewItems = new SimpleList([range]);
    changeEventInfo.NewStartingIndex = this.count - 1;
    this.onCollectionChanged(changeEventInfo);
    this.onPropertyChanged({ PropertyName: 'Count' });
  }

  /**
   * Removes all elements from the collection.
   *
   * @memberof ObservableCollection
   */
  clear(): void {
    super.clear();
    this.onCollectionChanged(
      new CollectionChangeInfo(CollectionChangeAction.Reset)
    );
    this.onPropertyChanged({ PropertyName: 'Count' });
  }

  /**
   * Replace the element at the given position with the given element.
   *
   * @param {number} index
   * @param {T} value
   * @memberof ObservableCollection
   * @wMethod SetItem
   */
  setItem(index: number, value: T): void {
    const oldItem = super.getItem(index);
    super.setItem(index, value);
    const changeEventInfo = new CollectionChangeInfo(
      CollectionChangeAction.Replace
    );
    changeEventInfo.OldItems = new SimpleList([oldItem]);
    changeEventInfo.NewItems = new SimpleList([value]);
    changeEventInfo.NewStartingIndex = index;
    this.onCollectionChanged(changeEventInfo);
  }

  /**
   * Removes all elements from the collection.
   *
   * @protected
   * @memberof ObservableCollection
   * @wMethod ClearItems
   */
  protected clearItems(): void {
    this.clear();
  }

  /**
   * Trigger a `CollectionChanged` event.
   *
   * @protected
   * @param {CollectionChangeInfo} info
   * @memberof ObservableCollection
   * @wMethod OnCollectionChanged
   */
  protected onCollectionChanged(info: CollectionChangeInfo): void {
    this.CollectionChanged.fire([this, info]);
  }

  /**
   * Trigger a `PropertyChanged` event.
   *
   * @protected
   * @param {{ PropertyName: string }} info
   * @memberof ObservableCollection
   * @wMethod OnPropertyChanged
   */
  protected onPropertyChanged(info: { PropertyName: string }): void {
    this.PropertyChanged.fire([this, info]);
  }
}

/**
 * SimpleQueue class
 *
 * @export
 * @class SimpleQueue
 * @implements {Iterable<T>}
 * @template T
 * @wType System.Collections.Generic.Queue`1
 */
export class SimpleQueue<T> implements Iterable<T> {
  public internalArray: T[] = [];

  constructor(initialData?: Iterable<T>) {
    if (typeof initialData !== 'undefined') {
      this.internalArray = [...initialData];
    }
  }

  /**
   * Count the elements in queue
   *
   * @readonly
   * @type {number}
   * @memberof SimpleQueue
   * @wProperty Count
   */
  get count(): number {
    return this.internalArray.length;
  }

  /**
   * Enqueue an element in the queue
   *
   * @param {T} value
   * @returns
   * @memberof SimpleQueue
   * @wMethod Enqueue
   */
  public enqueue(value: T) {
    return this.internalArray.push(value);
  }

  /**
   * Dequeue an element of the queue
   *
   * @returns {T}
   * @memberof SimpleQueue
   * @wMethod Dequeue
   */
  public dequeue(): T {
    return this.internalArray.shift();
  }

  [Symbol.iterator](): Iterator<T, any, undefined> {
    return this.internalArray[Symbol.iterator]();
  }
}

/**
 * ReadOnlyCollection class
 *
 * @export
 * @class ReadonlyCollection
 * @extends {SimpleList<T>}
 * @template T
 * @wType System.Collections.ObjectModel.ReadOnlyCollection`1
 */
export class ReadonlyCollection<T> extends SimpleList<T> {
  constructor(contents?: Iterable<T>) {
    super(contents);
  }

  // NOTE: this methods intentionally throw an exception, because
  //       it is not allowed to modify a read only collection.

  insert(index: number, value: T) {
    throw Error('Operation not supported');
  }

  remove(value: T): boolean {
    throw Error('Operation not supported');
  }

  removeAt(index: number) {
    throw Error('Operation not supported');
  }

  add(value: T): void {
    throw Error('Operation not supported');
  }

  clear(): void {
    throw Error('Operation not supported');
  }

  setItem(index: number, value: T) {
    throw Error('Operation not supported');
  }
}

//
// IterUtils
//

export function iuToArray<T>(iterable: Iterable<T>): Array<T> {
  return [...iterable];
}

class SelectIteratorWrapper<T, K> implements Iterator<K> {
  private index = 0;
  constructor(
    private innerIterator: Iterator<T>,
    private funcWithIndex?: (e: T, i?: number) => K
  ) {}
  next(): IteratorResult<K, any> {
    const nextValue = this.innerIterator.next();
    if (!nextValue.done) {
      return {
        value: this.funcWithIndex(nextValue.value, this.index++),
        done: false,
      };
    }
    return { value: undefined, done: true };
  }
}

export function iuSelect<T, K>(
  func: (e: T, i?: number) => K,
  iterable: Iterable<T>
): Iterable<K> {
  return {
    [Symbol.iterator]() {
      return new SelectIteratorWrapper(iterable[Symbol.iterator](), func);
    },
  };
}

class WhereIteratorWrapper<T> implements Iterator<T> {
  constructor(
    private innerIterator: Iterator<T>,
    private predicate: (e: T) => boolean
  ) {}
  next(value?: any): IteratorResult<T, any> {
    let next = this.innerIterator.next();
    while (!next.done && !this.predicate(next.value)) {
      next = this.innerIterator.next();
    }
    return next;
  }
}

export function iuWhere<T>(
  predicate: (e: T) => boolean,
  iterable: Iterable<T>
): Iterable<T> {
  return {
    [Symbol.iterator]() {
      return new WhereIteratorWrapper(iterable[Symbol.iterator](), predicate);
    },
  };
}

export function iuFirst<T>(
  iterable: Iterable<T>,
  predicate?: (e: T) => boolean
): T {
  if (
    iterable instanceof Array &&
    iterable.length > 0 &&
    (typeof predicate === 'undefined' || predicate(iterable[0]))
  ) {
    return iterable[0];
  } else {
    const iterator = iterable[Symbol.iterator]();
    let next = iterator.next();
    while (!next.done) {
      if (typeof predicate == 'undefined' || predicate(next.value)) {
        return next.value;
      }
      next = iterator.next();
    }
  }
  throw Error('Elements not available');
}

/**
 * Returns the first element of the iterable for which predicate returns `true`,
 * or a default value if the predicate returns `false` for every element.
 * If no predicate is provided, returns the first element of the iterable, or
 * a default value when the iterable is empty.
 *
 * @export
 * @template T
 * @param {Iterable<T>} iterable
 * @param {(e: T) => boolean} [predicate]
 * @param {T} [defaultValue=null]
 * @return {*}  {T}
 */
export function iuFirstOrDefault<T>(
  iterable: Iterable<T>,
  predicate?: (e: T) => boolean,
  defaultValue: T = null
): T {
  if (
    iterable instanceof Array &&
    iterable.length > 0 &&
    (typeof predicate === 'undefined' || predicate(iterable[0]))
  ) {
    return iterable[0];
  } else if (iterable != null) {
    const iterator = iterable[Symbol.iterator]();
    let next = iterator.next();
    while (!next.done) {
      if (typeof predicate == 'undefined' || predicate(next.value)) {
        return next.value;
      }
      next = iterator.next();
    }
  }
  return defaultValue;
}

export function iuCount<T>(
  iterable: Iterable<T>,
  predicate?: (e: T) => boolean
): number {
  if (iterable instanceof Array && !predicate) {
    return iterable.length;
  } else if (iterable instanceof SimpleList && !predicate) {
    return iterable.count;
  } else {
    const iterator = iterable[Symbol.iterator]();
    let i = 0;
    let itResult = iterator.next();
    while (!itResult.done) {
      const current = itResult.value;
      itResult = iterator.next();
      if (predicate && !predicate(current)) {
        continue;
      }
      i++;
    }
    return i;
  }
}

class TakeIteratorWrapper<T> implements Iterator<T> {
  constructor(private innerIterator: Iterator<T>, private count: number) {}
  next(value?: any): IteratorResult<T, any> {
    if (this.count > 0) {
      this.count--;
      const next = this.innerIterator.next();
      return next;
    } else {
      return { value: undefined, done: true };
    }
  }
}

export function iuTake<T>(count: number, iterable: Iterable<T>): Iterable<T> {
  return {
    [Symbol.iterator]() {
      return new TakeIteratorWrapper(iterable[Symbol.iterator](), count);
    },
  };
}

export function iuToList<T>(iterable: Iterable<T>): SimpleList<T> {
  return new SimpleList(iterable);
}

export function iuLast<T>(
  iterable: Iterable<T>,
  predicate?: (e: T) => boolean
): T {
  if (iterable instanceof Array && iterable.length > 0 && !predicate) {
    return iterable[iterable.length - 1];
  } else {
    const iterator = iterable[Symbol.iterator]();
    let next = iterator.next();
    let found = false;
    let lastValue = null;
    while (!next.done) {
      if (predicate) {
        if (predicate(next.value)) {
          found = true;
          lastValue = next.value;
        }
      } else {
        found = true;
        lastValue = next.value;
      }
      next = iterator.next();
    }
    if (!found) {
      throw Error('Elements not available');
    } else {
      return lastValue;
    }
  }
}

export function iuLastOrDefault<T>(
  iterable: Iterable<T>,
  predicate?: (e: T) => boolean
): T {
  if (iterable == null) {
    throw Error('ArgumentNullException');
  }

  if (iterable instanceof Array && iterable.length > 0) {
    if (predicate == undefined) {
      return iterable[iterable.length - 1];
    } else {
      for (let i = iterable.length - 1; i >= 0; i--) {
        if (predicate(iterable[i])) {
          return iterable[i];
        }
      }
    }
  } else {
    for (const val of Array.from(iterable).reverse()) {
      if (predicate == undefined || predicate(val)) {
        return val;
      }
    }
  }

  // here we have the problem of value types
  // in this case we should not return null
  // this work is still pending
  return null;
}

export function iuElementAt<T>(iterable: Iterable<T>, index: number): T {
  if (iterable instanceof Array && iterable.length > index && index >= 0) {
    return iterable[index];
  } else if (iterable instanceof SimpleList) {
    return iterable.getItem(index);
  } else {
    const iterator = iterable[Symbol.iterator]();
    let next = iterator.next();
    let found = false;
    let lastValue;
    let i = 0;
    while (!next.done) {
      if (i == index) {
        found = true;
        lastValue = next.value;
        break;
      }
      next = iterator.next();
      i++;
    }
    if (!found) {
      throw Error('Elements not available');
    } else {
      return lastValue;
    }
  }
}

export function iuElementAtOrDefault<T>(
  iterable: Iterable<T>,
  index: number
): T {
  if (iterable == null) {
    throw Error('ArgumentNullException');
  }

  if (iterable instanceof Array && iterable.length > index && index >= 0) {
    return iterable[index];
  } else {
    const iterator = iterable[Symbol.iterator]();
    let result = iterator.next();
    let i = 0;
    while (!result.done) {
      if (i == index) {
        return result.value;
      }
      result = iterator.next();
      i++;
    }
  }

  return null;
}

export function iuAny<T>(iterable: Iterable<T>, predicate?: (e: T) => boolean) {
  if (iterable instanceof Array && typeof predicate === 'undefined') {
    return iterable.length > 0;
  } else if (
    iterable instanceof SimpleList &&
    typeof predicate === 'undefined'
  ) {
    return iterable.count > 0;
  } else {
    const iterator = iterable[Symbol.iterator]();
    let next = iterator.next();
    if (typeof predicate === 'undefined') {
      return !next.done;
    } else {
      let found = false;
      while (!next.done) {
        if (predicate(next.value)) {
          found = true;
          break;
        }
        next = iterator.next();
      }
      return found;
    }
  }
}

/**
 *  Verifies if a predicate is `true` for all the elements of an iterable
 *
 * @export
 * @template T
 * @param {Iterable<T>} iterable sequence to verify
 * @param {(e: T) => boolean} [predicate] predicate
 * @return {*} true if the predicate is valid for all elements of the sequence
 */
export function iuAll<T>(iterable: Iterable<T>, predicate: (e: T) => boolean) {
  for (const element of iterable) {
    if (!predicate(element)) {
      return false;
    }
  }
  return true;
}

export function iuSingle<T>(
  iterable: Iterable<T>,
  predicate?: (e: T) => boolean
) {
  const errNotSingle = 'Sequence does not contain exactly one element';
  const errNoElements =
    'Sequence does not contain exactly one element that satisfies the condition';
  let found = false;
  let result: T = null;
  if (
    iterable instanceof Array &&
    iterable.length == 1 &&
    (typeof predicate == 'undefined' || predicate(iterable[0]))
  ) {
    return iterable[0];
  } else if (
    iterable instanceof SimpleList &&
    iterable.count == 1 &&
    (typeof predicate == 'undefined' || predicate(iterable.getItem(0)))
  ) {
    return iterable.getItem(0);
  } else {
    const iterator = iterable[Symbol.iterator]();
    let next = iterator.next();
    while (!next.done) {
      if (typeof predicate == 'undefined' || predicate(next.value)) {
        if (found) {
          throw new Error(errNotSingle);
        }
        result = next.value;
        found = true;
      }
      next = iterator.next();
    }
  }
  if (!found) {
    throw new Error(errNoElements);
  }
  return result;
}

class ConcatIterator<T> implements Iterator<T> {
  private firstFinished: boolean;
  private iterator: Iterator<T>;

  constructor(private iterable1: Iterable<T>, private iterable2: Iterable<T>) {
    this.iterator = iterable1[Symbol.iterator]();
  }

  next(value?: any): IteratorResult<T> {
    let result: IteratorResult<T> = null;
    if (!this.firstFinished) {
      result = this.iterator.next();
      if (!result.done) {
        return result;
      }

      this.firstFinished = true;
      this.iterator = this.iterable2[Symbol.iterator]();
    }

    return this.iterator.next();
  }
}

export function iuConcat<T>(
  first: Iterable<T>,
  second: Iterable<T>
): Iterable<T> {
  if (first == null || second == null) {
    throw new Error('ArgumentNullException');
  }

  return {
    [Symbol.iterator]() {
      return new ConcatIterator<T>(first, second);
    },
  };
}

/**
 * Returns whether the iterable contains a specified element.
 *
 * @param iterable - The iterable source.
 * @param value - The element to find.
 */
export function iuContains<T>(
  iterable: Iterable<T>,
  value: T,
  comparer?: any
): boolean {
  if (iterable == null) {
    throw new Error('ArgumentNullException');
  }

  // Use 'Equals' to compare elements if available. If not, use '==='.
  let isEqual: Function = getCompareFunction<T>(value);

  for (const v of iterable) {
    if (isEqual(value, v)) {
      return true;
    }
  }

  return false;
}

/**
 * Base class for DistinctIterator, IntersectIterator and ExceptIterator.
 *
 * This class contains the logic to detect if the elements have an 'Equals()'
 * function, and use it if available to compare elements. If 'Equals()' is
 * not available, then '===' is used to compare elements.
 *
 * The logic to discriminate which elements are returned must be implemented
 * on the derived classes.
 */
abstract class BaseDieIterator<T> implements Iterator<T> {
  protected baseSet: Set<T>;
  protected iterator: Iterator<T>;
  protected nextResult: IteratorResult<T, any>;
  protected baseSetHas: Function;
  protected baseSetDelete: Function;

  constructor(first: Iterable<T>, second: Iterable<T>) {
    this.iterator = first[Symbol.iterator]();
    this.baseSet = new Set(second);
    this.baseSetHas = null;

    // Get the first element to find out if it has an 'Equals()' function.
    this.nextResult = this.iterator.next();
    if (!this.nextResult.done) {
      if (
        this.nextResult?.value &&
        typeof this.nextResult.value.Equals === 'function'
      ) {
        // This is slow - O(n): use slow implementations of 'has()'
        // and 'delete()' to use 'Equals()' to compare elements.
        this.baseSetHas = (value: T): boolean =>
          iuContains(this.baseSet, value);
        this.baseSetDelete = (value: T): boolean => {
          let found = false;
          this.baseSet.forEach((x) => {
            if (value['Equals'](x)) {
              found = true;
              this.baseSet.delete(x);
            }
          });
          return found;
        };
      } else {
        // This is fast - O(1): uses the Set directly, but Set does not use 'Equals()'.
        // This approach is valid for value types and objects without 'Equals()'.
        this.baseSetHas = (value: T): boolean => this.baseSet.has(value);
        this.baseSetDelete = (value: T): boolean => this.baseSet.delete(value);
      }
    }
  }

  next(_value?: any): IteratorResult<T> {
    if (this.nextResult.done) {
      return this.nextResult;
    }

    while (!this.nextResult.done) {
      if (this.returnCondition()) {
        this.beforeReturn();
        const return_result = this.nextResult;
        this.nextResult = this.iterator.next();
        return return_result;
      }

      this.nextResult = this.iterator.next();
    }

    return this.nextResult;
  }

  abstract returnCondition(): boolean;

  abstract beforeReturn(): void;
}

class DistinctIterator<T> extends BaseDieIterator<T> {
  constructor(private iterable: Iterable<T>) {
    super(iterable, []);
  }

  returnCondition(): boolean {
    return !this.baseSetHas(this.nextResult.value);
  }

  beforeReturn(): void {
    this.baseSet.add(this.nextResult.value);
  }
}

function getCompareFunction<T>(value: T): (x: any, y: any) => boolean {
  let isEqual: (x: any, y: any) => boolean = null;
  if (value && typeof value['Equals'] === 'function') {
    isEqual = (left: T, right: T): boolean => left['Equals'](right);
  } else {
    isEqual = (left: T, right: T): boolean => left === right;
  }
  return isEqual;
}

/**
 * Returns distinct elements from the iterable.
 *
 * @param iterable - The iterable source.
 */
export function iuDistinct<T>(
  iterable: Iterable<T>,
  comparer?: any
): Iterable<T> {
  if (iterable == null) {
    throw new Error('ArgumentNullException');
  }

  return {
    [Symbol.iterator]() {
      return new DistinctIterator<T>(iterable);
    },
  };
}

class IntersectIterator<T> extends BaseDieIterator<T> {
  constructor(private first: Iterable<T>, private second: Iterable<T>) {
    super(first, second);
  }

  returnCondition(): boolean {
    return this.baseSetHas(this.nextResult.value);
  }

  beforeReturn(): void {
    this.baseSetDelete(this.nextResult.value);
  }
}

/**
 * Returns the set intersection of two iterables.
 *
 * @param first
 * @param second
 */
export function iuIntersect<T>(
  first: Iterable<T>,
  second: Iterable<T>,
  comparer?: any
): Iterable<T> {
  if (first == null || second == null) {
    throw new Error('ArgumentNullException');
  }

  return {
    [Symbol.iterator]() {
      return new IntersectIterator<T>(first, second);
    },
  };
}

/**
 * Defines a custom {Iterable<T>} object that will iterate thru two
 * iterables by ignoring the ones in the second iterable that already exists in the
 * first one.
 *
 * @class UnionIterator
 * @implements {Iterable<T>}
 * @template T
 */
class UnionIterator<T> implements Iterable<T> {
  protected nextResult: IteratorResult<T, any>;

  /**
   * Creates an instance of UnionIterator for two iterable objects
   * @param {Iterable<T>} first the iterable containing the first set of elements
   * @param {Iterable<T>} second the iterable containing the second set of elements, the ones
   * already in first will be discarded.
   * @memberof UnionIterator
   */
  constructor(private first: Iterable<T>, private second: Iterable<T>) {}

  /**
   * Defines the iterator for this class
   *
   * @return {*}  {Iterator<T, any, any>}
   * @memberof UnionIterator
   */
  *[Symbol.iterator](): Iterator<T, any, any> {
    for (const e of this.first) {
      yield e;
    }
    for (const e of this.second) {
      /* istanbul ignore else */
      if (!iuContains(this.first, e)) {
        yield e;
      }
    }
  }
}

/**
 * Iterates thru the elements of two iterables by disarding the duplicated
 * ones.  Default comparison is done using the standard '===' comparison operator
 *
 * @export
 * @template T
 * @param {Iterable<T>} first the iterable containing the first set of elements
 * @param {Iterable<T>} second the iterable containing the second set of elements, the ones
 * already in first will be discarded.
 * @param {*} [comparer] The function used to compared elements.  Currently not supported.
 * @return {*}  {Iterable<T>}
 */
export function iuUnion<T>(
  first: Iterable<T>,
  second: Iterable<T>,
  comparer?: any
): Iterable<T> {
  if (first == null || second == null) {
    throw new Error('ArgumentNullException');
  }

  return new UnionIterator<T>(first, second);
}

class ExceptIterator<T> extends BaseDieIterator<T> {
  constructor(private first: Iterable<T>, private second: Iterable<T>) {
    super(first, second);
  }

  returnCondition(): boolean {
    return !this.baseSetHas(this.nextResult.value);
  }

  beforeReturn(): void {
    this.baseSet.add(this.nextResult.value);
  }
}

/**
 * Returns the set difference of two iterables.
 *
 * @param first - base iterable.
 * @param second - the iterable to substract.
 */
export function iuExcept<T>(
  first: Iterable<T>,
  second: Iterable<T>
): Iterable<T> {
  if (first == null || second == null) {
    throw new Error('ArgumentNullException');
  }

  return {
    [Symbol.iterator]() {
      return new ExceptIterator<T>(first, second);
    },
  };
}

export function iuOrderBy<T>(
  iterable: Iterable<T>,
  criteria?: (e: T) => any
): IOrderedIterable<T>;
export function iuOrderBy<T, K>(
  iterable: Iterable<T>,
  criteria?: (e: T) => K,
  comparer?: IComparer<K>
): IOrderedIterable<T>;
export function iuOrderBy<T>(
  iterable: Iterable<T>,
  criteria?: (e: T) => any,
  comparer?: IComparer<any>
): IOrderedIterable<T> {
  return new IterableForSorting(iterable, criteria, comparer);
}

export function iuOrderByDescending<T>(
  iterable: Iterable<T>,
  criteria?: (e: T) => any
): IOrderedIterable<T>;
export function iuOrderByDescending<T, K>(
  iterable: Iterable<T>,
  criteria?: (e: T) => K,
  comparer?: IComparer<K>
): IOrderedIterable<T>;
export function iuOrderByDescending<T>(
  iterable: Iterable<T>,
  criteria?: (e: T) => any,
  comparer?: IComparer<any>
): IOrderedIterable<T> {
  return new IterableForSorting(
    iterable,
    criteria,
    new DescendingComparer(comparer)
  );
}

class DefaultOrderByComparer implements IComparer<unknown> {
  Compare(e1: unknown, e2: unknown): number {
    if (typeof e1 == 'number' && typeof e2 == 'number') {
      return e1 - e2;
    } else if (e1 instanceof Date && e2 instanceof Date) {
      return e1.getTime() - e2.getTime();
    } else if (typeof e1 == 'string' && typeof e2 == 'string') {
      return e1.localeCompare(e2);
    } else if (typeof e1 === 'boolean' && typeof e2 === 'boolean') {
      return +e1 - +e2;
    } else {
      // fail with equal
      return 0;
    }
  }
}

class DescendingComparer<T> implements IComparer<T> {
  constructor(private comparer: IComparer<T>) {
    if (comparer == null) {
      this.comparer = new DefaultOrderByComparer();
    }
  }

  Compare(x: T, y: T): number {
    return this.comparer.Compare(y, x); // Just reverse elements.
  }
}

class IterableForSorting<T> implements IOrderedIterable<T> {
  private criterias: ((e: T) => unknown)[] = [];
  private comparers: IComparer<unknown>[] = [];

  constructor(
    private iterable: Iterable<T>,
    criteria: (e: T) => unknown,
    comparer?: IComparer<unknown>
  ) {
    this.criterias.push(criteria);
    this.AddComparer(comparer);
  }

  CreateOrderedIterable<K>(
    keySelector: (e: T) => K,
    comparer: IComparer<K>,
    descending: boolean
  ): IOrderedIterable<T> {
    Debugger.Throw('Method not implemented.');
    return null;
  }

  [Symbol.iterator](): Iterator<T, any, undefined> {
    // until we find a better approach we will be converting
    // the collection to an array

    const collection = [...this.iterable];
    collection.sort((o1, o2) => {
      for (let i = 0; i < this.criterias.length; i++) {
        const e1 = this.criterias[i](o1);
        const e2 = this.criterias[i](o2);
        const result = this.comparers[i].Compare(e1, e2);
        if (result !== 0) {
          return result;
        }
      }
      return 0;
    });
    return collection[Symbol.iterator]();
  }

  ThenBy(criteria: (e: T) => unknown, comparer?: IComparer<unknown>): void {
    this.criterias.push(criteria);
    this.AddComparer(comparer);
  }

  private AddComparer(comparer?: IComparer<unknown>): void {
    if (comparer == null) {
      this.comparers.push(new DefaultOrderByComparer());
    } else {
      this.comparers.push(comparer);
    }
  }
}

export function iuThenBy<T>(
  iterable: Iterable<T>,
  criteria?: (e: T) => any
): IOrderedIterable<T>;
export function iuThenBy<T, K>(
  iterable: Iterable<T>,
  criteria?: (e: T) => K,
  comparer?: IComparer<K>
): IOrderedIterable<T>;
export function iuThenBy<T>(
  iterable: Iterable<T>,
  criteria?: (e: T) => any,
  comparer?: IComparer<any>
): IOrderedIterable<T> {
  (iterable as IterableForSorting<T>).ThenBy(criteria, comparer);
  return iterable as IOrderedIterable<T>;
}

export function iuThenByDescending<T>(
  iterable: Iterable<T>,
  criteria?: (e: T) => any
): IOrderedIterable<T>;
export function iuThenByDescending<T, K>(
  iterable: Iterable<T>,
  criteria?: (e: T) => K,
  comparer?: IComparer<K>
): IOrderedIterable<T>;
export function iuThenByDescending<T>(
  iterable: Iterable<T>,
  criteria?: (e: T) => any,
  comparer?: IComparer<any>
): IOrderedIterable<T> {
  (iterable as IterableForSorting<T>).ThenBy(
    criteria,
    new DescendingComparer(comparer)
  );
  return iterable as IOrderedIterable<T>;
}

export interface IGrouping<T, K> extends Iterable<T> {
  Key: K;
}
class GroupByGrouping<T, K> extends SimpleList<T> implements IGrouping<T, K> {
  public Key: K;
}

export function iuGroupBy<T, K>(
  iterable: Iterable<T>,
  keySelector: (e: T) => K
): Iterable<IGrouping<T, K>>;

export function iuGroupBy<T, K, V>(
  iterable: Iterable<T>,
  keySelector: (e: T) => K,
  valueSelector: (e: T) => V
): Iterable<IGrouping<V, K>>;

export function iuGroupBy<T, K, V>(
  iterable: Iterable<T>,
  keySelector: (e: T) => K,
  valueSelector?: (e: T) => V
): any {
  const groups = new Map<string, GroupByGrouping<T | V, K>>();
  for (const value of iterable) {
    const rawKey = keySelector(value);
    const key = (rawKey ?? '').toString();
    let group: GroupByGrouping<T | V, K> = null;
    if (groups.has(key)) {
      group = groups.get(key);
    } else {
      group = new GroupByGrouping<T, K>();
      groups.set(key, group);
      group.Key = rawKey;
    }
    group.add(valueSelector ? valueSelector(value) : value);
  }
  return groups.values();
}

/**
 * Gets a single element from the given iterable.  If no predicate then the only element in iterable is returned,
 * if more than one element then null is returned.  If predicate has value then the only element matching the
 * predicate is returned, if more than one matches or none then null is returned.
 *
 * @param iterable The {@link Iterable<T>} from where to extract the single element
 * @param predicate a the predicate to apply to get the single element.  If not predicate then the
 * iterable value must contain exactly one element.
 * @returns
 */
export function iuSingleOrDefault<T>(
  iterable: Iterable<T>,
  predicate?: (e: T) => boolean
) {
  // no predicate, a single element iterable.
  if (typeof predicate == 'undefined') {
    const iterator = iterable[Symbol.iterator]();
    const next = iterator.next();
    if (!next.done) {
      const result: T = next.value;
      if (iterator.next().done) {
        // checks for one element only
        return result;
      }
    }
  } else {
    let matchingElement: T = null;
    for (const e of iterable) {
      const isMatch: boolean = predicate(e);
      if (isMatch) {
        if (matchingElement == null) {
          matchingElement = e;
        } else {
          // more than one match returns null
          throw new InvalidOperationException(
            'more than one element in singleorDefault'
          );
        }
      }
    }
    if (matchingElement != null) {
      return matchingElement;
    }
  }
  return null;
}

class FlattenIteratorWrapper<T, K, V> implements Iterator<V> {
  private currentIterator: Iterator<V>;

  constructor(
    private mainIterator: Iterator<T>,
    private func: (e: T) => Iterable<K>,
    private resultSelector: (e: T, f: K) => V
  ) {}
  next(value?: any): IteratorResult<V, any> {
    if (this.currentIterator) {
      const result = this.currentIterator.next();
      if (!result.done) {
        return result;
      }
    }
    const nextInMain = this.mainIterator.next();
    if (!nextInMain.done) {
      this.currentIterator = iuSelect(
        (innerSingleResult) =>
          this.resultSelector(nextInMain.value, innerSingleResult),
        this.func(nextInMain.value)
      )[Symbol.iterator]();
      // controversial recurisive call:
      return this.next();
    } else {
      return { value: undefined, done: true };
    }
  }
}

export function iuSelectMany<T, K, V>(
  func: (e: T) => Iterable<K>,
  resultSelector: (e: T, f: K) => V,
  iterable: Iterable<T>
): Iterable<V>;
export function iuSelectMany<T, K>(
  func: (e: T) => Iterable<K>,
  iterable: Iterable<T>
): Iterable<K>;
export function iuSelectMany<T, K, V>(
  p1: unknown,
  p2: unknown,
  p3?: unknown
): unknown {
  if (typeof p3 !== 'undefined') {
    const iterable = p3 as Iterable<T>;
    const func = p1 as (e: T) => Iterable<K>;
    const resultSelector = p2 as (e: T, f: K) => V;
    return {
      [Symbol.iterator]() {
        return new FlattenIteratorWrapper(
          iterable[Symbol.iterator](),
          func,
          resultSelector
        );
      },
    };
  } else {
    const iterable = p2 as Iterable<T>;
    const func = p1 as (e: T) => Iterable<K>;
    return {
      [Symbol.iterator]() {
        return new FlattenIteratorWrapper(
          iterable[Symbol.iterator](),
          func,
          (e, f) => f
        );
      },
    };
  }
}

class RangeIterator implements Iterator<number> {
  constructor(private current: number, private count: number) {}
  next(value?: any): IteratorResult<number, any> {
    if (this.count > 0) {
      this.count--;

      return { value: this.current++, done: false };
    } else {
      return { value: undefined, done: true };
    }
  }
}

export function iuRange(start: number, count: number): Iterable<number> {
  return {
    [Symbol.iterator]() {
      return new RangeIterator(start, count);
    },
  };
}

const emptyIterable = {
  [Symbol.iterator]() {
    return { next: () => ({ done: true, value: undefined }) };
  },
};

export function iuEmpty<T>(): Iterable<T> {
  return emptyIterable;
}

export function iuAsEnumerable<T>(iterable: Iterable<T>): Iterable<T> {
  return iterable;
}

export function iuCast<T>(iterable: Iterable<unknown>): Iterable<T> {
  // The following code needs more work to avoid wrong conversions
  // In typescript we will not get cast exceptions
  return iuSelect((x) => x as T, iterable);
}

export function wrapArrayWithList<T>(arr: T[]): IList<T> {
  const result = new SimpleList<T>();
  result.internalArray = arr;
  return result;
}

export function iuMax<T>(iterable: Iterable<T>): T;
export function iuMax<T, K>(
  iterable: Iterable<T>,
  valueSelector: (item: T) => K
): K;
export function iuMax<T, K>(
  iterable: Iterable<T>,
  valueSelector?: (item: T) => K
): K {
  let max: K = null;
  for (const item of iterable) {
    const value = valueSelector ? valueSelector(item) : (item as unknown as K);
    if (max === null) {
      max = value;
    }
    if (value > max) {
      max = value;
    }
  }
  return max;
}

export function iuMin<T>(
  iterable: Iterable<T>,
  valueSelector?: (item: T) => number
) {
  let min = Number.MAX_VALUE;
  for (const item of iterable) {
    const value = valueSelector
      ? valueSelector(item)
      : (item as unknown as number);
    if (value < min) {
      min = value;
    }
  }
  return min;
}

export function iuSum<T>(
  iterable: Iterable<T>,
  valueSelector: (item: T) => number
) {
  let result = 0;
  for (const item of iterable) {
    const value = valueSelector(item);
    result += value;
  }
  return result;
}

export function iuOfType<K>(
  iterable: Iterable<unknown>,
  type: any
): Iterable<K> {
  // notices that  runtime verification of interface types checking is still pending
  return iuWhere(
    (obj) => obj instanceof type,
    iterable
  ) as unknown as Iterable<K>;
}

export function iuGetUntypedIterator<T>(iterable: Iterable<T>): any {
  Debugger.Throw('Not implemented');
}

function* joinGenerator<TInner, TOuter, TKey, TResult>(
  iterable: Iterable<TInner>,
  iterable2: Iterable<TOuter>,
  innerSelector: (inner: TInner) => TKey,
  outerSelector: (outer: TOuter) => TKey,
  resultSelector: (inner: TInner, outer: TOuter) => TResult
) {
  for (const x of iterable) {
    const x_key = innerSelector(x);
    for (const y of iterable2) {
      const y_key = outerSelector(y);
      const compare = getCompareFunction(x_key);
      if (compare(x_key, y_key)) {
        yield resultSelector(x, y);
      }
    }
  }
}

export function iuJoin<TInner, TOuter, TKey, TResult>(
  iterable: Iterable<TInner>,
  iterable2: Iterable<TOuter>,
  innerSelector: (inner: TInner) => TKey,
  outerSelector: (outer: TOuter) => TKey,
  resultSelector: (inner: TInner, outer: TOuter) => TResult
): Iterable<TResult> {
  return {
    [Symbol.iterator]() {
      return joinGenerator(
        iterable,
        iterable2,
        innerSelector,
        outerSelector,
        resultSelector
      );
    },
  };
}

/**
 *  Apply an operation to each element of an Iterable
 *
 * @export
 * @template T
 * @param {Iterable<T>} iterable iterable to process
 * @param {(e: T, i? : number) => void} func  opeartion to apply
 */
export function iuForEach<T>(
  iterable: Iterable<T>,
  func: (e: T, i?: number) => void
): void {
  for (const element of iterable) {
    func(element);
  }
}

/**
 *  Adds the contents of an iterable to the given collection
 *
 * @export
 * @template T
 * @param {ObjectModelCollection<T>} collection to modified
 * @param {Iterable<T>} newElements to add
 */
export function iuAddRange<T>(
  collection: ObjectModelCollection<T>,
  newElements: Iterable<T>
) {
  for (const obj of newElements) {
    collection.add(obj);
  }
}

result-matching ""

    No results matching ""