import {EventEmitter} from 'events'
// 兼容性
const requestFileSystem = (window as any).requestFileSystem || (window as any).webkitRequestFileSystem
const bytesUint1G = Math.pow(1024, 3)
const storageSizeDefault = 2 * bytesUint1G
const recordBasePath = 'jsp'

class FileStorage {
    private _bWaitMp4Index:boolean
    _fileEntry = null
    _fileWriter = null
    _stashBuffer = null
    _stashUsed = 0
    _bStarted = false   // 标记当前是否在录制状态，用于决定是否缓存塞入的数据，和判断是否结束录制
    _writedBytes = 0
    _bWaitingNewData = false    // 上一轮写入结束后，_stashBuffer可能没有新的数据，标记等待新数据塞入后再进行写入
    _stashSize = 1920 * 1080 * 30 * 10 / 8 // 缓存大小，默认值是以30帧1080P大小计算的
    _transFlag = false
    emitter: any
    constructor( ) {
        this.emitter = new EventEmitter()
    }

    on(event:any, listener:any) {
        this.emitter.addListener(event, listener);
    }

    off(event:any, listener:any) {
        this.emitter.removeListener(event, listener);
    }

    static queryUsageAndQuota() {
        return new Promise((resolve) => {
            const queryUsageAndQuotaSucCB = (usedBytes, grantedBytes) => {
                resolve({ usedBytes, grantedBytes })
            }
            (navigator as any).webkitTemporaryStorage.queryUsageAndQuota(queryUsageAndQuotaSucCB, errCallback)
        })
    }

    /**
     * @param {String} fileName
     * @param {JSON} extra { head, fileFormat, writeHead, ignoreAudio } fileFormat: 2-ps, 5-pm4, 100-mp3
     * @returns
     */
    async startRecord( fileName, extra:{ head:string,fileFormat:number}) {
        this._bStarted = true
        return new Promise(async (resolve, reject) => {
            const fs:any = await Singleton.getFileSystem()
            let getFileSucCb = (fileEntry) => {
                this._fileEntry = fileEntry
                fileEntry.createWriter(createWriterSucCb, errCallback)
            }
            let createWriterSucCb = (fileWriter) => {
                this._fileWriter = fileWriter
                fileWriter.onwriteend = this._onWriteEnd.bind(this)
                // 检查缓存中是否有数据，无则标记等待，有则开始写入
                if (this._stashUsed === 0) {
                    this._bWaitingNewData = true
                } else {
                    this._write()
                }
                resolve(true)
            }
            let ext = extra.fileFormat === 100 ? 'mp3' : 'mp4'
            fs.root.getFile(`${recordBasePath}/${fileName}.${ext}`, { create: true }, getFileSucCb, errCallback)
        })
    }

    stopRecord() {
        return new Promise((resolve, reject) => {
            if (!this._fileEntry || !this._bStarted){
                return resolve(true)
            }

            this.emitter.on('lastWriteEnd', () => {
                downloadFile(this._fileEntry.toURL(), this._fileEntry.name)
                resolve(true)
            })

            // 有时候在等新数据写入的间隙，无法触发lastWriteEnd
            if (this._bWaitingNewData && !this._bWaitMp4Index) {
                // Log.D('lastWriteEnd')
                this.emitter.emit('lastWriteEnd')
            }
        })
    }

    async inputData(chunk) {
        if (!this._bStarted) return
        if (!this._transFlag) {
            this._stash(chunk)
        }
    }

    _write() {
        if (!this._stashBuffer) return
        let stashData = this._stashBuffer.slice(0, this._stashUsed)
        let used = this._fileWriter.length
        // 判断fileSystem空间是否足够写入本次数据
        // let { usedBytes, grantedBytes } = await queryUsageAndQuota(),
        //     spareBytes = grantedBytes - usedBytes
        // Log.D('write', used, stashData, spareBytes)
        this._fileWriter.seek(used)
        this._fileWriter.write(new Blob([stashData]))
        this._writedBytes += stashData.byteLength
        this._stashUsed = 0
    }

    async _onWriteEnd(evt) {
        if (this._stashUsed > 0) {
            // 从缓存中拿数据写入文件
            this._write()
        } else if (this._stashUsed === 0 && this._bStarted) {
            // 等待缓存中有新数据塞入
            this._bWaitingNewData = true
        } else if (this._stashUsed === 0 && this._bWaitMp4Index) {
            // mp4录制，已结束，但是未收到mp4索引
            this._bWaitingNewData = true
        } else {
            // 结束，保存文件
            // Log.D('lastWriteEnd')
            // this.eventsCenter.emit('lastWriteEnd')
        }
    }

    _stash(chunk) {
        if (!this._stashBuffer) {
            this._stashBuffer = new ArrayBuffer(this._stashSize)
        }
        let stashArr = new Uint8Array(this._stashBuffer, 0, this._stashSize)
        stashArr.set(new Uint8Array(chunk, 0), this._stashUsed)
        this._stashUsed += chunk.byteLength
        // 检查是否在等新数据塞入
        if (this._bWaitingNewData) {
            this._bWaitingNewData = false
            this._write()
        }
    }

    destroy(){
        this.emitter.removeAllListeners()
        this.emitter = null;
    }
}
export default FileStorage

// 文件系统-单例
const Singleton = (function () {
    let fileSystem, dirEntry
    return {
        getFileSystem: function () {
            return new Promise(async (resolve, reject) => {
                if (fileSystem) return resolve(fileSystem)
                let { usedBytes, grantedBytes }:any = await FileStorage.queryUsageAndQuota(),
                    spareBytes = grantedBytes - usedBytes
                if (spareBytes < storageSizeDefault) {
                    return reject()
                }
                let requestFileSystemSucCB = (fs) => {
                    // 创建目录，用于清理缓存
                    fs.root.getDirectory(recordBasePath, { create: true }, getDirectorySucCB, errCallback)
                    fileSystem = fs
                }
                let getDirectorySucCB = (de) => {
                    dirEntry = de
                    // 清理之前的录像
                    de.createReader()
                        .readEntries(entries => {
                            if (entries.length === 0) {
                                // return Log.D('Directory /jsp clean.')
                            }
                            // Log.D('Remain fileEntries', entries)
                            entries.forEach(entry => {
                                entry.remove(() => {
                                    // Log.D(entry.name, 'removed.')
                                })
                            })
                        })
                    // Log.D('FileSystem "jsp" directory created', fileSystem, dirEntry)
                    resolve(fileSystem)
                }
                requestFileSystem((Window as any).TEMPORARY, storageSizeDefault, requestFileSystemSucCB, errCallback)
            })
        }
    }
})()

function errCallback(err) {
    console.log(err)
}

function downloadFile(href, fileName) {
    let mouseEvt = document.createEvent('MouseEvents')
    mouseEvt.initEvent('click', false, false)
    let aEl = document.createElement('a')
    aEl.href = href
    aEl.download = fileName
    aEl.dispatchEvent(mouseEvt)
}
