import { ipcRenderer, remote, shell } from 'electron';

import { Observable, Subject } from 'rxjs';

import {
  NativeService,
  WindowState,
  NotificationRequest,
  WindowIdle
} from '@ucap-webmessenger/native';
import { share } from 'rxjs/operators';
import {
  NotificationChannel,
  UpdaterChannel,
  FileChannel,
  WindowStateChannel,
  IdleStateChannel,
  ChatChannel,
  MessengerChannel
} from '../types/channel.type';
import { Injectable } from '@angular/core';
import { TranslateLoaderService } from '../translate/electron-loader';
import { TranslateLoader } from '@ngx-translate/core';
import { StatusCode } from '@ucap-webmessenger/core';

@Injectable({
  providedIn: 'root'
})
export class ElectronNativeService implements NativeService {
  private ipcRenderer: typeof ipcRenderer;
  private remote: typeof remote;
  private shell: typeof shell;

  private logoutSubject: Subject<void> | null = null;
  private logout$: Observable<void> | null = null;

  private changeStatusSubject: Subject<StatusCode> | null = null;
  private changeStatus$: Observable<StatusCode> | null = null;

  private showSettingSubject: Subject<void> | null = null;
  private showSetting$: Observable<void> | null = null;

  private windowStateChangedSubject: Subject<WindowState> | null = null;
  private windowStateChanged$: Observable<WindowState> | null = null;

  private idleStateChangedSubject: Subject<WindowIdle> | null = null;
  private idleStateChanged$: Observable<WindowIdle> | null = null;

  private chatOpenRoomSubject: Subject<string> | null = null;
  private chatOpenRoom$: Observable<string> | null = null;

  postAppInit(): void {}

  logout(): Observable<void> {
    if (!this.logoutSubject) {
      this.logoutSubject = new Subject<void>();
      this.logout$ = this.logoutSubject.asObservable().pipe(share());
    }

    this.ipcRenderer.on(MessengerChannel.Logout, (event: any) => {
      this.logoutSubject.next();
    });
    return this.logout$;
  }

  changeStatus(): Observable<StatusCode> {
    if (!this.changeStatusSubject) {
      this.changeStatusSubject = new Subject<StatusCode>();
      this.changeStatus$ = this.changeStatusSubject
        .asObservable()
        .pipe(share());
    }

    this.ipcRenderer.on(
      MessengerChannel.ChangeStatus,
      (event: any, statusCode: StatusCode) => {
        this.changeStatusSubject.next(statusCode);
      }
    );
    return this.changeStatus$;
  }

  showSetting(): Observable<void> {
    if (!this.showSettingSubject) {
      this.showSettingSubject = new Subject<void>();
      this.showSetting$ = this.showSettingSubject.asObservable().pipe(share());
    }

    this.ipcRenderer.on(MessengerChannel.ShowSetting, (event: any) => {
      this.showSettingSubject.next();
    });
    return this.showSetting$;
  }

  notify(noti: NotificationRequest): void {
    this.ipcRenderer.send(NotificationChannel.Notify, noti);
  }

  closeAllNotify(): void {
    this.ipcRenderer.send(NotificationChannel.CloseAllNotify);
  }

  checkForUpdates(): Promise<boolean> {
    return new Promise<boolean>((resolve, reject) => {
      try {
        resolve(this.ipcRenderer.sendSync(UpdaterChannel.Check));
      } catch (error) {
        reject(error);
      }
    });
  }

  showImageViewer(): void {
    this.ipcRenderer.send(FileChannel.ShowImageViewer);
  }

  readFile(path: string): Promise<Buffer> {
    return new Promise<Buffer>((resolve, reject) => {
      try {
        resolve(this.ipcRenderer.sendSync(FileChannel.ReadFile, path));
      } catch (error) {
        reject(error);
      }
    });
  }

  saveFile(
    buffer: Buffer,
    fileName: string,
    mimeType: string,
    path?: string
  ): Promise<string> {
    return new Promise<string>((resolve, reject) => {
      try {
        resolve(
          this.ipcRenderer.sendSync(
            FileChannel.SaveFile,
            buffer,
            fileName,
            mimeType,
            path
          )
        );
      } catch (error) {
        reject(error);
      }
    });
  }

  windowStateChanged(): Observable<WindowState> {
    if (!this.windowStateChangedSubject) {
      this.windowStateChangedSubject = new Subject<WindowState>();
      this.windowStateChanged$ = this.windowStateChangedSubject
        .asObservable()
        .pipe(share());
    }

    this.ipcRenderer.on(
      WindowStateChannel.Changed,
      (event: any, windowState: WindowState) => {
        this.windowStateChangedSubject.next(windowState);
      }
    );
    return this.windowStateChanged$;
  }

  windowClose(): void {
    const currentWindow = this.remote.getCurrentWindow();
    if (!currentWindow) {
      return;
    }

    currentWindow.close();
  }

  windowMinimize(): void {
    const currentWindow = this.remote.getCurrentWindow();
    if (!currentWindow) {
      return;
    }

    currentWindow.minimize();
  }

  windowMaximize(): void {
    const currentWindow = this.remote.getCurrentWindow();
    if (!currentWindow) {
      return;
    }

    if (currentWindow.isMaximized()) {
      currentWindow.unmaximize();
    } else {
      currentWindow.maximize();
    }
  }

  idleStateChanged(): Observable<WindowIdle> {
    if (!this.idleStateChangedSubject) {
      this.idleStateChangedSubject = new Subject<WindowIdle>();
      this.idleStateChanged$ = this.idleStateChangedSubject
        .asObservable()
        .pipe(share());
    }

    this.ipcRenderer.send(IdleStateChannel.StartCheck);

    this.ipcRenderer.on(
      IdleStateChannel.Changed,
      (event: any, idleState: WindowIdle) => {
        this.idleStateChangedSubject.next(idleState);
      }
    );
    return this.idleStateChanged$;
  }

  chatOpenRoom(): Observable<string> {
    if (!this.chatOpenRoomSubject) {
      this.chatOpenRoomSubject = new Subject<WindowIdle>();
      this.chatOpenRoom$ = this.chatOpenRoomSubject
        .asObservable()
        .pipe(share());
    }

    this.ipcRenderer.on(ChatChannel.OpenRoom, (event: any, roomSeq: string) => {
      this.chatOpenRoomSubject.next(roomSeq);
    });
    return this.chatOpenRoom$;
  }

  getTranslateLoader(prefix?: string, suffix?: string): TranslateLoader {
    return new TranslateLoaderService(this, prefix, suffix);
  }

  openDefaultBrowser(url: string): void {
    this.shell.openExternal(url);
  }

  get isElectron() {
    return window && (window as any).process && (window as any).process.type;
  }

  constructor() {
    if (this.isElectron) {
      this.ipcRenderer = (window as any).require('electron').ipcRenderer;
      this.remote = (window as any).require('electron').remote;
      this.shell = (window as any).require('electron').shell;
    }
  }
}
