import { HostListener, Injectable, NgZone } from "@angular/core";
import { FileSystemWritableFileStreamCommonInterface } from "../nano/nano-requests";
import { AudioService, Sounds } from "./audio.service";
import { RouterHandler } from "./router-handler.service";

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

export type NotificationParam = {
  id?: number;
  title: string;
  message: string;
  callback?: Function;
  callbackData?: Object;
};

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 {
  private _isAppInForeground: boolean = true;
  private notificationCallbacks: { [id: number]: Function } = {};

  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 getDevicePlatform(): string {
    return device.platform;
  }

  public getDeviceDefaultDownloadFolderPath(): string {
    switch (device.platform) {
      case "Android":
        return "file:///storage/emulated/0/Download";
      //cordova.file.externalDataDirectory;
      case "iOS":
        return cordova.file.documentsDirectory;
      default:
        throw "Can not recognize device, can not find download folder path";
    }
  }

  constructor(
    private zone: NgZone,
    private audioService: AudioService,
    private routerHandler: RouterHandler
  ) {
    if (this.isCordova()) {
      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
      );

      cordova.plugins.notification.local.on("click", (notification) => {
        if (this.notificationCallbacks[notification.id]) {
          this.zone.run(() => {
            // outside calls from angular zone does not work
            this.notificationCallbacks[notification.id](notification.data);
          });

          delete this.notificationCallbacks[notification.id];
        }
      });
    }
  }

  public isAppInForeground() {
    return this._isAppInForeground;
  }

  private lastNotificationId = 0;

  public callNotification(notificationParam: NotificationParam): number {
    if (!notificationParam.id) notificationParam.id = this.lastNotificationId++;

    if (notificationParam.callback) {
      this.notificationCallbacks[notificationParam.id] =
        notificationParam.callback;
    }

    if (this.isCordova()) {
      cordova.plugins.notification.local.schedule({
        id: notificationParam.id,
        title: notificationParam.title,
        text: notificationParam.message,
        smallIcon: "res://ic_stat_onesignal_default.png",
        color: "#F39A24",
        data: notificationParam.callbackData,
        sound: false, // sadly the plugin's sound effect does not work always
      });
    }

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

    console.log("noti", {
      notificationParam,
    });
    return notificationParam.id;
  }

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

  // best way cordova can check if the file exist, otherwise you need to iterate through the Download folder
  private isFileExist(dirEntry, fileName): Promise<boolean> {
    return new Promise((resolve, reject) => {
      try {
        dirEntry.getFile(
          fileName,
          { create: false },
          () => {
            resolve(true);
          },
          () => {
            resolve(false);
          }
        );
      } catch (e) {
        console.error("file read error", e);
        reject(e);
      }
    });
  }

  // make a unique name for the file if it is already exist
  private getFile(dirEntry, fileName, trial = 0): Promise<any> {
    let name = fileName;
    if (trial > 0) {
      name = "(" + trial + ") " + fileName;
    }
    return this.isFileExist(dirEntry, name).then((res) => {
      if (res) {
        return this.getFile(dirEntry, fileName, trial + 1);
      } else {
        return new Promise((resolve, reject) => {
          return dirEntry.getFile(
            name,
            { create: true },
            (writer) => {
              resolve(writer);
            },
            (err) => {
              reject(err);
            }
          );
        });
      }
    });
  }

  public getFileWriter(
    fileName: string
  ): Promise<FileSystemWritableFileStreamCommonInterface> {
    return new Promise((resolve, reject) => {
      try {
        var folderPath = this.getDeviceDefaultDownloadFolderPath();

        window["resolveLocalFileSystemURL"](
          folderPath,
          (dir) => {
            this.getFile(dir, fileName)
              .then((fileWriter) => {
                fileWriter.createWriter(
                  (fileWriter) => {
                    resolve(
                      this.convertFileSystemWritableFileStreamCommonInterfaceFromCordovaFileWriter(
                        fileWriter
                      )
                    );
                  },
                  function (err) {
                    // failed
                    alert("Unable to download");
                    console.error(err);
                    reject(err);
                  }
                );
              })
              .catch((err) => {
                alert("Unable to download");
                console.error(err);
                reject(err);
              });
          },
          (err) => {
            alert("Unable to download");
            console.error(err);
            reject(err);
          }
        );
      } catch (e) {
        console.error("Error during writablestream selection");
        reject(e);
      }
    });
  }

  private convertFileSystemWritableFileStreamCommonInterfaceFromCordovaFileWriter(
    fileWriter
  ): FileSystemWritableFileStreamCommonInterface {
    return {
      write: (payload) => {
        return new Promise(async (resolve, reject) => {
          let process = Math.random();
          fileWriter.onwriteend = () => {
            console.log("resolve now", process);
            resolve();
          };
          fileWriter.onerror = (e) => {
            console.log("error now", e, process);
            reject(e);
          };

          console.log("start writing", process);
          let blob = new Blob([payload], { type: "application/octet-stream" });
          console.log("blob done");
          fileWriter.write(blob);
          console.log("writing started...");
        });
      },
      close: () => {
        return Promise.resolve();
      },
      truncate: (size) => {
        return new Promise((resolve, reject) => {
          fileWriter.truncate(size);
          resolve(null);
        });
      },
    };
  }
}
