const Player = require('./clsPlayer').clsPlayer;
//const Logger = require('../logs/clsLogger').clsLogger;
const Downloader = require('../downloader/Downloader').Downloader;
const thirdPartyDB = require('../db/thirdPartyDB');
const fs = require('fs');
const path = require('path');
const _ = require('lodash');
const DiskUsage = require('../utils/DiskUsage');
const prettyBytes = require('pretty-bytes');
const moment = require('moment');
require('linqjs');

const thirdPartyPlayer = class thirdPartyPlayer {
    constructor({ db, utils, config, moment, musicVolume = 100, decodeOnly = false, logger, push } = {}) {
        this.db = db;
        this.utils = utils;
        this.config = config;
        this.moment = moment;
        this.logger = logger;
        this.musicVolume = musicVolume;
        this.decodeOnly = decodeOnly;
        this.push = push;
        this.mixerVolume = musicVolume;
        this.diskUsage = new DiskUsage();
        this.oneMegaByte = 1024 * 1024;
        this.minDiskSpace = 20 * this.oneMegaByte; // 20 megabytes
        this.downloadArr = [];
        this.isPlaying = false;
        this.currentFile = null;
        this.channel = 0;
        this.mixer = 0;
        this.checkTimeOut = 5000;
        this.downloadValidArr = [];
        this.downloadPath = path.join(utils.SchedulesSaveDir, 'thirdParty');

        if (!fs.existsSync(this.downloadPath)) {
            fs.mkdirSync(this.downloadPath);
        }

        this.options = {
            directory: this.downloadPath,
            filename: '',
            speed: 0,
            useTiming: true,
            startTime: '00:00:01',
            endTime: '23:59:59',
            downloadTimeout: 20000,
            useproxy: parseInt(config.proxy.proxyEnabled),
            proxyuser: config.proxy.proxyUser,
            proxypass: config.proxy.proxyPass,
            proxyport: config.proxy.proxyPort,
            proxyhost: config.proxy.proxyHost
        };
        this.eventTypes = {
            requestRecieved: 'requestRecieved',
            thirdPartyPlaying: 'thirdPartyPlaying',
            thirdPartyStopped: 'thirdPartyStopped',
            vumeter: 'vumeter',
            positionChange: 'positionChange',
            downloadProgress: 'downloadProgress',
            downloadError: 'downloadError',
            playError: 'playError'
        };

        this.tpdb = new thirdPartyDB({ db, moment, config, utils, logger });
        this.downloader = new Downloader({ options: this.options });
        this.tpdb.on(this.tpdb.eventTypes.dataEmpty, () => {
            this.deleteThirdPartyFiles();
        });

        this.push.on(this.push.eventsTypes.ThirdPartyApiKeys, async (data) => {
           this.tpdb.InsertCustomerKeys(data);
        });

        let tPlayer = new Player();
        this._tPlayer = tPlayer;
        this.streamUrl = '';
        this.activeFile = '';

        tPlayer.on(tPlayer.playerEvents.vumeter, (left, right) => {
            if(this.isPlaying) {
                this.emit(this.eventTypes.vumeter, left, right);
            }
        });

        tPlayer.on(tPlayer.playerEvents.positionChange, (pos, dur, chan) => {
            this.emit(tPlayer.playerEvents.positionChange, pos, dur, chan);
        });

        tPlayer.initSoundCard(this.config.Settings.soundCard, tPlayer.speakerAttributes.BASS_DEVICE_SPEAKERS);
        let tPlayerFlags = tPlayer.bassAttributes.BASS_SAMPLE_FLOAT;
        let speaker = tPlayer.bassAttributes.BASS_SAMPLE_DEFAULT;
        let channelCount = 2;
        this.addChannelFlags = tPlayer.mixerAttributes.BASS_MIXER_BUFFER;
        if (this.config.MultiOutput.isEnabled) {
            switch (parseInt(this.config.MultiOutput.Channel)) {
                case 1:
                    speaker = tPlayer.speakerAttributes.BASS_SPEAKER_FRONT;
                    break;
                case 2:
                    speaker = tPlayer.speakerAttributes.BASS_SPEAKER_REAR;
                    break;
                case 3:
                    speaker = tPlayer.speakerAttributes.BASS_SPEAKER_CENLFE;
                    channelCount = 1;
                    break;
                case 4:
                    speaker = tPlayer.speakerAttributes.BASS_SPEAKER_REAR2;
                    break;
                case 5:
                    speaker = tPlayer.speakerAttributes.BASS_SPEAKER_LEFT;
                    channelCount = 1;
                    this.addChannelFlags = tPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | tPlayer.mixerAttributes.BASS_MIXER_BUFFER;
                    break;
                case 6:
                    speaker = tPlayer.speakerAttributes.BASS_SPEAKER_RIGHT;
                    channelCount = 1;
                    this.addChannelFlags = tPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | tPlayer.mixerAttributes.BASS_MIXER_BUFFER;
                    break;
                case 7:
                    speaker = tPlayer.speakerAttributes.BASS_SPEAKER_FRONTLEFT;
                    channelCount = 1;
                    this.addChannelFlags = tPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | tPlayer.mixerAttributes.BASS_MIXER_BUFFER;
                    break;
                case 8:
                    speaker = tPlayer.speakerAttributes.BASS_SPEAKER_FRONTRIGHT;
                    channelCount = 1;
                    this.addChannelFlags = tPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | tPlayer.mixerAttributes.BASS_MIXER_BUFFER;
                    break;
                case 9:
                    speaker = tPlayer.speakerAttributes.BASS_SPEAKER_REARLEFT;
                    channelCount = 1;
                    this.addChannelFlags = tPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | tPlayer.mixerAttributes.BASS_MIXER_BUFFER;
                    break;
                case 10:
                    speaker = tPlayer.speakerAttributes.BASS_SPEAKER_REARRIGHT;
                    channelCount = 1;
                    this.addChannelFlags = tPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | tPlayer.mixerAttributes.BASS_MIXER_BUFFER;
                    break;
                case 11:
                    speaker = tPlayer.speakerAttributes.BASS_SPEAKER_CENTER;
                    channelCount = 1;
                    this.addChannelFlags = tPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | tPlayer.mixerAttributes.BASS_MIXER_BUFFER;
                    break;
                case 12:
                    speaker = tPlayer.speakerAttributes.BASS_SPEAKER_LFE;
                    channelCount = 1;
                    this.addChannelFlags = tPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | tPlayer.mixerAttributes.BASS_MIXER_BUFFER;
                    break;
                case 13:
                    speaker = tPlayer.speakerAttributes.BASS_SPEAKER_REAR2LEFT;
                    channelCount = 1;
                    this.addChannelFlags = tPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | tPlayer.mixerAttributes.BASS_MIXER_BUFFER;
                    break;
                case 14:
                    speaker = tPlayer.speakerAttributes.BASS_SPEAKER_REAR2RIGHT;
                    channelCount = 1;
                    this.addChannelFlags = tPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | tPlayer.mixerAttributes.BASS_MIXER_BUFFER;
                    break;
                default:
                    channelCount = 2;
                    break;
            }
        }
        if (this.decodeOnly) {
            speaker = tPlayer.bassAttributes.BASS_STREAM_DECODE;
        }

        let mixer = tPlayer.createMixer(channelCount, speaker);
        this.mixer = mixer;
        tPlayer.setVolume(mixer, this.mixerVolume);
    }

    async gotCustomerKeys(data) {
        await this.tpdb.InsertCustomerKeys(data);
    }

    async gotThirdPartyFiles(data) {
        await this.tpdb.InsertThirdPartyFiles(data);
    }

    checkFreeSpace() {
        let freeSpace = this.diskUsage.freeSpaceSync();
        if (freeSpace < this.minDiskSpace) {
            this.logger.ThirdParty('Not enough disk space! available: ' + prettyBytes(freeSpace) + ' needed ' + prettyBytes(this.minDiskSpace));
            return false;
        } else {
            return true;
        }
    }

    async addToDownload(arr) {
        this.emit(this.eventTypes.requestRecieved);
        if (!arr || arr.length == 0) return;
        await arr.forEach(c => this.downloadArr.push(c.FileName));
        this.downloadValidArr = arr;
        this.downloadNext();
    }

    async downloadNext() {
        if (!this.checkFreeSpace()) {
            setTimeout(() => {
                this.downloadNext();
            }, this.checkTimeOut);
            return;
        }
        let fileObj = this.downloadArr.shift();
        if (fileObj == undefined) {
            this.logger.ThirdParty('no 3rd party file to download');
            return;
        }
        this.currentFile = fileObj;
        this.downloader.downloadFile(this.currentFile, false, async (pageStatus, pageMessage, filename) => {
            this.logger.ThirdParty(`finished downloading ${filename}, page status: ${pageStatus}`);
            setTimeout(() => {
                this.downloadNext();
            }, 500);
        }, (filename, percentage, currentStr, durationStr, speed, current, length) => {
            let fileControl = this.downloadValidArr.where(c => c.FileName == filename);
            if(fileControl[0].FileSize != length) {
                this.downloader.response.req.abort();
                let q = _.castArray({ FileName: filename });
                this.utils.deleteFilesArray(q, this.options.directory);
                setTimeout(() => {
                    this.downloadNext();
                }, 500);
                return;
            }
            this.emit(this.eventTypes.downloadProgress, filename, percentage, currentStr, durationStr, speed, current, length);
        }, (msg, file) => {
            this.emit(this.eventTypes.downloadError, msg, file);
            //delete file with error
            let q = _.castArray({ FileName: file });
            this.utils.deleteFilesArray(q, this.options.directory);
            if (msg != 404) {
                this.downloadArr.push(fileObj);
                this.logger.ThirdParty(`Error downloading file: ${file}, err: ${msg}, re-added it to download que`);
            } else {
                this.logger.ThirdParty(`Error downloading file: ${file}, err: ${msg}`);
            }
            setTimeout(() => {
                this.downloadNext();
            }, 500)
        });
    }

    async toBePlayed(song) {
        let obj = song[0];
        this.emit(this.eventTypes.requestRecieved, obj);
        this.logger.ThirdParty(`Play request recieved: ${obj.FileName}, to be played after ${obj.playAt} seconds.`);
        let filename = obj.FileName.split('/').pop();
        let size = obj.FileSize;
        let timeToPlay = obj.playAt * 1000; //convert seconds to ms.
        let localFiles = await this.utils.getFilesInfo(this.downloadPath);
        let fileToPlay = _.find(localFiles, (c) => { return filename == c.file && size == c.size });
        let p = new Player();
        this.player = p;
        p.on(p.playerEvents.playbackFinished, () => {
            this.isPlaying = false;
            this.emit(this.eventTypes.thirdPartyStopped);
        });

        this.player.on(p.playerEvents.positionChange,(position, duration, chan)=>{
            let pos = moment({ hour: 0, minute: 0, second: 0 }).second(position).format('mm:ss');
            let dur = moment({ hour: 0, minute: 0, second: 0 }).second(duration).format('mm:ss');
            this.emit(this.eventTypes.positionChange, pos, dur, chan);
        });

        // p.on(p.playerEvents.stateChanged,(state)=>{
        //     console.log(`third party state changed:${state}`)
        //     if (state==0){
        //         this.player.Stop()
        //         this.isPlaying = false;
        //         this.emit(this.eventTypes.thirdPartyStopped);
        //     }
        // })

        if (fileToPlay == undefined) {
            //stream

            let header = await this.downloader.getHeaders({downloadUrl:obj.FileName});
            this.logger.ThirdParty(`we dont have ${obj.FileName} in drive, opening stream player`);
            let chan = p.openStreamUrl(obj.FileName, this._tPlayer.bassAttributes.BASS_STREAM_DECODE);
            let dur = p.getDuration(chan);
            let streamLength = p.getLength(chan);
            if(header.length != obj.FileSize){
                this.logger.ThirdParty(`stream length and json file size doesnt match, will not play. stream: ${header.length}, json filesize: ${obj.FileSize}`);
                this.isPlaying = false;
                this.emit(this.eventTypes.thirdPartyStopped);
                return;
            }
            let err = parseInt(p.getError());
            if (chan == 0 || err != 0) {
                this.isPlaying = false;
                this.emit(this.eventTypes.thirdPartyStopped);
                this.logger.ThirdParty(`Error opening stream ${obj.FileName}, err: ${err}`);
                this.emit(this.eventTypes.playError, err);
                return;
            }

            setTimeout(() => {
                if(this.isPlaying && (this.channel != 0 && chan != this.channel)) {
                    this.player.StopThirdParty(this.channel);
                }
                this.channel = chan;
                this.isPlaying = true;
                this.emit(this.eventTypes.thirdPartyPlaying);
                this.logger.ThirdParty('Starting 3rd party stream now');
                let result = this._tPlayer.addChannelToMixer(chan, this.addChannelFlags);
            }, timeToPlay);


        } else {
            // play from local
            this.logger.ThirdParty(`we have ${filename} in drive, will play in ${obj.playAt} seconds`);
            let filePath = path.join(this.downloadPath, filename);
            let chan = p.openThirdPartyFile(filePath, p.bassAttributes.BASS_STREAM_DECODE | p.bassAttributes.BASS_SAMPLE_FLOAT | p.bassAttributes.BASS_STREAM_PRESCAN);
            let dur = p.getDuration(chan);
            let err = parseInt(p.getError());
            if (chan == 0 || err != 0) {
                this.isPlaying = false;
                this.emit(this.eventTypes.thirdPartyStopped);
                this.logger.ThirdParty(`Error opening ${filePath}, err: ${err}`);
                this.emit(this.eventTypes.playError, err);
                return;
            }

            setTimeout(() => {
                if(this.isPlaying && (this.channel != 0 && chan != this.channel)) {
                    this.player.StopThirdParty(this.channel);
                }
                this.channel = chan;
                this.isPlaying = true;
                this.emit(this.eventTypes.thirdPartyPlaying);
                this.logger.ThirdParty('Starting 3rd party from drive now.');
                let result = this._tPlayer.addChannelToMixer(chan, this.addChannelFlags);
            }, timeToPlay);
        }
    }


    /*
      {"apiKey":"123456","cmd":1,"data":{"FileName":"http://cdn.smg.com.tr/song.mp3","FileSize":55555,"playAt":3}}
      {"apiKey":"123456","cmd":2,"data":[{"FileName":"http://cdn.smg.com.tr/song.mp3","FileSize":55555},{"FileName":"http://cdn.smg.com.tr/song2.mp3","FileSize":66666}]}
     */
    async requestParser(obj) {

        // download test obj {"apiKey":"123456","cmd":2,"data":[{"FileName":"http://cdn.smg.com.tr/e9e36b4b-f032-4940-bcf2-f1e704dea97d.mp3","FileSize":55555},{"FileName":"http://cdn.smg.com.tr/song2.mp3","FileSize":66666}]}
        // play test obj {"apiKey":"123456","cmd":1,"data":[{"FileName":"http://link.smg.com.tr/processed1.mp4", "FileSize": 55555, "playAt": 3}]}
        let dbData = await this.tpdb.getCustomerKeysDetails();
        //console.log(dbData);
        let auth = dbData.where(c => c.AuthKey == obj.apiKey);
        if (!auth || auth.length == 0) return { status: 514, msg: 'invalid auth key' };
        let customerID = auth[0].customerID;
        let cmds = {
            PlayFile: 1,
            DownloadFiles: 2
        };
        //first check apikey
        //then check cmd
        let isArray = _.isArray(obj.data);
        if (!isArray) {
            this.arr = _.castArray(obj.data);
        } else {
            this.arr = obj.data;
        }

        switch (obj.cmd) {
            case cmds.PlayFile:

                let valid = this.objValidation(this.arr);
                if (valid == null) return { status: 513, msg: 'data is missing a value or wrong type' };
                this.toBePlayed(valid);
                return { status: 200, msg: 'file successfully queued' };
                break;
            case cmds.DownloadFiles:

                let downloadValid = this.downloadValidation(this.arr);
                if (downloadValid) {
                    this.addToDownload(downloadValid);
                    let formattedData = this.dataFormatter(downloadValid, customerID);
                    this.gotThirdPartyFiles(formattedData);
                    return { status: 200, msg: 'files successfully added to download queue' };
                }
                return { status: 513, msg: 'files arent valid. either filename or filesize is wrong type' };
                break;
            default:
                return { status: 500, msg: 'command not found' };
                break;
        }

        let o = { status: 200, msg: 'ok' };
        return o
    }

    dataFormatter(data, customerID) {
        if(data) {
            data.forEach((c) => {
                c.customerID = customerID;
                c.FileName = c.FileName.split('/').pop();
            });
            return data;
        }
    }

    objValidation(data) {
        if(!data || data.length == 0) return;
        let validForm = {
            fname: '',
            fsize: 0,
            playAt: 0
        };
        let length = Object.keys(validForm).length;
        let q = [];
        data.forEach((c) => {
            if (typeof validForm.fname == typeof c.FileName && typeof validForm.fsize == typeof c.FileSize && typeof validForm.playAt == typeof c.playAt && this.getobjLength(validForm) == this.getobjLength(c)) {
                q.push(c);
            }
        });
        if (q.length > 0) return q;
        return null;
    }

    getobjLength(obj) {
        if(obj) return Object.keys(obj).length;
    }

    downloadValidation(data) {

        let validForm = {
            fname: '',
            fsize: 0
        };
        let q = [];
        data.forEach(c => {
            if (typeof validForm.fname == typeof c.FileName && typeof validForm.fsize == typeof c.FileSize && this.getobjLength(validForm) == this.getobjLength(c)) {
                q.push(c);
            }
        });
        if (q.length > 0) return q;
        return null;
    }

    async deleteThirdPartyFiles() {
        let files = await this.utils.getFilesInfo(this.downloadPath);
        let q = [];
        files.forEach((c) => {
            q.push({FileName: c.file})
        });
        this.utils.deleteFilesArray(q, this.downloadPath);
        files = null;
        q = null;
    }
};
const util = require('util'), EventEmitter = require('events').EventEmitter;
util.inherits(thirdPartyPlayer, EventEmitter);
module.exports = thirdPartyPlayer;
