/*
 * https://github.com/morethanwords/tweb
 * Copyright (C) 2019-2021 Eduard Kuzmenko
 * https://github.com/morethanwords/tweb/blob/master/LICENSE
 * 
 * Originally from:
 * https://github.com/zhukov/webogram
 * Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
 * https://github.com/zhukov/webogram/blob/master/LICENSE
 */

import { fontFamily } from "../../components/middleEllipsis";
import { MOUNT_CLASS_TO } from "../../config/debug";
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise";
import { tsNow } from "../../helpers/date";
import { copy, deepEqual } from "../../helpers/object";
import { convertInputKeyToKey } from "../../helpers/string";
import { isMobile } from "../../helpers/userAgent";
import { InputNotifyPeer, InputPeerNotifySettings, NotifyPeer, PeerNotifySettings, Update } from "../../layer";
import apiManager from "../mtproto/mtprotoworker";
import rootScope from "../rootScope";
import sessionStorage from "../sessionStorage";
import apiUpdatesManager from "./apiUpdatesManager";
import appPeersManager from "./appPeersManager";
import appStateManager from "./appStateManager";

type MyNotification = Notification & {
  hidden?: boolean,
  show?: () => void,
};

export type NotifyOptions = Partial<{
  tag: string;
  image: string;
  key: string;
  title: string;
  message: string;
  silent: boolean;
  onclick: () => void;
}>;

type ImSadAboutIt = Promise<PeerNotifySettings> | PeerNotifySettings;
export class AppNotificationsManager {
  private notificationsUiSupport: boolean;
  private notificationsShown: {[key: string]: MyNotification} = {};
  private notificationIndex = 0;
  private notificationsCount = 0;
  private soundsPlayed: {[tag: string]: number} = {};
  private vibrateSupport = !!navigator.vibrate;
  private nextSoundAt: number;
  private prevSoundVolume: number;
  private peerSettings = {
    notifyPeer: {} as {[peerId: number]: ImSadAboutIt},
    notifyUsers: null as ImSadAboutIt,
    notifyChats: null as ImSadAboutIt,
    notifyBroadcasts: null as ImSadAboutIt
  };
  private exceptions: {[peerId: string]: PeerNotifySettings} = {};
  private notifyContactsSignUp: Promise<boolean>;
  private faviconEl: HTMLLinkElement = document.head.querySelector('link[rel="icon"]');
  private langNotificationsPluralize = 'notifications';//_.pluralize('page_title_pluralize_notifications');

  private titleBackup = document.title;
  private titleChanged = false;
  private titleInterval: number;
  private prevFavicon: string;
  private stopped = false;

  private settings: Partial<{
    nodesktop: boolean,
    volume: number,
    novibrate: boolean,
    nopreview: boolean,
    nopush: boolean,
    nosound: boolean,
  }> = {};

  private registeredDevice: any;
  private pushInited = false;

  private topMessagesDeferred: CancellablePromise<void>;

  private notifySoundEl: HTMLElement;

  constructor() {
    // @ts-ignore
    navigator.vibrate = navigator.vibrate || navigator.mozVibrate || navigator.webkitVibrate;

    this.notificationsUiSupport = ('Notification' in window) || ('mozNotification' in navigator);

    this.topMessagesDeferred = deferredPromise<void>();

    this.notifySoundEl = document.createElement('div');
    this.notifySoundEl.id = 'notify-sound';
    document.body.append(this.notifySoundEl);

    /* rootScope.on('idle.deactivated', (newVal) => {
      if(newVal) {
        stop();
      }
    });*/

    rootScope.on('idle', (newVal) => {
      if(this.stopped) {
        return;
      }

      if(!newVal) {
        this.clear();
      }

      this.toggleToggler();
    });

    rootScope.on('apiUpdate', (update) => {
      // console.log('on apiUpdate', update)
      switch(update._) {
        case 'updateNotifySettings': {
          this.savePeerSettings(update.peer._ === 'notifyPeer' ? appPeersManager.getPeerId(update.peer.peer) : update.peer._, update.notify_settings);
          rootScope.broadcast('notify_settings', update);
          break;
        }
      }
    });

    /* rootScope.on('push_init', (tokenData) => {
      this.pushInited = true
      if(!this.settings.nodesktop && !this.settings.nopush) {
        if(tokenData) {
          this.registerDevice(tokenData);
        } else {
          WebPushApiManager.subscribe();
        }
      } else {
        this.unregisterDevice(tokenData);
      }
    });
    rootScope.on('push_subscribe', (tokenData) => {
      this.registerDevice(tokenData);
    });
    rootScope.on('push_unsubscribe', (tokenData) => {
      this.unregisterDevice(tokenData);
    }); */

    rootScope.addEventListener('dialogs_multiupdate', () => {
      //unregisterTopMsgs()
      this.topMessagesDeferred.resolve();
    }, true);

    /* rootScope.on('push_notification_click', (notificationData) => {
      if(notificationData.action === 'push_settings') {
        this.topMessagesDeferred.then(() => {
          $modal.open({
            templateUrl: templateUrl('settings_modal'),
            controller: 'SettingsModalController',
            windowClass: 'settings_modal_window mobile_modal',
            backdrop: 'single'
          })
        });
        return;
      }

      if(notificationData.action === 'mute1d') {
        apiManager.invokeApi('account.updateDeviceLocked', {
          period: 86400
        }).then(() => {
          // var toastData = toaster.pop({
          //   type: 'info',
          //   body: _('push_action_mute1d_success'),
          //   bodyOutputType: 'trustedHtml',
          //   clickHandler: () => {
          //     toaster.clear(toastData)
          //   },
          //   showCloseButton: false
          // })
        });

        return;
      }

      const peerId = notificationData.custom && notificationData.custom.peerId;
      console.log('click', notificationData, peerId);
      if(peerId) {
        this.topMessagesDeferred.then(() => {
          if(notificationData.custom.channel_id &&
              !appChatsManager.hasChat(notificationData.custom.channel_id)) {
            return;
          }

          if(peerId > 0 && !appUsersManager.hasUser(peerId)) {
            return;
          }

          // rootScope.broadcast('history_focus', {
          //   peerString: appPeersManager.getPeerString(peerId)
          // });
        });
      }
    }); */
  }

  private toggleToggler(enable = rootScope.idle.isIDLE) {
    if(isMobile) return;

    const resetTitle = () => {
      this.titleChanged = false;
      document.title = this.titleBackup;
      this.setFavicon();
    };

    window.clearInterval(this.titleInterval);
    this.titleInterval = 0;

    if(!enable) {
      resetTitle();
    } else {
      this.titleInterval = window.setInterval(() => {
        if(!this.notificationsCount) {
          this.toggleToggler(false);
        } else if(this.titleChanged) {
          resetTitle();
        } else {
          this.titleChanged = true;
          document.title = this.notificationsCount + ' ' + this.langNotificationsPluralize;
          //this.setFavicon('assets/img/favicon_unread.ico');

          // fetch('assets/img/favicon.ico')
          // .then(res => res.blob())
          // .then(blob => {
            // const img = document.createElement('img');
            // img.src = URL.createObjectURL(blob);

            const canvas = document.createElement('canvas');
            canvas.width = 32 * window.devicePixelRatio;
            canvas.height = canvas.width;
  
            const ctx = canvas.getContext('2d');
            ctx.beginPath();
            ctx.arc(canvas.width / 2, canvas.height / 2, canvas.width / 2, 0, 2 * Math.PI, false);
            ctx.fillStyle = '#5b8af1';
            ctx.fill();

            let fontSize = 24;
            let str = '' + this.notificationsCount;
            if(this.notificationsCount < 10) {
              fontSize = 22;
            } else if(this.notificationsCount < 100) {
              fontSize = 20;
            } else {
              str = '99+';
              fontSize = 16;
            }

            fontSize *= window.devicePixelRatio;
            
            ctx.font = `700 ${fontSize}px ${fontFamily}`;
            ctx.textBaseline = 'middle';
            ctx.textAlign = 'center';
            ctx.fillStyle = 'white';
            ctx.fillText(str, canvas.width / 2, canvas.height * .5625);

            /* const ctx = canvas.getContext('2d');
            ctx.drawImage(img, 0, 0, canvas.width, canvas.height); */
  
            this.setFavicon(canvas.toDataURL());
          // });
        }
      }, 1000);
    }
  }

  public updateLocalSettings = () => {
    Promise.all(['notify_nodesktop', 'notify_volume', 'notify_novibrate', 'notify_nopreview', 'notify_nopush'].map(k => sessionStorage.get(k as any)))
    .then((updSettings) => {
      this.settings.nodesktop = updSettings[0];
      this.settings.volume = updSettings[1] === undefined ? 0.5 : updSettings[1];
      this.settings.novibrate = updSettings[2];
      this.settings.nopreview = updSettings[3];
      this.settings.nopush = updSettings[4];

      /* if(this.pushInited) {
        const needPush = !this.settings.nopush && !this.settings.nodesktop && WebPushApiManager.isAvailable || false;
        const hasPush = this.registeredDevice !== false;
        if(needPush !== hasPush) {
          if(needPush) {
            WebPushApiManager.subscribe();
          } else {
            WebPushApiManager.unsubscribe();
          }
        }
      }

      WebPushApiManager.setSettings(this.settings); */
    });

    appStateManager.getState().then(state => {
      this.settings.nosound = !state.settings.notifications.sound;
    });
  }

  public getLocalSettings() {
    return this.settings;
  }

  public getNotifySettings(peer: InputNotifyPeer): ImSadAboutIt {
    let key: any = convertInputKeyToKey(peer._);
    let obj: any = this.peerSettings[key as NotifyPeer['_']];

    if(peer._ === 'inputNotifyPeer') {
      key = appPeersManager.getPeerId(peer.peer);
      obj = obj[key];
    }

    if(obj) {
      return obj;
    }

    return (obj || this.peerSettings)[key] = apiManager.invokeApi('account.getNotifySettings', {peer})
    .then(settings => {
      this.savePeerSettings(key, settings);
      return settings;
    });
  }

  public updateNotifySettings(peer: InputNotifyPeer, settings: InputPeerNotifySettings) {
    //this.savePeerSettings(peerId, settings);

    /* const inputSettings: InputPeerNotifySettings = copy(settings) as any;
    inputSettings._ = 'inputPeerNotifySettings'; */

    return apiManager.invokeApi('account.updateNotifySettings', {
      peer,
      settings
    }).then(value => {
      if(value) {
        apiUpdatesManager.processUpdateMessage({
          _: 'updateShort',
          update: {
            _: 'updateNotifySettings', 
            peer: {
              ...peer,
              _: convertInputKeyToKey(peer._)
            }, 
            notify_settings: { // ! WOW, IT WORKS !
              ...settings,
              _: 'peerNotifySettings',
            }
          } as Update.updateNotifySettings
        });
      }
    });
  }

  public getNotifyExceptions() {
    apiManager.invokeApi('account.getNotifyExceptions', {compare_sound: true})
    .then((updates) => {
      apiUpdatesManager.processUpdateMessage(updates);
    });
  }

  public getContactSignUpNotification() {
    if(this.notifyContactsSignUp) return this.notifyContactsSignUp;
    return this.notifyContactsSignUp = apiManager.invokeApi('account.getContactSignUpNotification');
  }

  public setContactSignUpNotification(silent: boolean) {
    apiManager.invokeApi('account.setContactSignUpNotification', {silent})
    .then(value => {
      this.notifyContactsSignUp = Promise.resolve(!silent);
    });
  }

  private setFavicon(href: string = 'assets/img/favicon.ico') {
    if(this.prevFavicon === href) {
      return;
    }

    const link = this.faviconEl.cloneNode() as HTMLLinkElement;
    link.href = href;
    this.faviconEl.parentNode.replaceChild(link, this.faviconEl);
    this.faviconEl = link;

    this.prevFavicon = href;
  }

  public savePeerSettings(key: number | Exclude<NotifyPeer['_'], 'notifyPeer'>, settings: PeerNotifySettings) {
    let obj: any;
    if(typeof(key) === 'number') {
      obj = this.peerSettings['notifyPeer'];
    }
    
    (obj || this.peerSettings)[key] = settings;

    if(typeof(key) !== 'number') {
      rootScope.broadcast('notify_peer_type_settings', {key, settings});
    }

    //rootScope.broadcast('notify_settings', {peerId: peerId});
  }

  public isMuted(peerNotifySettings: PeerNotifySettings) {
    return peerNotifySettings._ === 'peerNotifySettings' &&
      (peerNotifySettings.mute_until * 1000) > tsNow();
  }

  public getPeerMuted(peerId: number) {
    const ret = this.getNotifySettings({_: 'inputNotifyPeer', peer: appPeersManager.getInputPeerById(peerId)});
    return (ret instanceof Promise ? ret : Promise.resolve(ret))
    .then((peerNotifySettings) => this.isMuted(peerNotifySettings));
  }

  public getPeerLocalSettings(peerId: number, respectType = true): PeerNotifySettings {
    const n: PeerNotifySettings = {
      _: 'peerNotifySettings'
    };

    const notifySettings = this.peerSettings['notifyPeer'][peerId];
    //if(!notifySettings || (notifySettings instanceof Promise)) return false;
    if(notifySettings && !(notifySettings instanceof Promise)) {
      Object.assign(n, notifySettings);
    }

    if(respectType) {
      const inputNotify = appPeersManager.getInputNotifyPeerById(peerId, true);
      const key = convertInputKeyToKey(inputNotify._);
      const typeNotifySettings = this.peerSettings[key as NotifyPeer['_']];
      if(typeNotifySettings && !(typeNotifySettings instanceof Promise)) {
        for(let i in typeNotifySettings) {
          // @ts-ignore
          if(n[i] === undefined) {
            // @ts-ignore
            n[i] = typeNotifySettings[i];
          }
        }
      }
    }

    return n;
  }

  public isPeerLocalMuted(peerId: number, respectType = true) {
    if(peerId === rootScope.myId) return false;

    const notifySettings = this.getPeerLocalSettings(peerId, respectType);
    return this.isMuted(notifySettings);
  }

  public start() {
    this.updateLocalSettings();
    rootScope.on('settings_updated', this.updateLocalSettings);
    //WebPushApiManager.start();

    if(!this.notificationsUiSupport) {
      return false;
    }

    if('Notification' in window && Notification.permission !== 'granted' && Notification.permission !== 'denied') {
      window.addEventListener('click', this.requestPermission);
    }

    try {
      if('onbeforeunload' in window) {
        window.addEventListener('beforeunload', this.clear);
      }
    } catch (e) {}
  }

  private stop() {
    this.clear();
    window.clearInterval(this.titleInterval);
    this.titleInterval = 0;
    this.setFavicon();
    this.stopped = true;
  }

  private requestPermission = () => {
    Notification.requestPermission();
    window.removeEventListener('click', this.requestPermission);
  };

  public notify(data: NotifyOptions) {
    console.log('notify', data, rootScope.idle.isIDLE, this.notificationsUiSupport, this.stopped);
    if(this.stopped) {
      return;
    }

    // FFOS Notification blob src bug workaround
    /* if(Config.Navigator.ffos && !Config.Navigator.ffos2p) {
      data.image = 'https://telegram.org/img/t_logo.png'
    }
    else if (data.image && !angular.isString(data.image)) {
      if (Config.Navigator.ffos2p) {
        FileManager.getDataUrl(data.image, 'image/jpeg').then(function (url) {
          data.image = url
          notify(data)
        })
        return false
      } else {
        data.image = FileManager.getUrl(data.image, 'image/jpeg')
      }
    }
    else if (!data.image) */ {
      data.image = 'assets/img/logo.svg';
    }
    // console.log('notify image', data.image)

    this.notificationsCount++;
    if(!this.titleInterval) {
      this.toggleToggler();
    }

    const now = tsNow();
    if(this.settings.volume > 0 && !this.settings.nosound/* &&
      (
        !data.tag ||
        !this.soundsPlayed[data.tag] ||
        now > this.soundsPlayed[data.tag] + 60000
      ) */
    ) {
      this.testSound(this.settings.volume);
      this.soundsPlayed[data.tag] = now;
    }

    if(!this.notificationsUiSupport ||
      'Notification' in window && Notification.permission !== 'granted') {
      return false;
    }

    if(this.settings.nodesktop) {
      if(this.vibrateSupport && !this.settings.novibrate) {
        navigator.vibrate([200, 100, 200]);
        return;
      }

      return;
    }

    const idx = ++this.notificationIndex;
    const key = data.key || 'k' + idx;
    let notification: MyNotification;

    if('Notification' in window) {
      try {
        if(data.tag) {
          for(let i in this.notificationsShown) {
            const notification = this.notificationsShown[i];
            if(notification &&
                notification.tag === data.tag) {
              notification.hidden = true;
            }
          }
        }

        notification = new Notification(data.title, {
          icon: data.image || '',
          body: data.message || '',
          tag: data.tag || '',
          silent: data.silent || false
        });
        console.log('notify constructed notification');
      } catch(e) {
        this.notificationsUiSupport = false;
        //WebPushApiManager.setLocalNotificationsDisabled();
        return;
      }
    } /* else if('mozNotification' in navigator) {
      notification = navigator.mozNotification.createNotification(data.title, data.message || '', data.image || '')
    } else if(notificationsMsSiteMode) {
      window.external.msSiteModeClearIconOverlay()
      window.external.msSiteModeSetIconOverlay('img/icons/icon16.png', data.title)
      window.external.msSiteModeActivate()
      notification = {
        index: idx
      }
    } */ else {
      return;
    }

    notification.onclick = () => {
      notification.close();
      //AppRuntimeManager.focus();
      this.clear();
      if(data.onclick) {
        data.onclick();
      }
    };

    notification.onclose = () => {
      if(!notification.hidden) {
        delete this.notificationsShown[key];
        this.clear();
      }
    };

    if(notification.show) {
      notification.show();
    }
    this.notificationsShown[key] = notification;

    if(!isMobile) {
      setTimeout(() => {
        this.hide(key);
      }, 8000);
    }
  }

  public testSound(volume: number) {
    const now = tsNow();
    if(this.nextSoundAt && now < this.nextSoundAt && this.prevSoundVolume === volume) {
      return;
    }

    this.nextSoundAt = now + 1000;
    this.prevSoundVolume = volume;
    const filename = 'assets/audio/notification.mp3';
    const audio = document.createElement('audio');
    audio.autoplay = true;
    audio.setAttribute('mozaudiochannel', 'notification');
    audio.volume = volume;
    audio.innerHTML = `
      <source src="${filename}" type="audio/mpeg" />
      <embed hidden="true" autostart="true" loop="false" volume="${volume * 100}" src="${filename}" />
    `;
    this.notifySoundEl.append(audio);

    audio.addEventListener('ended', () => {
      audio.remove();
    }, {once: true});
  }

  public cancel(key: string) {
    const notification = this.notificationsShown[key];
    if(notification) {
      if(this.notificationsCount > 0) {
        this.notificationsCount--;
      }

      try {
        if(notification.close) {
          notification.hidden = true;
          notification.close();
        }/*  else if(notificationsMsSiteMode &&
          notification.index === notificationIndex) {
          window.external.msSiteModeClearIconOverlay()
        } */
      } catch (e) {}

      delete this.notificationsShown[key];
    }
  }

  private hide(key: string) {
    const notification = this.notificationsShown[key];
    if(notification) {
      try {
        if(notification.close) {
          notification.hidden = true;
          notification.close();
        }
      } catch (e) {}
    }
  }

  public soundReset(tag: string) {
    delete this.soundsPlayed[tag];
  }

  public clear() {
    /* if(notificationsMsSiteMode) {
      window.external.msSiteModeClearIconOverlay()
    } else { */
      for(let i in this.notificationsShown) {
        const notification = this.notificationsShown[i];
        try {
          if(notification.close) {
            notification.close();
          }
        } catch (e) {}
      }
    /* } */
    this.notificationsShown = {};
    this.notificationsCount = 0;

    //WebPushApiManager.hidePushNotifications();
  }

  private registerDevice(tokenData: any) {
    if(this.registeredDevice &&
        deepEqual(this.registeredDevice, tokenData)) {
      return false;
    }

    apiManager.invokeApi('account.registerDevice', {
      token_type: tokenData.tokenType,
      token: tokenData.tokenValue,
      other_uids: [],
      app_sandbox: false,
      secret: new Uint8Array()
    }).then(() => {
      this.registeredDevice = tokenData;
    }, (error) => {
      error.handled = true;
    })
  }

  private unregisterDevice(tokenData: any) {
    if(!this.registeredDevice) {
      return false;
    }

    apiManager.invokeApi('account.unregisterDevice', {
      token_type: tokenData.tokenType,
      token: tokenData.tokenValue,
      other_uids: []
    }).then(() => {
      this.registeredDevice = false
    }, (error) => {
      error.handled = true
    })
  }

  public getVibrateSupport() {
    return this.vibrateSupport
  }
}

const appNotificationsManager = new AppNotificationsManager();
MOUNT_CLASS_TO.appNotificationsManager = appNotificationsManager;
export default appNotificationsManager;
