class GameLocation{

	_isPublic = false;		get isPublic(){return this._isPublic;}
	_isOutdoors = false;	get isOutdoors(){return this._isOutdoors;}
	_isHome = false;		get isHome(){return this._isHome;}

	_region = '';			get region(){return this._region;}

	_passage = '';
	_passageArguments = [];	get passageArguments(){return this._passageArguments;}

	_passageQueue = [];
	_passageQueueMaxLength = 3;

	get locationIdentifier():LocationIdentifier{
		return new LocationIdentifier(this.passage,this.passageArguments);
	}

	set locationIdentifier(v:LocationIdentifier){
		this.update(v.passage,v.passageArguments);
	}

	get passage(){return this._passage;}
	set passage(v:string){
		if(typeof v != "string")
		{
			console.error('Location set to invalid passage:',v);
			return;
		}
		if(!Story.has(v)){
			console.error('Location set to non-existing passage:',v);
			return;
		}

		this._passageQueue.push(this._passage);
		while(this._passageQueue.length > this._passageQueueMaxLength) this._passageQueue.shift();

		this._passage = v;
		let passage_tags = tags(v);
		if(passage_tags.includes('indoors'))
			this._isOutdoors = false;
		else if(passage_tags.includes('outdoors'))
			this._isOutdoors = true;
		else
			console.warn("Location-passage does not contain an indoors- or outdoors-tag:",v);

		if(passage_tags.includes('private'))
			this._isPublic = false;
		else if(passage_tags.includes('public'))
			this._isPublic = true;
		else
			console.warn("Location-passage does not contain a private- or public-tag:",v);

		let homeTag = State.variables.housing.home;
		this._isHome = passage_tags.includes(homeTag);

		for(let tag of passage_tags){
			if(tag.startsWith('region_')){
				this._region = tag.replace('region_','');
				break;
			}
		}

	}

	
	/**
	 * A unique identifier for the place the player is at.
	 * @readonly
	 * @type {string}
	 */
	get here(){
		return this.passage;
	}

	get hasWardrobeAccess(){
		return this.isHome || this.tags.includes('wardrobe');
	}

	get tags(){
		return tags(this.passage);
	}

	#constantLocationData(locationId:string):LocationDefinition{
		return setup.getLocation(locationId);
	}


	_locationValues = {};
	get<T extends any>(locationId:string,field:string,def:T):T{
		const constData = this.#constantLocationData(locationId);
		const dynamicData = this._locationValues[locationId] ?? {};
		const combinedData = Object.assign({},constData,dynamicData);
		return combinedData[field] ?? def;
	}

	getHere(field:string,def:any=false){
		return this.get(this.here,field,def);
	}

	inc(locationId:string,field:string,v=1){
		this.set(locationId,field,this.get(locationId,field,0)+v);
	}

	set(locationId:string,field:string,v:any){
		this._locationValues[locationId] ??= {};
		this._locationValues[locationId][field] = v;
	}

	setHere(field:string,v:any){
		this.set(this.here,field,v);
	}

	
	/**
	 * Updates the current location and passage arguments.
	 * @param {string} locationId
	 * @param {Array<any>} [locationArguments=[]]
	 */
	update(locationId:string,locationArguments:Array<any>=[]){
		this.passage = locationId;
		this._passageArguments = locationArguments;
	}

	//#region Furniture
		get furniture(){
			return this.getHere('furniture',{});
		}

		hasFurniture(furnitureId:string){
			return (furnitureId in this.furniture);
		}

		removeFurniture(furnitureId:string){
			let furniture = this.furniture;
			delete furniture[furnitureId];
			this.setHere('furniture',furniture);
		}

		updateFurniture(furnitureId:string,updateObject:object){
			let furniture = this.furniture;
			furniture[furnitureId] = setup.mergeDeep(furniture[furnitureId] ?? {},updateObject);
			this.setHere('furniture',furniture);
		}
	//#endregion

	//#region OpenTime
		isOpen(locationId: string=undefined){
			locationId ??= this.passage;
			const openTimes:TimespanIdentifier = this.get(locationId,'openTimes',{});
			return State.variables.time.timespanIdentifierMatches(openTimes);
		}

		openingTimesStr(locationId: string=undefined){
			locationId ??= this.passage;
			const openTimes:TimespanIdentifier = this.get(locationId,'openTimes',{});
			return State.variables.time.timespanIdentifierToString(openTimes);
		}
	//#endregion

	//#region TagValues
		_tagValues={};
		getTagValue(tag:string,field:string,def:any=undefined){
			return (this._tagValues[tag]?.[field] ?? def);
		}

		getTagsValues(tags:string[],field:string,def:any=undefined):any[]{
			let result = [];
			for(const tag of tags){
				result.push(this.getTagValue(tag,field,def));
			}
			return result;
		}

		getTagsValuesHere(field:string,def:any=undefined):any[]{
			return this.getTagsValues(this.tags,field,def);
		}

		/*getTagsValuesAccumulated(tags:string[],field:string,acummulation:string):any{
			switch (acummulation) {
				case '||':
					let val_or = this.getTagsValues(tags,field,false);
					return val_or.includes(true);
				case '&&':
					let val_and = this.getTagsValues(tags,field,false);
					return !val_and.includes(false);
				case 'latest':
					const startdate = new Date(0);
					let val_latest:Date[] = this.getTagsValues(tags,field,startdate);
					return val_latest.reduce(
						(previous,current)=>new Date(
							Math.max(
								previous.getTime(),
								current.getTime()
							)
						)
						,startdate);
				default:
					break;
			}
		}*/

		setTagValue(tag:string,field:string,tagValue:any){
			this._tagValues[tag] ??= {};
			this._tagValues[tag][field] = tagValue;
		}
	//#endregion

	//#region System
		_init(location){
			Object.keys(location).forEach(function (pn) {
				this[pn] = clone(location[pn]);
			}, this);

			return this;
		}

		clone = function () {
			return (new setup.Location())._init(this);
		};

		toJSON = function () {
			var ownData = {};
			Object.keys(this).forEach(function (pn) {
				if(typeof this[pn] !== "function")
					ownData[pn] = clone(this[pn]);
			}, this);
			return JSON.reviveWrapper('(new setup.Location())._init($ReviveData$)', ownData);
		};
	//#endregion
}

setup.Location = GameLocation;
