import { MemoryRecord } from './MemoryRecord.js';

/**
* Class for representing the memory system of a game entity. It is used for managing,
* filtering, and remembering sensory input.
*
* @author {@link https://github.com/Mugen87|Mugen87}
*/
class MemorySystem {

	/**
	* Constructs a new memory system.
	*
	* @param {GameEntity} owner - The game entity that owns this memory system.
	*/
	constructor( owner = null ) {

		/**
		* The game entity that owns this memory system.
		* @type {?GameEntity}
		* @default null
		*/
		this.owner = owner;

		/**
		* Used to simulate memory of sensory events. It contains {@link MemoryRecord memory records}
		* of all relevant game entities in the environment. The records are usually update by
		* the owner of the memory system.
		* @type {Array<MemoryRecord>}
		*/
		this.records = new Array();

		/**
		* Same as {@link MemorySystem#records} but used for fast access via the game entity.
		* @type {Map<GameEntity,MemoryRecord>}
		*/
		this.recordsMap = new Map();

		/**
		* Represents the duration of the game entities short term memory in seconds.
		* When a bot requests a list of all recently sensed game entities, this value
		* is used to determine if the bot is able to remember a game entity or not.
		* @type {Number}
		* @default 1
		*/
		this.memorySpan = 1;

	}

	/**
	* Returns the memory record of the given game entity.
	*
	* @param {GameEntity} entity - The game entity.
	* @return {MemoryRecord} The memory record for this game entity.
	*/
	getRecord( entity ) {

		return this.recordsMap.get( entity );

	}

	/**
	* Creates a memory record for the given game entity.
	*
	* @param {GameEntity} entity - The game entity.
	* @return {MemorySystem} A reference to this memory system.
	*/
	createRecord( entity ) {

		const record = new MemoryRecord( entity );

		this.records.push( record );
		this.recordsMap.set( entity, record );

		return this;

	}

	/**
	* Deletes the memory record for the given game entity.
	*
	* @param {GameEntity} entity - The game entity.
	* @return {MemorySystem} A reference to this memory system.
	*/
	deleteRecord( entity ) {

		const record = this.getRecord( entity );
		const index = this.records.indexOf( record );

		this.records.splice( index, 1 );
		this.recordsMap.delete( entity );

		return this;

	}

	/**
	* Returns true if there is a memory record for the given game entity.
	*
	* @param {GameEntity} entity - The game entity.
	* @return {Boolean} Whether the game entity has a memory record or not.
	*/
	hasRecord( entity ) {

		return this.recordsMap.has( entity );

	}

	/**
	* Removes all memory records from the memory system.
	*
	* @return {MemorySystem} A reference to this memory system.
	*/
	clear() {

		this.records.length = 0;
		this.recordsMap.clear();

		return this;

	}

	/**
	* Determines all valid memory record and stores the result in the given array.
	*
	* @param {Number} currentTime - The current elapsed time.
	* @param {Array<MemoryRecord>} result - The result array.
	* @return {Array<MemoryRecord>} The result array.
	*/
	getValidMemoryRecords( currentTime, result ) {

		const records = this.records;

		result.length = 0;

		for ( let i = 0, l = records.length; i < l; i ++ ) {

			const record = records[ i ];

			if ( ( currentTime - record.timeLastSensed ) <= this.memorySpan ) {

				result.push( record );

			}

		}

		return result;

	}

	/**
	* Transforms this instance into a JSON object.
	*
	* @return {Object} The JSON object.
	*/
	toJSON() {

		const json = {
			type: this.constructor.name,
			owner: this.owner.uuid,
			records: new Array(),
			memorySpan: this.memorySpan
		};

		const records = this.records;

		for ( let i = 0, l = records.length; i < l; i ++ ) {

			const record = records[ i ];
			json.records.push( record.toJSON() );

		}

		return json;

	}

	/**
	* Restores this instance from the given JSON object.
	*
	* @param {Object} json - The JSON object.
	* @return {MemorySystem} A reference to this memory system.
	*/
	fromJSON( json ) {

		this.owner = json.owner; // uuid
		this.memorySpan = json.memorySpan;

		const recordsJSON = json.records;

		for ( let i = 0, l = recordsJSON.length; i < l; i ++ ) {

			const recordJSON = recordsJSON[ i ];
			const record = new MemoryRecord().fromJSON( recordJSON );

			this.records.push( record );

		}

		return this;

	}

	/**
	* Restores UUIDs with references to GameEntity objects.
	*
	* @param {Map<String,GameEntity>} entities - Maps game entities to UUIDs.
	* @return {MemorySystem} A reference to this memory system.
	*/
	resolveReferences( entities ) {

		this.owner = entities.get( this.owner ) || null;

		// records

		const records = this.records;

		for ( let i = 0, l = records.length; i < l; i ++ ) {

			const record = 	records[ i ];

			record.resolveReferences( entities );
			this.recordsMap.set( record.entity, record );

		}

		return this;

	}

}

export { MemorySystem };