/**
 * Module dependencies
 */
import LRUCache from 'lru-cache'
import { NumberAllocator } from 'number-allocator'

/**
 * Topic Alias sending manager
 * This holds both topic to alias and alias to topic map
 * @param {Number} [max] - topic alias maximum entries
 */
export default class TopicAliasSend {
	private aliasToTopic: LRUCache<number, string>

	private topicToAlias: Record<string, number>

	private max: number

	private numberAllocator: NumberAllocator

	public length: number

	constructor(max: number) {
		if (max > 0) {
			this.aliasToTopic = new LRUCache<number, string>({ max })
			this.topicToAlias = {}
			this.numberAllocator = new NumberAllocator(1, max)
			this.max = max
			this.length = 0
		}
	}

	/**
	 * Insert or update topic - alias entry.
	 * @param {String} [topic] - topic
	 * @param {Number} [alias] - topic alias
	 * @returns {Boolean} - if success return true otherwise false
	 */
	put(topic: string, alias: number): boolean {
		if (alias === 0 || alias > this.max) {
			return false
		}
		const entry = this.aliasToTopic.get(alias)
		if (entry) {
			delete this.topicToAlias[entry]
		}
		this.aliasToTopic.set(alias, topic)
		this.topicToAlias[topic] = alias
		this.numberAllocator.use(alias)
		this.length = this.aliasToTopic.size
		return true
	}

	/**
	 * Get topic by alias
	 * @param {Number} [alias] - topic alias
	 * @returns {String} - if mapped topic exists return topic, otherwise return undefined
	 */
	getTopicByAlias(alias: number): string {
		return this.aliasToTopic.get(alias)
	}

	/**
	 * Get topic by alias
	 * @param {String} [topic] - topic
	 * @returns {Number} - if mapped topic exists return topic alias, otherwise return undefined
	 */
	getAliasByTopic(topic: string): number | undefined {
		const alias = this.topicToAlias[topic]
		if (typeof alias !== 'undefined') {
			this.aliasToTopic.get(alias) // LRU update
		}
		return alias
	}

	/**
	 * Clear all entries
	 */
	clear() {
		this.aliasToTopic.clear()
		this.topicToAlias = {}
		this.numberAllocator.clear()
		this.length = 0
	}

	/**
	 * Get Least Recently Used (LRU) topic alias
	 * @returns {Number} - if vacant alias exists then return it, otherwise then return LRU alias
	 */
	getLruAlias(): number {
		const alias = this.numberAllocator.firstVacant()
		if (alias) return alias
		// get last alias (key) from LRU cache
		return [...this.aliasToTopic.keys()][this.aliasToTopic.size - 1]
	}
}
