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

import { Observable, Subject } from 'rxjs';

import {
  NativeService,
  WindowState,
  NotificationRequest,
  WindowIdle,
  UpdateInfo,
  UpdateCheckConfig,
  NativePathName,
  NativeType
} from '@ucap-webmessenger/native';
import { share } from 'rxjs/operators';
import {
  NotificationChannel,
  UpdaterChannel,
  FileChannel,
  WindowStateChannel,
  IdleStateChannel,
  ChatChannel,
  MessengerChannel,
  MessageChannel,
  ProcessChannel,
  AppChannel
} 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 webFrame: typeof webFrame;
  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;

  private msgOpenMessageSubject: Subject<string> | null = null;
  private msgOpenMessage$: Observable<string> | null = null;

  private backgroundCheckForUpdatesSubject: Subject<UpdateInfo> | null = null;
  private backgroundCheckForUpdates$: Observable<UpdateInfo> | null = null;

  type(): NativeType {
    return NativeType.Electron;
  }

  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$;
  }

  getNetworkInfo(): Promise<any> {
    return new Promise<string>((resolve, reject) => {
      try {
        resolve(this.ipcRenderer.sendSync(MessengerChannel.GetNetworkInfo));
      } catch (error) {
        reject(error);
      }
    });
  }

  getVersionInfo(): Promise<string> {
    return new Promise<string>((resolve, reject) => {
      try {
        resolve(this.ipcRenderer.sendSync(MessengerChannel.GetVersionInfo));
      } catch (error) {
        reject(error);
      }
    });
  }

  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$;
  }

  changeAutoLaunch(autoLaunch: boolean): Promise<boolean> {
    return new Promise<boolean>((resolve, reject) => {
      try {
        resolve(
          this.ipcRenderer.sendSync(
            MessengerChannel.ChangeAutoLaunch,
            autoLaunch
          )
        );
      } catch (error) {
        reject(error);
      }
    });
  }

  changeStartupHideWindow(startupHideWindow: boolean): Promise<boolean> {
    return new Promise<boolean>((resolve, reject) => {
      try {
        resolve(
          this.ipcRenderer.sendSync(
            MessengerChannel.ChangeStartupHideWindow,
            startupHideWindow
          )
        );
      } catch (error) {
        reject(error);
      }
    });
  }

  changeDownloadPath(downloadPath: string): Promise<string> {
    return new Promise<string>((resolve, reject) => {
      try {
        resolve(
          this.ipcRenderer.sendSync(
            MessengerChannel.ChangeDownloadPath,
            downloadPath
          )
        );
      } catch (error) {
        reject(error);
      }
    });
  }

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

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

  checkForUpdates(currentVersion: string): void {
    this.ipcRenderer.send(UpdaterChannel.Apply, currentVersion);
  }

  checkForInstantUpdates(config: UpdateCheckConfig): Observable<UpdateInfo> {
    if (!this.backgroundCheckForUpdatesSubject) {
      this.backgroundCheckForUpdatesSubject = new Subject<UpdateInfo>();
      this.backgroundCheckForUpdates$ = this.backgroundCheckForUpdatesSubject
        .asObservable()
        .pipe(share());
    }

    this.ipcRenderer.send(UpdaterChannel.StartCheckInstant, config);

    this.ipcRenderer.on(
      UpdaterChannel.ExistInstant,
      (event: any, updateInfo: UpdateInfo) => {
        this.backgroundCheckForUpdatesSubject.next(updateInfo);
      }
    );
    return this.backgroundCheckForUpdates$;
  }

  applyInstantUpdates(): void {
    this.ipcRenderer.send(UpdaterChannel.ApplyInstant);
  }

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

  readFile(path: string): Promise<Buffer> {
    return new Promise<Buffer>((resolve, reject) => {
      try {
        const buffer = this.ipcRenderer.sendSync(FileChannel.ReadFile, path);
        if (!!buffer) {
          resolve(buffer);
        } else {
          reject(buffer as Error);
        }
      } 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);
      }
    });
  }

  openDefaultDownloadFolder(): Promise<boolean> {
    return new Promise<boolean>((resolve, reject) => {
      try {
        resolve(
          this.ipcRenderer.sendSync(FileChannel.OpenFolderItem, undefined, true)
        );
      } catch (error) {
        reject(error);
      }
    });
  }
  openTargetFolder(folderPath?: string, make?: boolean): Promise<boolean> {
    return new Promise<boolean>((resolve, reject) => {
      try {
        resolve(
          this.ipcRenderer.sendSync(
            FileChannel.OpenFolderItem,
            folderPath,
            !make ? false : make
          )
        );
      } catch (error) {
        reject(error);
      }
    });
  }
  openTargetItem(filePath?: string): Promise<boolean> {
    return new Promise<boolean>((resolve, reject) => {
      try {
        resolve(
          this.ipcRenderer.sendSync(FileChannel.OpenFolderItem, filePath, false)
        );
      } catch (error) {
        reject(error);
      }
    });
  }
  getPath(name: NativePathName): Promise<string> {
    return new Promise<string>((resolve, reject) => {
      try {
        resolve(this.ipcRenderer.sendSync(FileChannel.GetPath, name));
      } catch (error) {
        reject(error);
      }
    });
  }

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

  selectSaveFilePath(
    defaultPath?: string
  ): Promise<{
    canceled: boolean;
    filePath: string;
  }> {
    return new Promise<{
      canceled: boolean;
      filePath: string;
    }>((resolve, reject) => {
      try {
        resolve(
          this.ipcRenderer.sendSync(FileChannel.SelectSaveFilePath, defaultPath)
        );
      } catch (error) {
        reject(error);
      }
    });
  }

  executeProcess(executableName: string): Promise<number> {
    return new Promise<number>((resolve, reject) => {
      try {
        resolve(
          this.ipcRenderer.sendSync(ProcessChannel.Execute, executableName)
        );
      } 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();
    }
  }

  appExit(): void {
    this.ipcRenderer.send(AppChannel.Exit);
  }

  zoomTo(factor: number): Promise<number> {
    return new Promise<number>((resolve, reject) => {
      try {
        this.webFrame.setZoomFactor(factor / 100);
        resolve(this.webFrame.getZoomFactor());
      } catch (error) {
        reject(error);
      }
    });
  }

  idleStateChanged(): Observable<WindowIdle> {
    if (!this.idleStateChangedSubject) {
      this.idleStateChangedSubject = new Subject<WindowIdle>();
      this.idleStateChanged$ = this.idleStateChangedSubject
        .asObservable()
        .pipe(share());
    }
    console.log('idleStateChanged');
    this.ipcRenderer.send(IdleStateChannel.StartCheck);

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

  idleStateStop(): void {
    this.ipcRenderer.send(IdleStateChannel.StopCheck);
  }

  changeLimitOfIdleState(limitTime: number): void {
    this.ipcRenderer.send(IdleStateChannel.ChangeLimitTime, limitTime);
  }

  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$;
  }

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

    this.ipcRenderer.on(
      MessageChannel.OpenMessage,
      (event: any, messageSeq: string) => {
        this.msgOpenMessageSubject.next(messageSeq);
      }
    );
    return this.msgOpenMessage$;
  }

  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;
      this.webFrame = (window as any).require('electron').webFrame;
    }
  }
}
