class Jobs{

	constructor(){}

	_jobs = {};

	get activeJobs(){
		return Object.fromEntries(
			Object.entries(this._jobs).
			filter(([jobId,jobData]) => jobData.active).
			map(
				([jobId,jobData]) => 
				([jobId,this.jobData(jobId)])
			)
		);
	}

	get(jobId,field,def=0){
		return (this.jobData(jobId)[field] ?? def);
	}

	inc(jobId,field,inc=1){
		this.set(jobId,field,this.get(jobId,field)+inc);
	}

	isActive(jobId){
		return this.get(jobId,'active',false);
	}

	
	/**
	 * @param {string} jobId
	 * @param {Date} date
	 * @returns {boolean}
	 */
	isWorkDay(jobId,date){
		//const time = State.variables.time;
		const weekday = date.getUTCDay() || 7;
		const jobData = this.jobData(jobId);

		if(jobData.starts.getTime() > date.getTime())
			return false;

		return jobData.workdays.includes(weekday);
	}

	isWorkPrepareTime(jobId){
		const jobData = this.jobData(jobId);

		
		/**
		 * @type {GameTime}
		 */
		const time = State.variables.time;

		if(!this.isWorkDay(jobId,time.now))
			return false;
		
		const timeDifferenceToStart = time.timeDifference(jobData.worktimes[0],jobData.worktimes[1]);
		if(timeDifferenceToStart < 0 && timeDifferenceToStart >= -60)
			return true;
		return false;
	}

	jobData(jobId){
		return Object.assign({vacation:0},this.jobStaticData(jobId),this._jobs[jobId] ?? {});
	}

	jobStaticData(jobId){
		return Object.assign({
			title: 'Title missing',
			pay: 100,
			workdays: [1,2,3,4,5,6,7],
			worktimes: [10,11]
		},setup.jobs[jobId] ?? {});
	}

	paymentForPeriod(jobId){
		const jobData = this.jobData(jobId);

		switch(jobData.payInterval){
			case 'week':
				return jobData.pay * 7;
		}
	}
	
	/**
	 * Returns the daystart of the next payment
	 * @date 9/10/2023 - 1:15:04 PM
	 *
	 * @param {*} jobId
	 * @returns {number} daystart of the next payment
	 */
	paymentNext(jobId,offset=0){
		const jobData = this.jobData(jobId);
		const time = State.variables.time;

		switch(jobData.payInterval){
			case 'week':
				if(offset>0)
					return this.paymentNext(jobId,offset-1) + 7;
				let daystartOfPayment = time.daystartNextAtWeekday(jobData.payDay);
				return daystartOfPayment;
		}
	}

	scheduledForDay(day=null){
		/**
		 * @type {GameTime}
		 */
		const time = State.variables.time;
		day ??= time.now;
		if(typeof day == "number")
			day = time.daystartToDate(day);

		const activeJobs = this.activeJobs;
		
		return Object.fromEntries(
			Object.entries(activeJobs).
			filter(([jobId,jobData]) => this.isWorkDay(jobId,day))
		);
	}

	set(jobId,field,v){
		this._jobs[jobId] ??= {};
		this._jobs[jobId][field] = v;
	}


	start(jobId){
		const finances = State.variables.finances;
		const time = State.variables.time;
		const jobData = this.jobData(jobId);

		this._jobs[jobId] = {
			active: true,
			payments:[],
			starts: time.tomorrow,
			count:0,
			vacation:0
		};

		// First payment

		let daystartOfFirstPayment = this.paymentNext(jobId);
		let daystartDifference = daystartOfFirstPayment - time.daystart -1; //-1 because you don't start today
		let firstPaymentMoney = daystartDifference * jobData.pay;
		let firstPaymentId = `${jobId}_initial`;

		finances.bankScheduledTransactionsRegister(firstPaymentId,firstPaymentMoney,'once',undefined,daystartOfFirstPayment,`Salary (${jobData.title}) for ${daystartDifference} day(s) of work`);
		this._jobs[jobId].payments.push(firstPaymentId);

		// Regular payment
		let daystartOfRegularPayment = this.paymentNext(jobId,1);
		let regularPayment = this.paymentForPeriod(jobId);
		let regularPaymentId = `${jobId}_regular`;
		finances.bankScheduledTransactionsRegister(regularPaymentId,regularPayment,'nth of week',undefined,daystartOfRegularPayment,`Salary (${jobData.title}) for 1 week of work`);
		this._jobs[jobId].payments.push(regularPaymentId);

		// Schedule Regular Event
		let startTime = new Date(this.workStartTime(jobId,1).getTime()-3600000);
		this._jobs[jobId].workEvent = time.addTimedEventByDate('work',startTime,60,{jobId:jobId});
	}

	stop(jobId){
		const jobData = this.jobData(jobId);
		const finances = State.variables.finances;
		const paymentIds = [`${jobId}_initial`,`${jobId}_regular`];

		for(let paymentId of paymentIds){
			finances.bankScheduledTransactionsUnregister(paymentId);
		}

		const time = State.variables.time;
		time.unscheduleTimedEventById(jobData.workEvent);

		this.set(jobId,'active',false);
	}

	work(jobId){
		/** @type {GameTime} */
		const time = State.variables.time;
		const jobData = this.jobData(jobId);

		// Reschedule Work-Miss-Event
		let nextStartTime = new Date(this.workStartTime(jobId,1).getTime()-3600000);
		time.rescheduleTimedEventById(jobData.workEvent,nextStartTime);

		//Times worked
		const count = (jobData.count ?? 0) + 1;
		this.set(jobId,'count',count);

		//Vacation Update
		const vacationGainInterval	= jobData.vacationInterval[0];
		const vacationGainAmount	= jobData.vacationInterval[1];
		if(count % vacationGainInterval == 0){
			this.inc(jobId,'vacation',vacationGainAmount);
		}

		// lastWorkday
		this.set(jobId,'lastWorkday',time.daystart);
	}
	
	/**
	 * Description placeholder
	 * @date 9/10/2023 - 7:14:50 PM
	 *
	 * @param {*} location
	 * @returns {Array} an array of the jobIds that are reachable with a work-action right now
	 */
	workActionsByLocation(location){
		var results = [];

		const activeJobs = this.activeJobs;
		for (const [jobId,jobData] of Object.entries(activeJobs)) {
			if(!this.isWorkPrepareTime(jobId))
				continue;
			if(jobData.workLocations.includes(location))
				results.push(jobId);
		}

		return results;
	}

	workedToday(jobId){
		/** @type {GameTime} */
		const time = State.variables.time;
		return (this.get(jobId,'lastWorkday') == time.daystart);
	}


	/**
	 * Run by $time if a work time has been missed.
	 * @date 9/10/2023 - 2:33:01 PM
	 *
	 * @param {string} jobId
	 * @param {Date} missTime the time the boss realizes that you won't show up
	 */
	workMissed(jobId,missTime){
		console.log('Work missed',jobId,missTime);

		
		/**
		 * @type {Phone}
		 */
		const phone = State.variables.phone;

		const jobData = this.jobData(jobId);
		var vacationLeft = jobData.vacation;
		if(vacationLeft > 0){
			this.set(jobId,'vacation',vacationLeft-1);
			//TODO: Send message
			return;
		}
		
		phone.smsCreate(jobData.boss,missTime,'job_sms_fired',{jobId:jobId});

		console.log('Work fired',jobId);
		this.stop(jobId);
		this.inc(jobId,'fired');
	}

	workStartTime(jobId,dayOffset=0){
		const jobData = this.jobData(jobId);
		
		/**
		 * Description placeholder
		 * @date 9/10/2023 - 12:04:16 PM
		 *
		 * @type {Array}
		 */
		const workdays = jobData.workdays;

		const time = State.variables.time;
		const now = time.now;

		const oneDay = 86400000;

		let maxIterations = 100;
		while(maxIterations-- > 0){
			let searchTime = new Date(now.getTime() + dayOffset * oneDay);
			if(workdays.includes(searchTime.getUTCDay() || 7)){
				//TODO: Check holidays
				searchTime.setUTCHours(jobData.worktimes[0],jobData.worktimes[1]);
				return searchTime;
			}
		}
	}

	_init(jobs){
		Object.keys(jobs).forEach(function (pn) {
			this[pn] = clone(jobs[pn]);
		}, this);

		return this;
	}

	clone = function () {
		return (new setup.Jobs())._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.Jobs())._init($ReviveData$)', ownData);
	};
}

setup.Jobs = Jobs;