const util = require('util');
const EventEmitter = require('events').EventEmitter;
require('linqjs');
let Player=require("./clsPlayer").clsPlayer;
let _ = require('lodash');
let rootPath=process.cwd();
let path=require('path');
let moment=require('moment');
//let uid = require('gen-uid');
let chalk=require('chalk');
const PlaylistManager=class PlaylistManager{
    constructor({moment,utils,config,
                    isLooping=true,
                    shuffle=true,
                    autoStart=true,
                    minFileCountToPlay=3,
                    fadeInEnabled=true,
                musicVolume=100,decodeOnly=true}={}){
        this.moment=moment;
        this.utils=utils;
        this.config=config;
        this.shuffle=shuffle;
        this.isLooping=isLooping;
        this.autoStart=autoStart;
        this.minFileCountToPlay=minFileCountToPlay;
        this.fadeInEnabled=fadeInEnabled;
        this.musicVolume=musicVolume;
        this.mixerVolume=musicVolume;
        this.decodeOnly=decodeOnly;
        this.index=-1;
        this.totalFiles=0;
        this.activeFile=null;
        this.fadePeriod=4000;
        this.lastSongIndex=0;
        this.path=path.join(rootPath,'files');
        this.oldPlayer=null;
        this.playerEvents = {
            stateChanged: 'stateChanged',
            positionChange: 'positionChange',
            fileChanged: 'fileChanged',
            playlistFinished: 'playlistFinished',
            fadeInStarted: 'fadeInStarted',
            fadeOutStarted: 'fadeOutStarted',
            vumeter: 'vumeter',
            Error: 'error'
        };

        this.lastPlayedSongOnDB="";
       
        let mixerPlayer=new Player();
        this._mixerPlayer=mixerPlayer;


        mixerPlayer.initSoundCard(this.config.Settings.soundCard,mixerPlayer.basslib.BASS_DEVICE_SPEAKERS);

        //get multi output settings from config

        //default settings
        let mixerFlags=mixerPlayer.bassAttributes.BASS_SAMPLE_FLOAT;
        let speaker=mixerPlayer.bassAttributes.BASS_SAMPLE_DEFAULT;
        let channelCount=2;
        this.addChannelFlags=mixerPlayer.bassAttributes.BASS_STREAM_DECODE;
        //check config  mono out and which channel
        if(this.config.MultiOutput.isEnabled){
            switch (parseInt(this.config.MultiOutput.Channel)){
                case 1:
                    speaker = mixerPlayer.speakerAttributes.BASS_SPEAKER_FRONT;
                    break;
                case 2:
                    speaker = mixerPlayer.speakerAttributes.BASS_SPEAKER_REAR;
                    break;
                case 3:
                    speaker = mixerPlayer.speakerAttributes.BASS_SPEAKER_CENLFE;
                    channelCount=1;
                    break;
                case 4:
                    speaker = mixerPlayer.speakerAttributes.BASS_SPEAKER_REAR2;
                    break;
                case 5:
                    speaker = mixerPlayer.speakerAttributes.BASS_SPEAKER_LEFT;
                    channelCount=1;
                    this.addChannelFlags= mixerPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | mixerPlayer.bassAttributes.BASS_STREAM_DECODE;
                    break;
                case 6:
                    speaker = mixerPlayer.speakerAttributes.BASS_SPEAKER_RIGHT;
                    channelCount=1;
                    this.addChannelFlags= mixerPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | mixerPlayer.bassAttributes.BASS_STREAM_DECODE;
                    break;
                case 7:
                    speaker = mixerPlayer.speakerAttributes.BASS_SPEAKER_FRONTLEFT;
                    channelCount=1;
                    this.addChannelFlags= mixerPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | mixerPlayer.bassAttributes.BASS_STREAM_DECODE;
                    break;
                case 8:
                    speaker = mixerPlayer.speakerAttributes.BASS_SPEAKER_FRONTRIGHT;
                    channelCount=1;
                    this.addChannelFlags= mixerPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | mixerPlayer.bassAttributes.BASS_STREAM_DECODE;
                    break;
                case 9:
                    speaker = mixerPlayer.speakerAttributes.BASS_SPEAKER_REARLEFT;
                    channelCount=1;
                    this.addChannelFlags= mixerPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | mixerPlayer.bassAttributes.BASS_STREAM_DECODE;
                    break;
                case 10:
                    speaker = mixerPlayer.speakerAttributes.BASS_SPEAKER_REARRIGHT;
                    channelCount=1;
                    this.addChannelFlags= mixerPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | mixerPlayer.bassAttributes.BASS_STREAM_DECODE;
                    break;
                case 11:
                    speaker = mixerPlayer.speakerAttributes.BASS_SPEAKER_CENTER;
                    channelCount=1;
                    this.addChannelFlags= mixerPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | mixerPlayer.bassAttributes.BASS_STREAM_DECODE;
                    break;
                case 12:
                    speaker = mixerPlayer.speakerAttributes.BASS_SPEAKER_LFE;
                    channelCount=1;
                    this.addChannelFlags= mixerPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | mixerPlayer.bassAttributes.BASS_STREAM_DECODE;
                    break;
                case 13:
                    speaker = mixerPlayer.speakerAttributes.BASS_SPEAKER_REAR2LEFT;
                    channelCount=1;
                    this.addChannelFlags= mixerPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | mixerPlayer.bassAttributes.BASS_STREAM_DECODE;
                    break;
                case 14:
                    speaker = mixerPlayer.speakerAttributes.BASS_SPEAKER_REAR2RIGHT;
                    channelCount=1;
                    this.addChannelFlags= mixerPlayer.mixerAttributes.BASS_MIXER_DOWNMIX | mixerPlayer.bassAttributes.BASS_STREAM_DECODE;
                    break;
                default:
                    channelCount=2;
                    break;
            }
        }

        if(this.decodeOnly){
            speaker=mixerPlayer.bassAttributes.BASS_STREAM_DECODE;

        }
        //create the mixer
        let mixer=mixerPlayer.createMixer(channelCount,speaker);
        this.mixer=mixer;
        mixerPlayer.setVolume(mixer,this.mixerVolume);
        //mixerPlayer.setVolume(mixer,0)
        //var res=mixerPlayer.basslib.BASS_ChannelSlideAttribute(mixer, mixerPlayer.basslib.BASS_ATTRIB_VOL , 0,5000);
        let err=mixerPlayer.getError();

        //show vumeter
        if(!this.decodeOnly) {
            setInterval(() => {
                let vu=mixerPlayer.getVumeter(mixer);
                //console.log(`vumeter left:${vu.left} right:${vu.right}`)
                this.emit(this.playerEvents.vumeter,vu.left,vu.right)
            }, 250)
        }

        this.files=[];

        if(this.autoStart && !this.decodeOnly){
            //start Mixer
            mixerPlayer.PlayMixer();
        }
    }

    setMixerVolume(newVolume){
        this.mixerVolume=newVolume;
        this._mixerPlayer.setVolume(mixer,this.mixerVolume);
    }
    setMusicVolume(newVolume){
            //todo: fade music volume
            this.musicVolume=newVolume;
            if(this.oldPlayer==null)return;
            let m=this.oldPlayer;
            this.oldPlayer.setVolume(this.oldPlayer.channel,this.musicVolume);

    }

    addFiles(newFiles,lastSong){
        /*
        this.files.push(newFiles);
        this.files = _.flattenDeep(this.files);
         */

        for(let i=0;i<newFiles.length;i++){
            this.files.push(newFiles[i]);
        }
        this.lastPlayedSongOnDB=lastSong?lastSong.FileName:'';
        this.setLastSong(lastSong);
        //todo: if is already playing, then stop and streamfree it.
      /*  if (this.oldPlayer!=null) {
            if (this.oldPlayer.isPlaying) {
                this.oldPlayer.FadeAndStop(1000);
            }
        }*/
        if(this.autoStart){

            if (this.oldPlayer!=null) {
                if (!this.oldPlayer.isPlaying) {
                    this.Play();
                }else{
                    console.log('already playing , just added new files')
                }
            }else{
                this.Play();
            }
        }
    }

    addFile(newFile){
        this.files.push(newFile);
    }

    clearFiles(){
        this.files=[];
    }

    setLastSong(song){
        if(!song){
            this.lastSongIndex=-1;
            return;
        }
        let index=_.findIndex(this.files,(o)=>{return o.FileName==song.FileName});
        if(index>=0){
            this.lastSongIndex=index;
        }else{
            console.log('this playlist is never played before')
        }

    }

    setFileStatus(filename,status){
        //get the first index
            let index=_.findIndex(this.files, (o)=> { return o.FileName == filename; });
            while (index>-1){
                this.files[index].isDownloaded=status;
                //find the index in the files with same name
                index=_.findIndex(this.files, (o)=> { return o.FileName == filename; },++index);
            }
    }

    Play(){
        let files=this.files.where(c=>{return c.isDownloaded==true});

        if(files.length<this.minFileCountToPlay){
            //wait till enough files to play
            console.log(`waiting for ${this.minFileCountToPlay} , now we have ${files.length}`);
            setTimeout(()=>{this.Play()},2000);
            return;
        }

        ++this.lastSongIndex;
        this.lastSongIndex = (this.lastSongIndex % this.files.length);
        let index= _.findIndex(this.files, (o) => { return o.isDownloaded  &&  !o.isPlayed },this.lastSongIndex);
        //todo: announce player gets stuck here. write a play method for announces?
        //todo: if index -1 or there is no file in this.files, handle it!

      if (this.lastPlayedSongOnDB == this.files[ index ].FileName) {
        this.files[ index ].isPlayed = true;
        //this means the last played file on db is same with the selected index.
        ++this.lastSongIndex;
        //clear it.. we dont use it anymore
        this.lastPlayedSongOnDB = '';
        setTimeout(() => {
          this.Play();
        }, 500);
        return;
      }

        // if index=-1 then all files are played,
        //  now reset all of them.
        if(index == -1){
            this.lastSongIndex = 0;
           console.log('played all files');
            this.emit(this.playerEvents.playlistFinished);

            if(this.isLooping){
                this.files.forEach(c => c.isPlayed=false);
                // check if shuffle
                if(this.shuffle){
                    console.log('shuffling playlist');
                    // another fast shuffling
                    // this.files=this.files.orderBy(c=>uid.token(true))
                    this.files=_.shuffle(this.files);
                    console.log('shuffle completed')
                }
                setTimeout(() => { this.Play() },500);
            }
            return;
        }

        this.files[index].isPlayed=true;

        this.activeFile=this.files[index];

        let p=new Player();
        p.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");
            console.log("position ", pos,'-',dur);

            this.emit(this.playerEvents.positionChange,this.activeFile,pos,dur,position,duration);
            pos=null;
            dur=null;
        });

        p.on(p.playerEvents.fadeOutStarted,(chan)=>{
            this.emit(this.playerEvents.fadeOutStarted,chan);
            console.log("Calling next song");
            p.clearPositionEvent();
            this.Play();
        });

        p.on(p.playerEvents.playbackFinished,(chan)=>{
            console.log("finished:",chan);
            if(!this.fadeInEnabled) {
                setTimeout(()=>{this.Play()},500);
                return;
            }
        });

        let chan = p.openFile(path.join(this.path, this.activeFile.FileName), p.bassAttributes.BASS_STREAM_DECODE);

        if(chan==0 || parseInt( p.getError())!=0){
            this.emit(this.playerEvents.Error,p.getError(),"Error opening file");
            setTimeout(()=>{this.Play()},500);
            return;
        }
        //this.emit(this.eventTypes.fileChanged,chan,this.activeFile.FileName,this.activeFile);

        this.oldPlayer=p;
        //check if fadein/fadeout enabled
        if(this.fadeInEnabled) {
            p.setVolume(chan,0);
            p.setFadeIn(chan, this.musicVolume,this.fadePeriod);
            //p.setFadeIn(chan, 10,this.fadePeriod)
            p.setFadeOut(chan, this.fadePeriod)
        }else{
            p.setVolume(chan,this.musicVolume);
            p.Play()
        }

        let result= this._mixerPlayer.addChannelToMixer(chan,this.addChannelFlags);
        let duration=p.Duration;
        console.log(`Playing index: ${index}/${this.files.length}, ID:${this.activeFile.ID} - ${this.activeFile.FileName}, duration:${moment({hour: 0, minute: 0, second: 0}).second(duration).format("mm:ss")}`);
        this.emit(this.playerEvents.fileChanged,chan,this.activeFile.FileName,this.activeFile, duration, index);
        //console.log(`error:${p.getError()}`);

        //todo: test goto last 10 second , dont forget to remove this

        // if(!this.utils.isCompiled) {
        //
        //     setTimeout(() => {
        //         console.log(chalk.bgBlue.yellow( 'we are on dev mode so lets jump to last 10 secs'));
        //         p.setPosition(chan, duration - 10)
        //     }, 8000)
        // }
    }
};

util.inherits(PlaylistManager, EventEmitter);
module.exports = PlaylistManager;