import { Injectable, NgZone } from '@angular/core';
import { marker } from '@biesbjerg/ngx-translate-extract-marker';
import { FileSystemWritableFileStreamCommonInterface } from '../nano/nano-requests';
import { styleChangedObserver } from '../shared-observers';
import { AudioService, Sounds } from './audio.service';
import { BlobService } from './blob.service';
import { RouterHandler } from './router-handler.service';
import { SnackBarService } from './snackbar.service';

declare const cordova;
declare const device; // cordova-plugin-device
declare const universalLinks; // cordova-plugin-deeplinks
declare const StatusBar; // cordova-plugin-statusbar

export type NotificationParam = {
  id?: number;
  title?: string;
  text?: string;
  data?: Object;
  group?: string;
};

export enum NotificationCommands {
  NAVIGATE,
}

export interface ProgressNotificationParam extends NotificationParam {
  progressBar: { value?: number; enabled: boolean };
}

export type DeepLinkEvent = {
  /**
   * fargment
   */
  hash: string;
  /**
   * host
   */
  host: string;
  /**
   * query params
   */
  params: { [key: string]: string };
  /**
   * path after host
   */
  path: string;
  /**
   * https or http
   */
  scheme: 'https' | 'http';
  /**
   * full url
   */
  url: string;
};

@Injectable({
  providedIn: 'root',
})
export class CordovaService {
  public static PROGRESS_NOTIFICATION_ID = 0; //all progress runs on the same notification
  public static PROGRESS_ERROR_NOTIFICATION_ID = 1;

  private _isAppInForeground: boolean = true;

  public isCordova(): boolean {
    return !!window['cordova']; // use window[] or it will throw a reference error instead of undefined
  }

  public isOnApp(): boolean {
    return this.isCordova() && cordova.platformId !== 'browser';
  }

  public isOnAndroid(): boolean {
    return this.isOnApp() && cordova.platformId == 'android';
  }

  public isOnIOS(): boolean {
    return this.isOnApp() && cordova.platformId == 'ios';
  }

  public getDevicePlatform(): string {
    return device.platform;
  }

  public getPermissionHandler() {
    return cordova.plugins.permissions;
  }

  public hasFileCreatePermissionPromise(): Promise<boolean> {
    return new Promise((resolve, reject) => {
      let permHandler = this.getPermissionHandler();

      permHandler.checkPermission(
        permHandler.READ_EXTERNAL_STORAGE,
        (status) => {
          console.log('PERM: Check read permission: ', status);

          if (status.hasPermission) {
            permHandler.checkPermission(permHandler.WRITE_EXTERNAL_STORAGE, (manageStatus) => {
              console.log('PERM: Check write permission: ', manageStatus);
              resolve(manageStatus.hasPermission);
            });
          } else {
            resolve(false);
          }
        },
        (err) => {
          console.log('PERM: ERROR Check read or write permission: ', err);
          reject(err);
        }
      );
    });
  }

  public promptFileCreatePermissionPromise(): Promise<boolean> {
    return new Promise((resolve, reject) => {
      let permHandler = this.getPermissionHandler();

      permHandler.requestPermissions(
        [permHandler.READ_EXTERNAL_STORAGE, permHandler.WRITE_EXTERNAL_STORAGE],
        (status) => {
          console.log('PERM: Prompt has given read and write permission: ', status);
          resolve(status.hasPermission);
        },
        (err) => {
          console.log('PERM: ERROR Prompt has given read and write permission: ', err);
          reject(err);
        }
      );
    });
  }

  public open(filePath): void {
    cordova.plugins.fileOpener2.showOpenWithDialog(filePath, null, {
      error: (e) => {
        console.log('Error status: ' + e.status + ' - Error message: ' + e.message);
        if (e.status === 9) {
          this.snackbarService.showSnackbar(marker('File not found.'));
        }
      },
      success: () => {
        console.log('file opened successfully');
      },
    });
  }

  constructor(
    // DO NOT USE DialogService here, its copy mechanism requires cordova
    // so it cause a circular dependency
    // bubble up promise error instead
    private zone: NgZone,
    private audioService: AudioService,
    private routerHandler: RouterHandler,
    private blobService: BlobService,
    private snackbarService: SnackBarService
  ) {
    if (this.isCordova()) {
      //iOS needs this setting so it won't push the header out of the view. https://github.com/cjpearson/cordova-plugin-keyboard
      if (window['Keyboard'] && window['Keyboard'].shrinkView) {
        window['Keyboard'].shrinkView(true, (ok) => {
          console.log('shrink keyboard set up done', ok);
        });
        window['Keyboard'].hideFormAccessoryBar(true);
      }

      universalLinks.subscribe('deeplink', (ev: DeepLinkEvent) => {
        console.log('deeplink navigation', ev);
        this.zone.run(() => {
          this.routerHandler.navigate([ev.path], {
            fragment: ev.hash,
          });
        });
      });

      // notification for messages
      document.addEventListener(
        'pause',
        () => {
          this._isAppInForeground = false;
        },
        false
      );

      document.addEventListener(
        'resume',
        () => {
          this._isAppInForeground = true;
        },
        false
      );

      this.setUpLocalNotification();
    }

    styleChangedObserver.subscribe((style) => {
      if (style == 'dark') {
        this.setDarkThemeStatusbar();
      } else if (style == 'light') {
        this.setLightThemeStatusbar();
      }
    });
  }

  private setUpLocalNotification(): void {
    cordova.plugins.notification.local.setDefaults({
      smallIcon: 'res://notif.png',
      color: '#F39A24',
      sound: false, // sadly the plugin's sound effect does not work always
    });
    cordova.plugins.notification.local.on('click', (notification) => {
      this.zone.run(() => {
        // outside calls from angular zone does not work
        if (notification.data.command === NotificationCommands.NAVIGATE) {
          this.routerHandler.navigate([notification.data.to]);
        }
      });
    });
  }

  public isAppInForeground() {
    return this._isAppInForeground;
  }

  public playNotificationSound() {
    if (this.isAppInForeground()) {
      this.audioService.playSound(Sounds.NOTIFICATION);
    } else {
      if (this.isOnApp()) {
        navigator['notification'].beep();
      } else {
        this.audioService.playSound(Sounds.BACKGROUND_NOTIFICATION);
      }
    }
  }

  public callProgressNotification(progressNotification: ProgressNotificationParam): void {
    if (progressNotification.progressBar.enabled) {
      if (progressNotification.progressBar.value === 0) {
        console.log('noti progress start', progressNotification);
        cordova.plugins.notification.local.schedule(progressNotification);
      } else {
        // console.log('noti progress');
        cordova.plugins.notification.local.update({
          id: progressNotification.id,
          progressBar: { value: progressNotification.progressBar.value },
        });
      }
    } else {
      setTimeout(() => {
        console.log('noti progress end', progressNotification);
        this.clearNotification(CordovaService.PROGRESS_NOTIFICATION_ID);
        cordova.plugins.notification.local.schedule(progressNotification);
      }, 500);
    }
  }

  public callProgressErrorNotification(progressErrorNotification: NotificationParam): void {
    cordova.plugins.notification.local.schedule(progressErrorNotification);
  }

  public clearNotification(notificationId): void {
    cordova.plugins.notification.local.update({ id: notificationId, sticky: false });
    cordova.plugins.notification.local.cancel(notificationId);
    cordova.plugins.notification.local.clear([notificationId]);
  }

  public openLinkInBrowser(url) {
    cordova.InAppBrowser.open(url, '_system');
  }

  public copyClipboard(text): Promise<void> {
    return new Promise((resolve, reject) => {
      cordova.plugins.clipboard.copy(text, resolve, reject);
    });
  }

  public getFileWriter(fileName: string): Promise<FileSystemWritableFileStreamCommonInterface> {
    let mobilePermissionPromise = new Promise<boolean>((resolve, reject) => {
      this.hasFileCreatePermissionPromise()
        .then((hasPerm) => {
          console.log('PERM: Check perm returns ', hasPerm);
          if (hasPerm) {
            console.log('PERM: App has file read and write permission');
            resolve(true);
          } else {
            this.promptFileCreatePermissionPromise()
              .then((granted) => {
                if (granted) {
                  console.log('PERM: User granted file read and write permission');
                  resolve(true);
                } else {
                  console.log('PERM: User denied file read and write permission');
                  resolve(false);
                }
              })
              .catch((err) => reject(err));
          }
        })
        .catch((err) => reject(err));
    });

    return mobilePermissionPromise.then((granted) => {
      if (!granted) {
        console.error('Permissions not granted.', granted);
        return Promise.reject('Permissions not granted.');
      } else {
        return window['blobSaver'].getFileWriter(fileName, '({x}) {base}{ext}', (payload) => {
          return new Promise((resolve, reject) => {
            try {
              resolve(this.blobService.new([payload])); // { type: "application/octet-stream" }
            } catch (e) {
              reject(e);
            }
          });
        });
      }
    });
  }

  /**
   * running processes extends the device running time in the background
   */
  public registerRunningProcess(id): Promise<void> {
    return window['blobSaver'].registerRunningProcess(id);
  }

  /**
   * clean up the process, so the device can suspends the app immediatly in the background
   */
  public removeRunningProcess(id): Promise<void> {
    return window['blobSaver'].removeRunningProcess(id);
  }

  public subscribeNotification(cb) {
    if (window['pushNotification']) {
      window['pushNotification'].subscribeNotification(cb);
    }
  }

  public subscribeNotificationTap(cb) {
    if (window['pushNotification']) {
      window['pushNotification'].subscribeNotificationTap(cb);
    }
  }

  public notificationPullStart() {
    if (window['pushNotification']) {
      window['pushNotification'].pullStart();
    } else {
      console.warn('there is no implemented pushNotification handler for this platform');
    }
  }

  public setDarkThemeStatusbar() {
    if (window['StatusBar']) {
      StatusBar.overlaysWebView(false);
      StatusBar.styleLightContent();
      StatusBar.backgroundColorByName('black');
    }
  }

  public setLightThemeStatusbar() {
    if (window['StatusBar']) {
      StatusBar.overlaysWebView(false);
      StatusBar.styleDefault();
      StatusBar.backgroundColorByName('white');
    }
  }
}
