import { IEventAggregationService } from "@studyportals/event-aggregation-service-interface";
import { ITokenBasedSessionService } from '@studyportals/student-interfaces';
import { IStudent, StudentField } from '@studyportals/studentdomain';
import { Actor, AnonymousStudentStateChanged } from '../../interfaces';
import { AnonymousStudentEventBroadcaster } from '../infrastructure/anonymous-student-event-broadcaster';
import { OfflineToOnlineSynchronizationService } from './services/offline-to-online-synchronization-service';
import { OfflineStudentRepositoryState } from './states/offline-student-repository-state';
import { OnlineStudentRepositoryState } from './states/online-student-repository-state';
import { PendingStudentRepositoryState } from './states/pending-student-repository-state';
import { StudentRepositoryState } from './states/student-repository-state';

export class StudentRepository {

	private state: StudentRepositoryState;
	public readonly pendingState: StudentRepositoryState;
	public readonly offlineState: OfflineStudentRepositoryState;
	public readonly onlineState: OnlineStudentRepositoryState;
	public readonly offlineToOnlineSynchronizationService: OfflineToOnlineSynchronizationService;
	public readonly anonymousStudentEventBroadcaster: AnonymousStudentEventBroadcaster;

	constructor(eventAggregationService: IEventAggregationService, sessionService: ITokenBasedSessionService) {
		this.anonymousStudentEventBroadcaster = this.createAnonymousStudentEventBroadcaster(eventAggregationService);
		this.pendingState = this.createPendingStudentRepositoryState(eventAggregationService, sessionService);
		this.offlineState = this.createOfflineStudentRepositoryState(eventAggregationService, sessionService);
		this.onlineState = this.createOnlineStudentRepositoryState(eventAggregationService, sessionService);
		this.offlineToOnlineSynchronizationService = this.createOfflineToOnlineSynchronizationService();

		this.state = this.pendingState;
	}

	public initialize(): void {
		this.offlineState.initialize();
		this.onlineState.initialize();
		this.pendingState.initialize();
	}

	public async setStudentData(studentData: IStudent, actor: Actor): Promise<void> {
		await this.state.setStudentData(studentData, actor);
	}

	public async getStudentData(studentFields: StudentField[]): Promise<IStudent> {
		const studentData = await this.state.getStudentData(studentFields);
		const studentDataKeys = Object.keys(studentData);

		studentFields.forEach((field) => {
			if (!studentDataKeys.includes(field)) {
				studentData[field] = undefined;
			}
		});

		return studentData;
	}

	public async getStudentDataCompleteness(studentFields: StudentField[]): Promise<number> {
		const studentData = await this.getStudentData(studentFields);

		const completeFields = studentFields.filter((field) => {
			return studentData[field] !== undefined;
		});

		return completeFields.length / studentFields.length;
	}

	public addToCollection(type: StudentField, items: any[]): Promise<void> {
		return this.state.addToCollection(type, items);
	}

	public removeFromCollection(type: StudentField, items: any[]): Promise<void> {
		return this.state.removeFromCollection(type, items);
	}

	public async setGPA(grade_type: string, grade_value: any): Promise<void> {

		const data = {
			gpa: {
				[grade_type]: grade_value,
				current_type: grade_type
			}
		};

		return this.setStudentData(data, Actor.USER);
	}

	public async setName(name: string): Promise<void> {
		const firstSpace = name.indexOf(' ', 0);
		let first_name: string | undefined;
		let last_name: string | undefined;

		if (firstSpace !== -1) {
			first_name = name.substring(0, firstSpace);
			last_name = name.substring(firstSpace).trim();
		} else {
			first_name = name;
			last_name = undefined;
		}

		const data = {
			name,
			first_name,
			last_name
		};

		return this.setStudentData(data, Actor.USER);
	}

	public updateState(state: StudentRepositoryState): void {
		const oldState = this.state;

		this.state = state;

		const event = new AnonymousStudentStateChanged(new Date(), oldState.stateType, state.stateType);
		this.anonymousStudentEventBroadcaster.broadcastStateChangedEvent(event);
	}

	private createPendingStudentRepositoryState(eventAggregationService: IEventAggregationService, sessionService: ITokenBasedSessionService): StudentRepositoryState {
		return new PendingStudentRepositoryState(
			eventAggregationService,
			sessionService,
			this
		);
	}

	private createOnlineStudentRepositoryState(eventAggregationService: IEventAggregationService, sessionService: ITokenBasedSessionService): OnlineStudentRepositoryState {
		return new OnlineStudentRepositoryState(
			eventAggregationService,
			sessionService,
			this
		);
	}

	private createOfflineStudentRepositoryState(eventAggregationService: IEventAggregationService, sessionService: ITokenBasedSessionService): OfflineStudentRepositoryState {
		return new OfflineStudentRepositoryState(
			eventAggregationService,
			sessionService,
			this
		);
	}

	private createOfflineToOnlineSynchronizationService(): OfflineToOnlineSynchronizationService {
		return new OfflineToOnlineSynchronizationService(
			this.offlineState,
			this.onlineState
		);
	}

	private createAnonymousStudentEventBroadcaster(eventAggregationService: IEventAggregationService): AnonymousStudentEventBroadcaster {
		return new AnonymousStudentEventBroadcaster(eventAggregationService);
	}
}
