import {EVENT_BOOKING, EVENT_REGISTER, RSSchedule} from "./util.js";
import {Icon, INFOPANEL_STATE_ACTIVITIES} from "./page.js";
import {STUDY_SESSION_MODE_INSTANT} from "./roomscanner.js";

/**
 * Holds state for the mode the Room Scanner client is using.
 * Example - 'Registration' mode - the scanner is picking up student registrations
 *
 * Implemented as a finite-state machine (FSM) using the State pattern. This is the 'Context' class.
 * [State pattern](https://en.wikipedia.org/wiki/State_pattern)
 *
 * "Mode" here is synonymous with "State".
 * @see {Mode}
 */
export class ModeFSM {

	constructor(roomScanner, page) {
		/**
		 * @type {RoomScanner} The RS client itself
		 */
		this._roomScanner = roomScanner;
		/**
		 * @type {Page}
		 */
		this._page = page;

		/**
		 * Will be `true` if a Mode transition alters the Page InfoPanel.
		 * Always made `false` at the start of each transition.
		 * @type {boolean}
		 * @private
		 */
		this._infoPanelUpdated = false;

		/**
		 * Current RS mode.
		 * Defaults to "Activity" i.e .
		 * @type {Mode}
		 * @private
		 */
		this._mode = this.getDefaultMode();
	}

	getDefaultMode() {
		if (this._roomScanner.sessionData.studySessionMode === STUDY_SESSION_MODE_INSTANT) {
			return new InstantStudySessionsMode(this);
		} else {
			return new ActivityMode(this);
		}
	}

	refresh() {
		// Set to false for this transition.
		this._infoPanelUpdated = false;
		// Transition state, possibility looping back to the same state.
		// This may set `infoPanelUpdated` to `true`.
		this._mode = this._mode.refresh();
	}

	/**
	 * If this is true, then the RoomScanner client will not need to manually update the infopanel again.
	 * @return {boolean}
	 */
	get infoPanelUpdated() {
		return this._infoPanelUpdated;
	}
	set infoPanelUpdated(updated) {
		this._infoPanelUpdated = updated;
	}

	get mode() {
		return this._mode;
	}

	// Data about the Room Scanner itself, used by the Mode states for determining the next state.

	get page() {
		return this._page;
	}
	get room() {
		return this._roomScanner.getRoom();
	}
	get roomSchedule() {
		return this._roomScanner.getRoomSchedule();
	}

	/**
	 * Get next room scanner Event. Events are considered "started" in advance (e.g. 10 minutes) if the room
	 * is otherwise free/unused during that time window.
	 *
	 * @return {(Event|null)}
	 */
	getNextScheduleEvent() {
		// Allow use of `startRegistersMsInAdvance` for Register and RoomSessions.
		// `startRegistersMsInAdvance` will allow for students to register themselves in-advance (as
		// soon as the "Activity starts in X mins" indicator is visible).
		let attendanceEvent = this.roomSchedule.getNextEventWithinTime(
			this._roomScanner.getGlobalConfig().startRegistersMsInAdvance
		);

		// If not either a Register or RoomSession, then do not change mode earlier than normal.
		if (attendanceEvent && RSSchedule.isAttendanceEvent(attendanceEvent)) {
			return attendanceEvent;
		} else {
			return this.roomSchedule.getNextEvent(true);
		}
	}

	/**
	 * If we're currently in a 'event-type' mode (e.g. registration, booking) then return the
	 * associated Event object.
	 * @return {(null|RSEvent)} NULL if we're not in an EventMode.
	 */
	getModeEvent() {
		if (this.mode instanceof EventMode) {
			return this.mode.event;
		}

		return null;
	}
}


/**
 * Represents a state in the "Room Scanner Mode" FSM.
 *
 * States have the following transitions:
 * - `refresh`: Check Room Scanner scheduling to see if we need to change state (e.g. switch
 *              to a registration mode, which automatically activates the page).
 *              If not, then this transition will loop back onto itself.
 * @abstract
 */
export class Mode {

	/**
	 * @param {ModeFSM} context
	 */
	constructor(context) {
		/**
		 * @type {ModeFSM}
		 * @protected
		 */
		this._context = context;
	}

	/**
	 * @public
	 */
	refresh() { /* Must be implemented */ }

	/**
	 * Should be called if this alters the Page InfoPanel (i.e. calls `updatePageContent`) during a
	 * transition (in or out).
	 * @protected
	 */
	_setInfoPanelUpdated() {
		// ModeFSM will record and use this for each transition:
		this._context.infoPanelUpdated = true;
	}

	/**
	 * Render a default (generic) view for the info panel.
	 * Should just be a display of user activities, with a generic headline.
	 * @protected
	 */
	_setDefaultPageAppearance() {

		// Update the screen in a fade transition
		this._context.page.updatePageContent(INFOPANEL_STATE_ACTIVITIES, () => {
			// Update the display of activities.
			this._context.page.setActivities(this._context.roomSchedule);
			// Have the scanner page reflect the current mode.
			this._context.page.setScannerMode(this);
		});
	}
}

/**
 * Represents a default/base mode for the Room Scanner.
 * This is the mode the scanner should return to when it is finished with a time-sensitive mode e.g. an EventMode.
 */
export class BaseMode extends Mode {

	/**
	 * Default `refresh` transition. Checks for current events in progress.
	 * @public
	 * @return {Mode}
	 */
	refresh() {
		let nextEvent = this._context.getNextScheduleEvent();

		// No upcoming events - keep showing activities.
		if (!nextEvent) {
			return this;
		}

		switch(nextEvent.type) {
			case EVENT_REGISTER:
				return new RegistrationMode(this._context, nextEvent);

			case EVENT_BOOKING:
				return new BookingMode(this._context, nextEvent);
		}

		return this;
	}
}

/**
 * The scanner is just displaying the current room schedule, and accepting user scans.
 */
export class ActivityMode extends BaseMode {

	constructor(context) {
		super(context);
		this._activityScreenHandler();
	}

	_activityScreenHandler() {
		let page = this._context.page;

		// Allow the screen to deactivate itself once more:
		page.screenSaver.enabled = true;

		page.setDefaultIcon(null); // Switch back to the default icon

		// Update the screen in a fade transition
		this._setDefaultPageAppearance();

		this._setInfoPanelUpdated();
	}
}

/**
 * Mode representing a dedicated Study Session scanner.
 * While in this mode, study sessions will only require 1 scan to log in/out.
 * This is a configurable option when starting a Room Scanner.
 */
export class InstantStudySessionsMode extends BaseMode {

	constructor(context) {
		super(context);
		this._studySessionScreenHandler();
	}

	_studySessionScreenHandler() {
		let page = this._context.page;

		// Allow the screen to deactivate itself once more:
		page.screenSaver.enabled = true;

		// Use a custom icon to differentiate this BaseMode from ActivityMode.
		page.setDefaultIcon(Icon.GRADUATION_CAP);

		this._setDefaultPageAppearance();
		this._setInfoPanelUpdated();
	}
}

/**
 * Active when there is a scheduled event going on right now in this room.
 */
export class EventMode extends Mode {

	/**
	 * @param {ModeFSM} context
	 * @param {RSEvent} event
	 */
	constructor(context, event) {
		super(context);
		/**
		 * @type {RSEvent}
		 * @protected
		 */
		this._event = event;
	}

	get event() {
		return this._event;
	}
}

/**
 * Active when the room is in a lesson and the scanner is accepting student registration.
 */
export class RegistrationMode extends EventMode {

	constructor(context, register) {
		super(context, register);
		this._regScreenHandler();
	}

	refresh() {
		let nextEvent = this._context.getNextScheduleEvent();

		// If the event has finished, return to the default State:
		if (!nextEvent) {
			return this._context.getDefaultMode();
		}

		switch(nextEvent.type) {
			case EVENT_REGISTER:
				// If a new register is coming up (but a register nonetheless):
				if (nextEvent.id !== this._event.id) {
					// Update register event
					this._event = nextEvent;
					// Refresh the screen with new register info:
					this._regScreenHandler();
				}
				return this;

			case EVENT_BOOKING:
				return new BookingMode(this._context, nextEvent);
		}

		return this;
	}

	/**
	 * @private
	 */
	_regScreenHandler() {
		let page = this._context.page;

		// Stop the page from automatically disabling itself.
		page.screenSaver.enabled = false;

		// Show an icon for registration
		page.setDefaultIcon(Icon.PENCIL);

		// Ensure the page is active.
		page.setActive();

		// Update the screen in a fade transition
		this._setDefaultPageAppearance();
		this._setInfoPanelUpdated();
	}
}

/**
 * Active when there is a booking going on in this room right now.
 */
export class BookingMode extends EventMode {

	constructor(context, booking) {
		super(context, booking);
		this._bookingScreenHandler();
	}

	refresh() {
		let nextEvent = this._context.getNextScheduleEvent();

		// If the event has finished, return to the default State:
		if (!nextEvent) {
			return this._context.getDefaultMode();
		}

		switch(nextEvent.type) {
			case EVENT_BOOKING:
				// If a new booking is coming up:
				if (nextEvent.id !== this._event.id) {
					this._event = nextEvent;
					// Refresh the screen with new booking info:
					this._bookingScreenHandler();
				}
				return this;

			case EVENT_REGISTER:
				return new RegistrationMode(this._context, nextEvent);
		}

		return this;
	}

	isRoomSession() {
		return RSSchedule.isRoomSession(this.event);
	}

	/**
	 * @private
	 */
	_bookingScreenHandler() {
		let page = this._context.page;
		let isRoomSession = this.isRoomSession();

		// Stop the page from automatically disabling itself.
		page.screenSaver.enabled = false;

		page.setDefaultIcon( isRoomSession ? Icon.ELECTRIC_LIGHT_BULB : Icon.DOOR );

		// Ensure the page is active.
		page.setActive();

		// Update the screen in a fade transition
		this._setDefaultPageAppearance();
		this._setInfoPanelUpdated();
	}
}