/*
 * 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 { AccountWallPapers, Document, MessagesSavedGifs, PhotoSize, WallPaper } from '../../layer';
import { ReferenceContext } from '../mtproto/referenceDatabase';
import { getFullDate } from '../../helpers/date';
import isObject from '../../helpers/object/isObject';
import safeReplaceArrayInObject from '../../helpers/object/safeReplaceArrayInObject';
import { AppManager } from './manager';
import wrapPlainText from '../richTextProcessor/wrapPlainText';
import assumeType from '../../helpers/assumeType';
import { getEnvironment } from '../../environment/utils';
import { isServiceWorkerOnline } from '../mtproto/mtproto.worker';
import MTProtoMessagePort from '../mtproto/mtprotoMessagePort';
import getDocumentInput from './utils/docs/getDocumentInput';
import getDocumentURL from './utils/docs/getDocumentURL';
import { CancellablePromise } from '../../helpers/cancellablePromise';

export type MyDocument = Document.document;

// TODO: если залить картинку файлом, а потом перезайти в диалог - превьюшка заново скачается

const EXTENSION_MIME_TYPE_MAP = {
  mov: 'video/quicktime',
  gif: 'image/gif',
  pdf: 'application/pdf',
};

let uploadWallPaperTempId = 0;

export class AppDocsManager extends AppManager {
  private docs: {[docId: DocId]: MyDocument};

  private stickerCachedThumbs: {[docId: DocId]: {[toneIndex: number]: {url: string, w: number, h: number}}};

  protected after() {
    this.docs = {};
    this.stickerCachedThumbs = {};

    MTProtoMessagePort.getInstance<false>().addEventListener('serviceWorkerOnline', (online) => {
      if(!online) {
        this.onServiceWorkerFail();
      }
    });
  }

  private onServiceWorkerFail = () => {
    for(const id in this.docs) {
      const doc = this.docs[id];

      if(doc.supportsStreaming) {
        delete doc.supportsStreaming;
        this.thumbsStorage.deleteCacheContext(doc);
      }
    }
  };

  public saveDoc(doc: Document, context?: ReferenceContext): MyDocument {
    if(doc._ === 'documentEmpty') {
      return undefined;
    }

    const oldDoc = this.docs[doc.id];

    if(doc.file_reference) { // * because we can have a new object w/o the file_reference while sending
      safeReplaceArrayInObject('file_reference', oldDoc, doc);
      this.referenceDatabase.saveContext(doc.file_reference, context);
    }
    
    //console.log('saveDoc', apiDoc, this.docs[apiDoc.id]);
    // if(oldDoc) {
    //   //if(doc._ !== 'documentEmpty' && doc._ === d._) {
    //     if(doc.thumbs) {
    //       if(!oldDoc.thumbs) oldDoc.thumbs = doc.thumbs;
    //       /* else if(apiDoc.thumbs[0].bytes && !d.thumbs[0].bytes) {
    //         d.thumbs.unshift(apiDoc.thumbs[0]);
    //       } else if(d.thumbs[0].url) { // fix for converted thumb in safari
    //         apiDoc.thumbs[0] = d.thumbs[0];
    //       } */
    //     }

    //   //}

    //   return oldDoc;

    //   //return Object.assign(d, apiDoc, context);
    //   //return context ? Object.assign(d, context) : d;
    // }

    if(!oldDoc) {
      this.docs[doc.id] = doc;
    }

    // * exclude from state
    // defineNotNumerableProperties(doc, [/* 'thumbs',  */'type', 'h', 'w', 'file_name', 
    // 'file', 'duration', 'downloaded', 'url', 'audioTitle', 
    // 'audioPerformer', 'sticker', 'stickerEmoji', 'stickerEmojiRaw', 
    // 'stickerSetInput', 'stickerThumbConverted', 'animated', 'supportsStreaming']);

    for(let i = 0, length = doc.attributes.length; i < length; ++i) {
      const attribute = doc.attributes[i];
      switch(attribute._) {
        case 'documentAttributeFilename':
          doc.file_name = wrapPlainText(attribute.file_name);
          break;

        case 'documentAttributeAudio':
          doc.duration = attribute.duration;
          doc.type = attribute.pFlags.voice && doc.mime_type === 'audio/ogg' ? 'voice' : 'audio';
          /* if(apiDoc.type === 'audio') {
            apiDoc.supportsStreaming = true;
          } */
          break;

        case 'documentAttributeVideo':
          doc.duration = attribute.duration;
          doc.w = attribute.w;
          doc.h = attribute.h;
          //apiDoc.supportsStreaming = attribute.pFlags?.supports_streaming/*  && apiDoc.size > 524288 */;
          if(/* apiDoc.thumbs &&  */attribute.pFlags.round_message) {
            doc.type = 'round';
          } else /* if(apiDoc.thumbs) */ {
            doc.type = 'video';
          }
          break;

        case 'documentAttributeSticker':
          if(attribute.alt !== undefined) {
            doc.stickerEmojiRaw = attribute.alt;
          }

          if(attribute.stickerset) {
            if(attribute.stickerset._ === 'inputStickerSetEmpty') {
              delete attribute.stickerset;
            } else if(attribute.stickerset._ === 'inputStickerSetID') {
              doc.stickerSetInput = attribute.stickerset;
            }
          }

          // * there can be no thumbs, then it is a document
          if(/* apiDoc.thumbs &&  */doc.mime_type === 'image/webp' && (doc.thumbs || getEnvironment().IS_WEBP_SUPPORTED)) {
            doc.type = 'sticker';
            doc.sticker = 1;
          } else if(doc.mime_type === 'video/webm') {
            if(!getEnvironment().IS_WEBM_SUPPORTED) {
              return;
            }

            doc.type = 'sticker';
            doc.sticker = 3;
            doc.animated = true;
          }
          break;

        case 'documentAttributeImageSize':
          doc.type = 'photo';
          doc.w = attribute.w;
          doc.h = attribute.h;
          break;

        case 'documentAttributeAnimated':
          if((doc.mime_type === 'image/gif' || doc.mime_type === 'video/mp4')/*  && apiDoc.thumbs */) {
            doc.type = 'gif';
          }

          doc.animated = true;
          break;
      }
    }
    
    if(!doc.mime_type) {
      const ext = (doc.file_name || '').split('.').pop();
      // @ts-ignore
      const mappedMimeType = ext && EXTENSION_MIME_TYPE_MAP[ext.toLowerCase()];
      if(mappedMimeType) {
        doc.mime_type = mappedMimeType;
      } else {
        switch(doc.type) {
          case 'gif':
          case 'video':
          case 'round':
            doc.mime_type = 'video/mp4';
            break;
          case 'sticker':
            doc.mime_type = 'image/webp';
            break;
          case 'audio':
            doc.mime_type = 'audio/mpeg';
            break;
          case 'voice':
            doc.mime_type = 'audio/ogg';
            break;
          default:
            doc.mime_type = 'application/octet-stream';
            break;
        }
      }
    } else if(doc.mime_type === EXTENSION_MIME_TYPE_MAP.pdf) {
      doc.type = 'pdf';
    } else if(doc.mime_type === EXTENSION_MIME_TYPE_MAP.gif) {
      doc.type = 'gif';
    }

    if(doc.type === 'voice' || doc.type === 'round') {
      // browser will identify extension
      doc.file_name = doc.type + '_' + getFullDate(new Date(doc.date * 1000), {monthAsNumber: true, leadingZero: true}).replace(/[:\.]/g, '-').replace(', ', '_');
    }

    if(isServiceWorkerOnline()) {
      if((doc.type === 'gif' && doc.size > 8e6) || doc.type === 'audio' || doc.type === 'video'/*  || doc.mime_type.indexOf('video/') === 0 */) {
        doc.supportsStreaming = true;
        
        const cacheContext = this.thumbsStorage.getCacheContext(doc);
        if(!cacheContext.url) {
          this.thumbsStorage.setCacheContextURL(doc, undefined, getDocumentURL(doc), 0);
        }
      }
    }

    // for testing purposes
    // doc.supportsStreaming = false;
    // doc.url = ''; // * this will break upload urls
    
    if(!doc.file_name) {
      doc.file_name = '';
    }

    if(doc.mime_type === 'application/x-tgsticker' && doc.file_name === 'AnimatedSticker.tgs') {
      doc.type = 'sticker';
      doc.animated = true;
      doc.sticker = 2;
    }

    /* if(!doc.url) {
      doc.url = this.getFileURL(doc);
    } */

    if(oldDoc) {
      return Object.assign(oldDoc, doc);
    }

    return doc;
  }
  
  public getDoc(docId: DocId | MyDocument): MyDocument {
    return isObject<MyDocument>(docId) ? docId : this.docs[docId];
  }

  public downloadDoc(doc: MyDocument, queueId?: number, onlyCache?: boolean) {
    return this.apiFileManager.downloadMedia({
      media: doc,
      queueId,
      onlyCache
    });
  }

  public getLottieCachedThumb(docId: DocId, toneIndex: number) {
    const cached = this.stickerCachedThumbs[docId];
    return cached && cached[toneIndex];
  }

  public saveLottiePreview(docId: DocId, blob: Blob, width: number, height: number, toneIndex: number) {
    const doc = this.getDoc(docId);
    if(!doc) {
      return;
    }

    const cached = this.stickerCachedThumbs[doc.id] ??= {};
  
    const thumb = cached[toneIndex];
    if(thumb && thumb.w >= width && thumb.h >= height) {
      return;
    }

    cached[toneIndex] = {
      url: URL.createObjectURL(blob),
      w: width,
      h: height
    };
  }

  public saveWebPConvertedStrippedThumb(docId: DocId, bytes: Uint8Array) {
    const doc = this.getDoc(docId);
    if(!doc) {
      return;
    }

    const thumb = doc.thumbs && doc.thumbs.find((thumb) => thumb._ === 'photoStrippedSize') as PhotoSize.photoStrippedSize;
    if(!thumb) {
      return;
    }

    doc.pFlags.stickerThumbConverted = true;
    thumb.bytes = bytes;
  }

  public getWallPapers() {
    return this.apiManager.invokeApiHashable({method: 'account.getWallPapers'}).then((accountWallpapers) => {
      const wallPapers = (accountWallpapers as AccountWallPapers.accountWallPapers).wallpapers as WallPaper.wallPaper[];
      wallPapers.forEach((wallPaper) => {
        wallPaper.document = this.saveDoc(wallPaper.document);
      });

      return wallPapers;
    });
  }

  public uploadWallPaper(file: File) {
    const id = 'wallpaper-upload-' + ++uploadWallPaperTempId;

    const thumb = {
      _: 'photoSize',
      h: 0,
      w: 0,
      location: {} as any,
      size: file.size,
      type: 'full',
    } as PhotoSize.photoSize;
    let document: MyDocument = {
      _: 'document',
      access_hash: '',
      attributes: [],
      dc_id: 0,
      file_reference: [],
      id,
      mime_type: file.type,
      size: file.size,
      date: Date.now() / 1000,
      pFlags: {},
      thumbs: [thumb],
      file_name: file.name
    };

    document = this.saveDoc(document);

    const cacheContext = this.thumbsStorage.setCacheContextURL(document, undefined, URL.createObjectURL(file), file.size);

    let wallpaper: WallPaper.wallPaper = {
      _: 'wallPaper',
      access_hash: '',
      document: document,
      id,
      slug: id,
      pFlags: {}
    };

    const upload = this.apiFileManager.upload({file, fileName: file.name});

    upload.then((inputFile) => {
      this.apiManager.invokeApi('account.uploadWallPaper', {
        file: inputFile,
        mime_type: file.type,
        settings: {
          _: 'wallPaperSettings'
        }
      }).then((_wallpaper) => {
        const newDoc = (_wallpaper as WallPaper.wallPaper).document as MyDocument;
        this.thumbsStorage.setCacheContextURL(newDoc, undefined, cacheContext.url, cacheContext.downloaded);

        wallpaper = _wallpaper as WallPaper.wallPaper;
        wallpaper.document = this.saveDoc(wallpaper.document);
      });
    });

    return wallpaper;
  }

  public getGifs() {
    return this.apiManager.invokeApiHashable({
      method: 'messages.getSavedGifs',
      processResult: (res) => {
        assumeType<MessagesSavedGifs.messagesSavedGifs>(res);
        return res.gifs.map((doc) => this.saveDoc(doc));
      }
    });
  }

  public requestDocPart(docId: DocId, dcId: number, offset: number, limit: number) {
    const doc = this.getDoc(docId);
    return this.apiFileManager.requestFilePart(dcId, getDocumentInput(doc), offset, limit);
  }
}
