import { Application } from 'express';
import * as express from 'express';
import { Server as SocketIOServer } from 'socket.io';
import { createServer, Server as HTTPServer } from 'http';
import { Main } from '../Main';
import { DashboardRouter } from './DashboardRouter';
import { join } from 'path';
import { CalibrationRouter } from './CalibrationRouter';
import { delay } from '../Utils';
import { ControlRouter } from './ControlRouter';
import { StatusType } from '../Status';

const PREFIX = '[WebServer]';
export class WebServer {
	private _Main: Main;

	Control: ControlRouter;
	Dashboard: DashboardRouter;
	Calibration: CalibrationRouter;

	httpServer: HTTPServer;
	app: Application;
	socket: SocketIOServer;

	constructor(Main: Main) {
		this._Main = Main;
		this.Dashboard = new DashboardRouter(this._Main);
		this.Calibration = new CalibrationRouter(this._Main);
		this.Control = new ControlRouter(this._Main);

		this.prepare();
	}

	private prepare() {
		this.app = express();
		this.httpServer = createServer(this.app);
		this.socket = new SocketIOServer(this.httpServer);

		this.app.use(
			express.static(
				join(__filename, '..', '..', '..', 'frontend', 'static'),
			),
		);

		this.app.use(this.Dashboard.Router);
		this.app.use(this.Control.Router);
		this.app.use(this.Calibration.Router);

		this.socket.on('connection', (socket) => {
			socket.emit('status', this._Main.Status.getState());
			socket.emit('timer', this._Main.Timer.getState());
			socket.emit(
				'cameraRunnerState',
				this._Main.CameraRunner.getState(),
			);
			socket.emit('unityRunnerState', this._Main.UnityRunner.getStatus());
			socket.emit(
				'unityWebSocketState',
				this._Main.UnityWebSocket.getState(),
			);
			socket.emit(
				'supportNumber',
				this._Main.Config.support.telephone.replace('+', ''),
			);

			socket.on(
				'restartInstallation',
				(
					callback: (response: {
						succeed: boolean;
						message?: string;
					}) => void,
				) => {
					if (this._Main.CameraRunner.state !== 'CONNECTED')
						return callback({
							succeed: false,
							message:
								'Cannot reboot camera runner because it is not connected.',
						});

					this._Main.restart();
					callback({ succeed: true });
				},
			);

			socket.on(
				'shutdownInstallation',
				async (
					callback: (response: {
						succeed: boolean;
						message?: string;
					}) => void,
				) => {
					await delay(1000);
					callback({ succeed: true });
					this._Main.shutdown();
				},
			);
			socket.on('cameraRunner', (command: string, ...args: any[]) =>
				this._Main.CameraRunner.handle(command, ...args),
			);
			socket.on('unityRunner', (command: string, ...args: any[]) =>
				this._Main.UnityRunner.handle(command, ...args),
			);
			socket.on('unityWebSocket', (command: string, ...args: any[]) =>
				this._Main.UnityWebSocket.handle(command, ...args),
			);

			socket.on(
				'status',
				(
					action: 'fullreboot' | 'reboot' | 'start' | 'stop',
					type: StatusType,
				) => {
					switch (action) {
						case 'fullreboot':
							return this._Main.restart();
						case 'reboot':
							return this._Main.Status.reboot(type);
						case 'start':
							return this._Main.Status.start(type);
						case 'stop':
							return this._Main.Status.stop(type);
					}
				},
			);
			socket.on(
				'setTimerStart',
				(data: { hour: number; minute: number }) => {
					this._Main.Timer.setStart(data);
				},
			);
			socket.on(
				'setTimerEnd',
				(data: { hour: number; minute: number }) => {
					this._Main.Timer.setEnd(data);
				},
			);
		});
	}

	listen() {
		return new Promise<void>((resolve) => {
			const port = this._Main.Config.webServer.port;
			this.httpServer.listen(port, () => {
				console.log(
					PREFIX,
					`Listening on port http://127.0.0.1:${port}`,
				);
				resolve();
			});
		});
	}
}
