import { isEmpty, values } from "lodash";

/**
 * A single entry in a prioity list
 */
export class Item<T> {

	public content: T;
	public weight: number;

	public constructor(content: T, priority: number) {
		this.content = content;
		this.weight = priority;
	}
}

/**
 * Priority lists are used to create priority-sorted arrays
 * where each item has a weight value. The weight value
 * is used to determine the order of the items in the
 * list.
 */
export class PriorityList<T> {

	private items: { [key: string]: Item<T> };
	private cache: Item<T>[]|null;

	public constructor() {
		this.items = {};
		this.cache = null;
	}

	/**
	 * Check whether the list is empty
	 */
	public isEmpty(): boolean {
		return isEmpty(this.items);
	}

	/**
	 * Check whether an item is present in the list
	 *
	 * @param key The key to check
	 */
	public has(key: string) : boolean {
		return !!this.items[key];
	}

	/**
	 * Returns the value at the specified key
	 *
	 * @param key The key to retrieve
	 */
	public get(key: string) : T {
		return this.items[key].content;
	}
	
	/**
	 * Returns the first entry in the list
	 * @returns 
	 */
	public getFirst(): Item<T> | undefined {
		return this.toItems()[0];
	}

	/**
	 * Returns the last item in the list
	 */
	public getLast(): Item<T> | undefined {
		return this.toItems()[this.cache!.length - 1];
	}

	/**
	 * Returns the weight of the specified key
	 *
	 * @param key The key weight to retrieve
	 */
	public getWeight(key: string) : number {
		return this.items[key].weight;
	}

	/**
	 * Add a new item to this priority list with the specified weight
	 * 
	 * @param key The key
	 * @param content The value to assign
	 * @param weight The weight of this value
	 * @param replace Whether to replace an existing value
	 * @returns self
	 */
	public add(key: string, value: T, weight = 0, replace = true) : PriorityList<T> {
		if(replace || !this.has(key)) {
			this.items[key] = new Item(value, weight);
		}

		this.cache = null;
		return this;
	}

	/**
	 * Remove an item from the list
	 *
	 * @param key The key to remove
	 * @returns self
	 */
	public remove(key: string) : PriorityList<T> {
		delete this.items[key];
		this.cache = null;
		return this;
	}

	/**
	 * Merge another list's items into this one
	 *
	 * @param items Another priority list
	 * @returns self
	 */
	public merge(items: PriorityList<T>) : PriorityList<T> {
		this.items = { ...this.items, ...items.items };
		this.cache = null;
		return this;
	}

	/**
	 * Convert the list into an array of item content arranged by priority. Returns
	 * an array of objects with the item's content and weight.
	 * 
	 * @returns An array of objects with the item's content and weight
	 */
	public toItems(): Item<T>[] {
		if(!this.cache) {
			this.cache = values(this.items).sort((a, b) => a.weight - b.weight);
		}

		return this.cache!;
	}

	/**
	 * Convert the list into an array of item content arranged by priority. Returns
	 * an array of all values.
	 * 
	 * @returns An array of all values
	 */
	public toArray(): T[] {
		return this.toItems().map(item => item.content);
	}

}