const util = require("util");
const EventEmitter = require("events").EventEmitter;
const chalk = require("chalk");
const Logger = require("../logs/clsLogger").clsLogger;

class clsPlayer {
    constructor({
                    config,
                    utils
                } = {}) {
        this.config = config;
        this.utils = utils;
        this.channel = 0;
        this.soundCard = -1;
        this.bass = require("./basswrapper").basswrapper;
        this.basslib = new this.bass();
        this.logger = new Logger();
        this.position = 0;
        this.duration = 0;
        this.FileName = "";
        this.volume = 0;
        this.mixerChannel = 0;
        this.mixer = 0;
        this.channelCount = 0;
        this.flags = 0;
        this.channelToAdd = 0;
        this.encoderChannel = 0;
        this.initFlags = 0;
        this.streamHealthCheckInterval = 5000;
        this.checkPosition = setInterval(() => {
            if (this.channel != 0) {
                if (
                    this.basslib.BASS_ChannelIsActive(this.channel) ==
                    this.playerStates.BASS_ACTIVE_PLAYING
                ) {
                    let pos = this.Position;
                    let dur = this.Duration;
                    this.emit(this.playerEvents.positionChange, pos, dur, this.channel);
                    pos = null;
                    dur = null;
                }
                if (
                    this.basslib.BASS_ChannelIsActive(this.channel) !=
                    this.playerStates.BASS_ACTIVE_PLAYING
                ) {
                    let state = this.PlayerState;
                    this.emit(this.playerEvents.stateChanged, state);
                }
            }
        }, 1000);

        this.setThreadCount(2);
        this.setBuffer();
        this.vumeterInterval = setInterval(() => {
            if (this.mixer != 0) {
                let vu = this.getVumeter(this.mixer);
                this.emit(this.playerEvents.vumeter, vu.left, vu.right);
            } else if (this.channel != 0) {
                let vu = this.getVumeter(this.channel);
                this.emit(this.playerEvents.vumeter, vu.left, vu.right);
            }
        }, 250);

        this._deviceTypes = {
            line: 'line',
            displayPort: 'displayPort',
            hdmi: 'hdmi',
            headPhones: 'headPhones',
            headSet: 'headSet',
            spdif: 'spdif',
            speakers: 'speakers',
            digital: 'digital',
            network: 'network',
            handset: 'handset',
            mask: 'mask'
        }
    }

    get bassVersion() {
        return this.basslib.BASS_GetVersion();
    }

    get mixerVersion() {
        return this.basslib.BASS_Mixer_GetVersion();
    }

    get encoderVersion() {
        return this.basslib.BASS_Encode_GetVersion();
    }

    get playerEvents() {
        return {
            stateChanged: "stateChanged",
            positionChange: "positionChange",
            fileChanged: "fileChanged",
            playlistFinished: "playlistFinished",
            vumeter: "vumeter",
            fadeInStarted: "fadeInStarted",
            fadeOutStarted: "fadeOutStarted",
            playbackFinished: "playbackFinished",
            Error: "Error",
            announcesStarted: "announcesStarted",
            announcesFinished: "announcesFinished",
            streamPlayerError: "streamPlayerError"
        };
    }

    get PlayerState() {
        return this.basslib.BASS_ChannelIsActive(this.channel);
    }

    get devices() {
        return this.getDevices();
    }

    // set ThreadCount(cnt) {
    //   this.basslib.BASS_SetConfig(this.basslib.BASS_CONFIG_UPDATETHREADS,cnt);
    // }

    get ThreadCount() {
        return this.basslib.BASS_GetConfig(this.basslib.BASS_CONFIG_UPDATETHREADS);
    }

    get EncodeCast() {
        return this.basslib.BASS_ENCODE_NOTIFY_CAST;
    }

    get EncoderDied() {
        return this.basslib.BASS_ENCODE_NOTIFY_ENCODER;
    }

    get EncoderFreed() {
        return this.basslib.BASS_ENCODE_NOTIFY_FREE;
    }

    get channelAttributes() {
        return {
            BASS_ATTRIB_FREQ: 1,
            BASS_ATTRIB_VOL: 2,
            BASS_ATTRIB_PAN: 3,
            BASS_ATTRIB_EAXMIX: 4,
            BASS_ATTRIB_NOBUFFER: 5,
            BASS_ATTRIB_VBR: 6,
            BASS_ATTRIB_CPU: 7,
            BASS_ATTRIB_SRC: 8,
            BASS_ATTRIB_NET_RESUME: 9,
            BASS_ATTRIB_SCANINFO: 10,
            BASS_ATTRIB_MUSIC_AMPLIFY: 0x100,
            BASS_ATTRIB_MUSIC_PANSEP: 0x101,
            BASS_ATTRIB_MUSIC_PSCALER: 0x102,
            BASS_ATTRIB_MUSIC_BPM: 0x103,
            BASS_ATTRIB_MUSIC_SPEED: 0x104,
            BASS_ATTRIB_MUSIC_VOL_GLOBAL: 0x105,
            BASS_ATTRIB_MUSIC_ACTIVE: 0x106,
            BASS_ATTRIB_MUSIC_VOL_CHAN: 0x200, // + channel #
            BASS_ATTRIB_MUSIC_VOL_INST: 0x300 // + instrument #
        };
    }

    get speakerAttributes() {
        let BASS_SPEAKER_FRONT = 0x1000000, // front speakers
            BASS_SPEAKER_REAR = 0x2000000, // rear/side speakers
            BASS_SPEAKER_CENLFE = 0x3000000, // center & LFE speakers (5.1)
            BASS_SPEAKER_REAR2 = 0x4000000, // rear center speakers (7.1)
            BASS_SPEAKER_LEFT = 0x10000000, // modifier: left
            BASS_SPEAKER_RIGHT = 0x20000000; // modifier: right
        return {
            BASS_SPEAKER_FRONT: BASS_SPEAKER_FRONT,
            BASS_SPEAKER_REAR: BASS_SPEAKER_REAR,
            BASS_SPEAKER_CENLFE: BASS_SPEAKER_CENLFE,
            BASS_SPEAKER_REAR2: BASS_SPEAKER_REAR2,
            BASS_SPEAKER_LEFT: BASS_SPEAKER_LEFT,
            BASS_SPEAKER_RIGHT: BASS_SPEAKER_RIGHT,
            BASS_SPEAKER_FRONTLEFT: BASS_SPEAKER_FRONT | BASS_SPEAKER_LEFT,
            BASS_SPEAKER_FRONTRIGHT: BASS_SPEAKER_FRONT | BASS_SPEAKER_RIGHT,
            BASS_SPEAKER_REARLEFT: BASS_SPEAKER_REAR | BASS_SPEAKER_LEFT,
            BASS_SPEAKER_REARRIGHT: BASS_SPEAKER_REAR | BASS_SPEAKER_RIGHT,
            BASS_SPEAKER_CENTER: BASS_SPEAKER_CENLFE | BASS_SPEAKER_LEFT,
            BASS_SPEAKER_LFE: BASS_SPEAKER_CENLFE | BASS_SPEAKER_RIGHT,
            BASS_SPEAKER_REAR2LEFT: BASS_SPEAKER_REAR2 | BASS_SPEAKER_LEFT,
            BASS_SPEAKER_REAR2RIGHT: BASS_SPEAKER_REAR2 | BASS_SPEAKER_RIGHT
        };
    }

    get bassAttributes() {
        return {
            BASS_SAMPLE_DEFAULT: 0,
            BASS_SAMPLE_8BITS: 1, // 8 bit
            BASS_SAMPLE_FLOAT: 256, // 32 bit floating-point
            BASS_SAMPLE_MONO: 2, // mono
            BASS_SAMPLE_LOOP: 4, // looped
            BASS_SAMPLE_3D: 8, // 3D functionality
            BASS_SAMPLE_SOFTWARE: 16, // not using hardware mixing
            BASS_SAMPLE_MUTEMAX: 32, // mute at max distance (3D only)
            BASS_SAMPLE_VAM: 64, // DX7 voice allocation & management
            BASS_SAMPLE_FX: 128, // old implementation of DX8 effects
            BASS_SAMPLE_OVER_VOL: 0x10000, // override lowest volume
            BASS_SAMPLE_OVER_POS: 0x20000, // override longest playing
            BASS_SAMPLE_OVER_DIST: 0x30000, // override furthest from listener (3D only)
            BASS_STREAM_PRESCAN: 0x20000, // enable pin-point seeking/length (MP3/MP2/MP1)
            BASS_MP3_SETPOS: 0x20000,
            BASS_STREAM_AUTOFREE: 0x40000, // automatically free the stream when it stop/ends
            BASS_STREAM_RESTRATE: 0x80000, // restrict the download rate of internet file streams
            BASS_STREAM_BLOCK: 0x100000, // download/play internet file stream in small blocks
            BASS_STREAM_DECODE: 0x200000, // don't play the stream, only decode (BASS_ChannelGetData)
            BASS_STREAM_STATUS: 0x800000 // give server status info (HTTP/ICY tags) in DOWNLOADPROC
        };
    }

    get setConfigAttributes() {
        return {
            BASS_CONFIG_BUFFER: 0,
            BASS_CONFIG_UPDATEPERIOD: 1,
            BASS_CONFIG_GVOL_SAMPLE: 4,
            BASS_CONFIG_GVOL_STREAM: 5,
            BASS_CONFIG_GVOL_MUSIC: 6,
            BASS_CONFIG_CURVE_VOL: 7,
            BASS_CONFIG_CURVE_PAN: 8,
            BASS_CONFIG_FLOATDSP: 9,
            BASS_CONFIG_3DALGORITHM: 10,
            BASS_CONFIG_NET_TIMEOUT: 11,
            BASS_CONFIG_NET_BUFFER: 12,
            BASS_CONFIG_PAUSE_NOPLAY: 13,
            BASS_CONFIG_NET_PREBUF: 15,
            BASS_CONFIG_NET_PASSIVE: 18,
            BASS_CONFIG_REC_BUFFER: 19,
            BASS_CONFIG_NET_PLAYLIST: 21,
            BASS_CONFIG_MUSIC_VIRTUAL: 22,
            BASS_CONFIG_VERIFY: 23,
            BASS_CONFIG_UPDATETHREADS: 24,
            BASS_CONFIG_DEV_BUFFER: 27,
            BASS_CONFIG_VISTA_TRUEPOS: 30,
            BASS_CONFIG_IOS_MIXAUDIO: 34,
            BASS_CONFIG_DEV_DEFAULT: 36,
            BASS_CONFIG_NET_READTIMEOUT: 37,
            BASS_CONFIG_VISTA_SPEAKERS: 38,
            BASS_CONFIG_IOS_SPEAKER: 39,
            BASS_CONFIG_MF_DISABLE: 40,
            BASS_CONFIG_HANDLES: 41,
            BASS_CONFIG_UNICODE: 42,
            BASS_CONFIG_SRC: 43,
            BASS_CONFIG_SRC_SAMPLE: 44,
            BASS_CONFIG_ASYNCFILE_BUFFER: 45,
            BASS_CONFIG_OGG_PRESCAN: 47,
            BASS_CONFIG_MF_VIDEO: 48,
            BASS_CONFIG_AIRPLAY: 49,
            BASS_CONFIG_DEV_NONSTOP: 50,
            BASS_CONFIG_IOS_NOCATEGORY: 51,
            BASS_CONFIG_VERIFY_NET: 52,
            BASS_CONFIG_DEV_PERIOD: 53,
            BASS_CONFIG_FLOAT: 54,
            BASS_CONFIG_NET_SEEK: 56
        };
    }

    get mixerAttributes() {
        return {
            BASS_MIXER_BUFFER: 0x2000, // buffer data for BASS_Mixer_ChannelGetData/Level
            BASS_MIXER_LIMIT: 0x4000, // limit mixer processing to the amount available from this source
            BASS_MIXER_MATRIX: 0x10000, // matrix mixing
            BASS_MIXER_PAUSE: 0x20000, // don't process the source
            BASS_MIXER_DOWNMIX: 0x400000, // downmix to stereo/mono
            BASS_MIXER_NORAMPIN: 0x800000, // don't ramp-in the start
            BASS_MIXER_NONSTOP: this.basslib.BASS_MIXER_NONSTOP
        };
    }

    get playerStates() {
        return {
            BASS_ACTIVE_STOPPED: 0,
            BASS_ACTIVE_PLAYING: 1,
            BASS_ACTIVE_STALLED: 2,
            BASS_ACTIVE_PAUSED: 3
        };
    }

    get Duration() {
        let position = 0;
        if (this.channel == 0) return 0;
        if (
            this.basslib.BASS_ChannelIsActive(this.channel) ==
            this.basslib.BASS_ACTIVE_PLAYING
        ) {
            return this.basslib.BASS_ChannelBytes2Seconds(
                this.channel,
                this.basslib.BASS_ChannelGetLength(this.channel, 0)
            );
        }
        return 0;
    }

    get isSliding() {
        return this.basslib.BASS_ChannelIsSliding(
            this.channel,
            this.basslib.BASS_ATTRIB_VOL
        );
    }

    set Volume(newVolume) {
        if (this.channel == 0) return;
        //console.log("setting volume:" + newVolume + ' for ' + this.channel)
        this.basslib.BASS_ChannelSetAttribute(
            this.channel,
            this.basslib.BASS_ATTRIB_VOL,
            newVolume / 100
        );
    }

    get Volume() {
        if (this.channel == 0) return 0;
        return (
            this.basslib.BASS_ChannelGetAttribute(
                this.channel,
                this.basslib.BASS_ATTRIB_VOL
            ) * 100
        );
    }

    get Position() {
        if (this.channel == 0) return 0;
        if (
            this.basslib.BASS_ChannelIsActive(this.channel) ==
            this.basslib.BASS_ACTIVE_PLAYING
        ) {
            return this.basslib.BASS_ChannelBytes2Seconds(
                this.channel,
                this.basslib.BASS_ChannelGetPosition(this.channel, 0)
            );
        }
        return 0;
    }

    get isPlaying() {
        return (
            this.basslib.BASS_ChannelIsActive(this.channel) ==
            this.basslib.BASS_ACTIVE_PLAYING
        );
    }

    set Position(position) {
        let positionInBytes = this.basslib.BASS_ChannelSeconds2Bytes(
            this.channel,
            position
        );
        this.basslib.BASS_ChannelSetPosition(this.channel, positionInBytes, 0);
        positionInBytes = null;
    }

    //-----------------------------

    getDuration(channel) {
        return this.basslib.BASS_ChannelBytes2Seconds(
            channel,
            this.basslib.BASS_ChannelGetLength(channel, 0)
        );
    }

    setPosition(channel, position) {
        let positionInBytes = this.basslib.BASS_ChannelSeconds2Bytes(
            channel,
            position
        );
        this.basslib.BASS_ChannelSetPosition(channel, positionInBytes, 0);
        positionInBytes = null;
    }

    getIsPlaying(chan) {
        if (chan && chan != 0) {
            if (
                this.basslib.BASS_ChannelIsActive(chan) ==
                this.basslib.BASS_ACTIVE_PLAYING
            ) {
                return true;
            }
            return false;
        }
    }

    setVolume(channel, newVolume) {
        this.basslib.BASS_ChannelSetAttribute(
            channel,
            this.basslib.BASS_ATTRIB_VOL,
            newVolume / 100
        );
    }

    ChannelSlideAttribute(channel, attrib, value, time) {
        this.basslib.BASS_ChannelSlideAttribute(channel, attrib, value, time);
    }

    setThreadCount(cnt) {
        this.basslib.BASS_SetConfig(this.basslib.BASS_CONFIG_UPDATETHREADS, cnt);
        return this.ThreadCount;
    }

    setBuffer() {
        if (this.config) {
            let bufferInt = parseInt(this.config.Settings.Buffer);
            this.basslib.BASS_SetConfig(this.setConfigAttributes.BASS_CONFIG_BUFFER, bufferInt);
        }
    }

    SlideVolume(value, duration) {
        this.ChannelSlideAttribute(
            this.channel,
            this.basslib.BASS_ATTRIB_VOL,
            value,
            duration
        );
    }

    // #region recording

    recordingDevicesInfos() {
        let recordDevices = [];
        let i = 0;
        let info = this.basslib.BASS_RecordGetDeviceInfo(i);
        while (info) {
            let recDevice = {};
            recDevice.index = i;
            recDevice.name = info.name;
            recDevice.driver = info.driver;
            recDevice.IsDefault = (info.flags & this.basslib.BASS_DEVICE_DEFAULT) == this.basslib.BASS_DEVICE_DEFAULT;
            recDevice.enabled = (info.flags & this.basslib.BASS_DEVICE_ENABLED) == this.basslib.BASS_DEVICE_ENABLED;
            recDevice.IsInitialized = (info.flags & this.basslib.BASS_DEVICE_INIT) == this.basslib.BASS_DEVICE_INIT;
            recDevice.IsLineIn = (info.flags & this.basslib.BASS_DEVICE_TYPE_LINE) == this.basslib.BASS_DEVICE_TYPE_LINE;
            recDevice.IsMic = (info.flags & this.basslib.BASS_DEVICE_TYPE_MICROPHONE) == this.basslib.BASS_DEVICE_TYPE_MICROPHONE;
            recordDevices.push(recDevice);
            i++;
            info = this.basslib.BASS_RecordGetDeviceInfo(i);
        }
        console.dir(`Found input devices are: ${JSON.stringify(recordDevices)}`);
        return recordDevices;
    }

    getRecordingDeviceStatus() {
        return this.basslib.BASS_RecordGetDevice();
    }

    initRecordingDevice(index) {
        const recordingDevices = this.recordingDevicesInfos();
        const currentDevice = recordingDevices.filter(recDevice => recDevice.index === index).pop();
        console.dir(`Found current device: ${JSON.stringify(currentDevice)}`);
        if (currentDevice && currentDevice.IsInitialized) {
            this.logger.AudioEvents(`Recording device: ${currentDevice.name} is already initialized`);
            return true;
        }
        const result = this.basslib.BASS_RecordInit(index);
        const error = this.basslib.BASS_ErrorGetCode();

        if (result && !error) {
            let type = currentDevice.IsLineIn ? 'line in' : 'microphone';
            this.logger.AudioEvents(`Recording device ${currentDevice.name}, type: ${type} is initialized successfully`);
            return true;
        }

        if (error !== 0) {
            currentDevice ?
                this.logger.AudioEvents(`Error initializing recording device at index ${index}, name: ${currentDevice.name}, driver: ${currentDevice.driver}, error: ${error}`) :
                this.logger.AudioEvents(`Error initializing recording device at index ${index}, error: ${error}`);
            return false;
        }
    }

    initRecDev(index) {
        let err;
        let recDevice = {};
        try {
            let info = this.basslib.BASS_RecordGetDeviceInfo(index);
            if(info) {
                recDevice.index = index;
                recDevice.name = info.name;
                recDevice.driver = info.driver;
                recDevice.IsDefault = (info.flags & this.basslib.BASS_DEVICE_DEFAULT) == this.basslib.BASS_DEVICE_DEFAULT;
                recDevice.enabled = (info.flags & this.basslib.BASS_DEVICE_ENABLED) == this.basslib.BASS_DEVICE_ENABLED;
                recDevice.IsInitialized = (info.flags & this.basslib.BASS_DEVICE_INIT) == this.basslib.BASS_DEVICE_INIT;
                recDevice.IsLineIn = (info.flags & this.basslib.BASS_DEVICE_TYPE_LINE) == this.basslib.BASS_DEVICE_TYPE_LINE;
                recDevice.IsMic = (info.flags & this.basslib.BASS_DEVICE_TYPE_MICROPHONE) == this.basslib.BASS_DEVICE_TYPE_MICROPHONE;
            }
            if(recDevice.IsInitialized) {
                this.logger.InputPlayer(`${recDevice.name} at index ${recDevice.index} is already init`);
                return true;
            }

            let initResult = this.basslib.BASS_RecordInit(index);
            if(initResult) {
                this.logger.InputPlayer(`Record device: ${recDevice.index} init success`);
                return true;
            }
            err = this.getError();
            this.logger.InputPlayer(`Error initializing recording device at index ${recDevice.index}, err: ${err}`);
            return false;
        } catch (e) {
            this.logger.InputPlayer(`Error initializing recording device: ${JSON.stringify(recDevice)}, err: ${e}`);
            return false;
        }
    }


    recordStart(channel) {

        return this.basslib.BASS_RecordStart(44100, this.channelCount, this.basslib.BASS_RECORD_PAUSE, (handle, len, buffer, user) => {
            this.basslib.BASS_StreamPutData(channel, buffer, len);
            //console.log(buffer, len);
        });
    }


    getMicrophone() {
        const recordingDevices = this.recordingDevicesInfos();
        if (this.utils.isArm && recordingDevices.length > 0) {
            let usb = recordingDevices.filter(c => c.name === 'USB Audio Device: USB Audio');
            if (usb.length > 0) {
                return usb[0];
            }
            return null;
        }
        const micInputs = recordingDevices.filter(device => device.IsMic === true);
        if (micInputs.length !== -1) {
            const defaultMicInput = micInputs.filter(device => device.IsDefault === true).pop();
            // if there's a mic input thats default return it.
            if (defaultMicInput) return defaultMicInput;
            // else return first mic
            return micInputs[0];
        }
        // if there are no mic inputs return null
        return null;
    }

    playChannel(channel) {
        return this.basslib.BASS_ChannelPlay(channel, 0);
    }

    createRecordPlayingChannel(channelCount, flags) {
        this.channelCount = channelCount;
        this.flags = flags;
        return this.basslib.BASS_StreamCreate(44100, channelCount, flags);
    }

    // #endregion

    getDevices() {
        let arr = [];
        arr.push(null);
        let i = 1;
        let info = this.basslib.BASS_GetDeviceInfo(i);
        while (info) {
            let o = new Object();
            o.name = info.name;
            o.driver = info.driver;
            o.flags = info.flags;
            o.enabled =
                (info.flags & this.basslib.BASS_DEVICE_ENABLED) ==
                this.basslib.BASS_DEVICE_ENABLED;
            o.IsDefault =
                (info.flags & this.basslib.BASS_DEVICE_DEFAULT) ==
                this.basslib.BASS_DEVICE_DEFAULT;
            o.IsInitialized =
                (info.flags & this.basslib.BASS_DEVICE_INIT) ==
                this.basslib.BASS_DEVICE_INIT;
            o.typeDigital =
                (info.flags & this.basslib.BASS_DEVICE_TYPE_DIGITAL) ==
                this.basslib.BASS_DEVICE_TYPE_DIGITAL;
            o.typeDisplayPort =
                (info.flags & this.basslib.BASS_DEVICE_TYPE_DISPLAYPORT) ==
                this.basslib.BASS_DEVICE_TYPE_DISPLAYPORT;
            o.typeHandset =
                (info.flags & this.basslib.BASS_DEVICE_TYPE_HANDSET) ==
                this.basslib.BASS_DEVICE_TYPE_HANDSET;
            o.typeHdmi =
                (info.flags & this.basslib.BASS_DEVICE_TYPE_HDMI) ==
                this.basslib.BASS_DEVICE_TYPE_HDMI;
            o.typeHeadPhones =
                (info.flags & this.basslib.BASS_DEVICE_TYPE_HEADPHONES) ==
                this.basslib.BASS_DEVICE_TYPE_HEADPHONES;
            o.typeHeadSet =
                (info.flags & this.basslib.BASS_DEVICE_TYPE_HEADSET) ==
                this.basslib.BASS_DEVICE_TYPE_HEADSET;
            o.typeLine =
                (info.flags & this.basslib.BASS_DEVICE_TYPE_LINE) ==
                this.basslib.BASS_DEVICE_TYPE_LINE;
            o.typeMask =
                (info.flags & this.basslib.BASS_DEVICE_TYPE_MASK) ==
                this.basslib.BASS_DEVICE_TYPE_MASK;
            o.typeMicrophone =
                (info.flags & this.basslib.BASS_DEVICE_TYPE_MICROPHONE) ==
                this.basslib.BASS_DEVICE_TYPE_MICROPHONE;
            o.typeNetwork =
                (info.flags & this.basslib.BASS_DEVICE_TYPE_NETWORK) ==
                this.basslib.BASS_DEVICE_TYPE_NETWORK;
            o.typeSPDIF =
                (info.flags & this.basslib.BASS_DEVICE_TYPE_SPDIF) ==
                this.basslib.BASS_DEVICE_TYPE_SPDIF;
            o.typeSpeakers =
                (info.flags & this.basslib.BASS_DEVICE_TYPE_SPEAKERS) ==
                this.basslib.BASS_DEVICE_TYPE_SPEAKERS;
            o.index = i;
            // if(this.basslib.BASS_Init(i, 44100, 0)) {
            //   o.speakerCount = this.basslib.BASS_GetInfo().speakers;
            // }
            arr.push(o);
            i++;
            info = this.basslib.BASS_GetDeviceInfo(i);
        }

        return arr;
    }

    _getDeviceInfo(info, index) {
        let dev = {};
        dev.name = info.name;
        dev.enabled = (info.flags & this.basslib.BASS_DEVICE_ENABLED) == this.basslib.BASS_DEVICE_ENABLED;
        dev.isDefault = (info.flags & this.basslib.BASS_DEVICE_DEFAULT) == this.basslib.BASS_DEVICE_DEFAULT;
        dev.speakerCount = this.basslib.BASS_GetInfo().speakers;
        dev.index = index;
        this._sortCardType(info, dev);
        return dev;
    }

    _sortCardType(info, dev) {
        if ((info.flags & this.basslib.BASS_DEVICE_TYPE_DIGITAL) == this.basslib.BASS_DEVICE_TYPE_DIGITAL) dev.type = this._deviceTypes.digital;
        if ((info.flags & this.basslib.BASS_DEVICE_TYPE_DISPLAYPORT) == this.basslib.BASS_DEVICE_TYPE_DISPLAYPORT) dev.type = this._deviceTypes.displayPort;
        if ((info.flags & this.basslib.BASS_DEVICE_TYPE_HANDSET) == this.basslib.BASS_DEVICE_TYPE_HANDSET) dev.type = this._deviceTypes.handset;
        if ((info.flags & this.basslib.BASS_DEVICE_TYPE_HDMI) == this.basslib.BASS_DEVICE_TYPE_HDMI) dev.type = this._deviceTypes.hdmi;
        if ((info.flags & this.basslib.BASS_DEVICE_TYPE_HEADPHONES) == this.basslib.BASS_DEVICE_TYPE_HEADPHONES) dev.type = this._deviceTypes.headPhones;
        if ((info.flags & this.basslib.BASS_DEVICE_TYPE_HEADSET) == this.basslib.BASS_DEVICE_TYPE_HEADSET) dev.type = this._deviceTypes.headSet;
        if ((info.flags & this.basslib.BASS_DEVICE_TYPE_LINE) == this.basslib.BASS_DEVICE_TYPE_LINE) dev.type = this._deviceTypes.line;
        if ((info.flags & this.basslib.BASS_DEVICE_TYPE_MASK) == this.basslib.BASS_DEVICE_TYPE_MASK) dev.type = this._deviceTypes.mask;
        if ((info.flags & this.basslib.BASS_DEVICE_TYPE_NETWORK) == this.basslib.BASS_DEVICE_TYPE_NETWORK) dev.type = this._deviceTypes.network;
        if ((info.flags & this.basslib.BASS_DEVICE_TYPE_SPDIF) == this.basslib.BASS_DEVICE_TYPE_SPDIF) dev.type = this._deviceTypes.spdif;
        if ((info.flags & this.basslib.BASS_DEVICE_TYPE_SPEAKERS) == this.basslib.BASS_DEVICE_TYPE_SPEAKERS) dev.type = this._deviceTypes.speakers;
    }

    getExtendedInfo() {
        let sc = parseInt(this.config.Settings.soundCard);
        let arr = [];
        let i = 1;
        let info = this.basslib.BASS_GetDeviceInfo(i);
        while (info) {
            if (sc === -1) {
                let devices = this.getDevices();
                let dev = devices.filter(c => c !== null && c.IsDefault).pop();
                dev ? sc = dev.index : sc = -1;
            }
            if (sc !== i) {
                try {
                    if (this.basslib.BASS_Init(i, 44100, 0)) {
                        let dev = this._getDeviceInfo(info, i);
                        this.basslib.BASS_Free();
                        arr.push(dev);
                    }
                } catch (e) {
                    this.logger.AudioEvents(`Error fetching sound card device info, card index: ${i}, err: ${e.message}`);
                }
            } else {
                let dev = this._getDeviceInfo(info, i);
                arr.push(dev);
            }
            i++;
            info = this.basslib.BASS_GetDeviceInfo(i);
        }
        return arr;
    }

    //  getExtendedInfo() {
    //   let arr = [];
    //   let i = 1;
    //   let info = this.basslib.BASS_GetDeviceInfo(i);
    //   while (info) {
    //       if(this.basslib.BASS_Init(i, 44100, 0)) {
    //         let dev = {};
    //
    //         dev.name = info.name;
    //         dev.enabled = (info.flags & this.basslib.BASS_DEVICE_ENABLED) == this.basslib.BASS_DEVICE_ENABLED;
    //         dev.isDefault = (info.flags & this.basslib.BASS_DEVICE_DEFAULT) == this.basslib.BASS_DEVICE_DEFAULT;
    //         dev.speakerCount = this.basslib.BASS_GetInfo().speakers;
    //         dev.index = i;
    //         arr.push(dev);
    //         this.basslib.BASS_Free();
    //       }
    //       i++;
    //       info = this.basslib.BASS_GetDeviceInfo(i);
    //   }
    //   return  arr;
    // }

    getDevice(device) {
        let devs = this.getDevices();
        if (device == -1) {
            for (let i = 1; i < devs.length; i++) {
                if (devs[i].IsDefault) {
                    return devs[i];
                }
            }
        }
        return devs[device];
    }

    getError() {
        return this.basslib.BASS_ErrorGetCode();
    }

    init() {
        if (this.soundCard != 0 && this.initFlags != 0) {
            return this.basslib.BASS_Init(this.soundCard, 44100, this.initFlags);
        }
    }

    streamFree(channel) {
        return this.basslib.BASS_StreamFree(channel);
    }

    recordFree(channel) {
        return this.basslib.BASS_RecordFree(channel);
    }

    initSoundCard(soundCard, initFlags) {
        this.soundCard = soundCard;
        this.initFlags = initFlags;
        let dev = this.getDevice(this.soundCard);
        if (!dev.IsInitialized) {
            let result = this.basslib.BASS_Init(this.soundCard, 44100, initFlags);
            let err = this.basslib.BASS_ErrorGetCode();
            if (result) {
                this.logger.System(chalk.bgGreen(this.soundCard, "init success"));
            } else {
                this.logger.System(
                    chalk.red(`Error initializing ${this.soundCard}, exiting. err:${err}`));
                process.exit(4);
                //console.log(chalk.bgRed('Error initializing: ', this.getError()));
            }
            return result;
        } else {
            this.logger.AudioEvents(
                chalk.bgGreen("sound card", this.soundCard, "is already init")
            );
            return 1;
        }
    }

    openThirdPartyFile(fname, flags) {
        this.flags = flags;
        this.channel = this.basslib.BASS_StreamCreateFile(
            0,
            fname,
            0,
            0,
            this.flags
        );
        let sync = this.basslib.BASS_ChannelSetSync(
            this.channel,
            this.basslib.BASS_SYNC_END,
            0,
            (handle, channel, data, user) => {
                if (handle == sync) {
                    clearInterval(this.checkPosition);
                    this.basslib.BASS_ChannelRemoveSync(this.channel, sync);
                    let streamFree = this.basslib.BASS_StreamFree(this.channel);
                    //console.log('song finished: ', streamFree);
                    this.emit(this.playerEvents.playbackFinished, this.channel);
                }
            }
        );
        return this.channel;
    }

    openFile(fname, flags) {
        this.flags = flags;
        this.channel = this.basslib.BASS_StreamCreateFile(
            0,
            fname,
            0,
            0,
            this.flags
        );
        return this.channel;
    }

    openUrl(url, flags) {
        this.flags = flags;
        this.channel = this.basslib.BASS_StreamCreateURL(url, 0, this.flags, 0, 0);
        return this.channel;
    }

    openStreamUrl(url, flags) {
        this.flags = flags;
        this.channel = this.basslib.BASS_StreamCreateURL(url, 0, this.flags, 0, 0);
        let sync = this.basslib.BASS_ChannelSetSync(
            this.channel,
            this.basslib.BASS_SYNC_END,
            0,
            (handle, channel, data, user) => {
                if (handle == sync) {
                    clearInterval(this.checkPosition);
                    this.basslib.BASS_ChannelRemoveSync(this.channel, sync);
                    let streamFree = this.basslib.BASS_StreamFree(this.channel);
                    //console.log('song finished: ', streamFree);
                    this.emit(this.playerEvents.playbackFinished, this.channel);
                }
            }
        );
        this.isStreamPlaying = setInterval(() => {
            if (this.channel != 0) {
                if (!this.getIsPlaying(this.channel)) {
                    this.emit(this.playerEvents.streamPlayerError);
                }
            }
        }, this.streamHealthCheckInterval);
        return this.channel;
    }

    PlayMixer() {
        let res = this.basslib.BASS_ChannelPlay(this.mixer, 0);
        //console.log(res)
        return res;
    }

    Stop() {
        clearInterval(this.checkPosition);
        this.basslib.BASS_ChannelStop(this.channel);
    }

    StopChannel(chan) {
        clearInterval(this.checkPosition);
        this.basslib.BASS_ChannelStop(chan);
    }

    StopThirdParty(chan) {
        this.basslib.BASS_ChannelStop(chan);
    }

    FadeAndStop(chan, fadePeriod) {
        clearInterval(this.checkPosition);
        this.basslib.BASS_ChannelSlideAttribute(
            chan,
            this.basslib.BASS_ATTRIB_VOL,
            0,
            fadePeriod
        );
        setTimeout(() => {
            this.basslib.BASS_ChannelStop(chan);
        }, fadePeriod);
    }

    Pause() {
        let result = this.basslib.BASS_ChannelPause(this.channel);
        if (this.getError()) {
            //console.log(this.getError());
        }
    }

    Resume() {
        let result = this.basslib.BASS_ChannelPlay(this.channel, 0);
        if (this.getError()) {
            //console.log(this.getError());
        }
    }

    Play() {
        let result = this.basslib.BASS_ChannelPlay(this.channel);
        let endPos = this.Duration;
        let currPos = this.basslib.BASS_ChannelSeconds2Bytes(this.channel, endPos);
        let sync = this.basslib.BASS_ChannelSetSync(
            this.channel,
            this.basslib.BASS_SYNC_END,
            0,
            (handle, channel, data, user) => {
                if (handle == sync) {
                    clearInterval(this.checkPosition);
                    this.basslib.BASS_ChannelRemoveSync(this.channel, sync);
                    let streamFree = this.basslib.BASS_StreamFree(this.channel);
                    //console.log('song finished: ', streamFree);
                    this.emit(this.playerEvents.playbackFinished, this.channel);
                }
            }
        );
        return result;
    }

    PlayFadeIn(volume, fadeDur) {
        let startPos = this.basslib.BASS_ChannelSetSync(
            this.channel,
            this.basslib.BASS_SYNC_POS,
            0,
            (handle, channel, data, user) => {
                this.Volume = 0;
                this.basslib.BASS_ChannelSlideAttribute(
                    this.channel,
                    this.basslib.BASS_ATTRIB_VOL,
                    volume / 100,
                    fadeDur
                );
                //console.log('fade in started, current Vol:', this.Volume);
                this.emit(this.playerEvents.fadeInStarted, this.mixer);
                setTimeout(() => {
                    this.basslib.BASS_ChannelRemoveSync(this.channel, startPos);
                    //console.log('fade in finished, current Vol:', this.Volume)
                }, fadeDur + 1000);
            }
        );

        let result = this.basslib.BASS_ChannelPlay(this.mixer, 0);
        let durBytes = this.basslib.BASS_ChannelGetLength(this.channel, 0);
        let dur = this.basslib.BASS_ChannelBytes2Seconds(this.channel, durBytes);
        let lastFadeSecs = parseInt(dur - fadeDur / 1000) * 1;
        let currentPos = this.basslib.BASS_ChannelSeconds2Bytes(
            this.channel,
            lastFadeSecs
        );
        let endPos = this.basslib.BASS_ChannelSetSync(
            this.channel,
            this.basslib.BASS_SYNC_POS,
            currentPos,
            (handle, channel, data, user) => {
                this.basslib.BASS_ChannelSlideAttribute(
                    this.channel,
                    this.basslib.BASS_ATTRIB_VOL,
                    0,
                    fadeDur
                );
                //console.log('fade out started, current Vol: ', this.Volume);
                this.emit(this.playerEvents.fadeOutStarted, this.channel);
                setTimeout(() => {
                    this.basslib.BASS_ChannelRemoveSync(this.channel, endPos);
                    //console.log('fade out finished, new Vol: ', this.Volume);
                }, fadeDur + 1000);
            }
        );

        let sync = this.basslib.BASS_ChannelSetSync(
            this.channel,
            this.basslib.BASS_SYNC_END,
            0,
            (handle, channel, data, user) => {
                if (handle == sync) {
                    clearInterval(this.checkPosition);
                    this.basslib.BASS_ChannelRemoveSync(this.channel, sync);
                    this.emit(this.playerEvents.playbackFinished, this.channel);
                    let streamFree = this.basslib.BASS_StreamFree(this.channel);
                    //console.log('song finished: ', streamFree);
                }
            }
        );
    }

    setFadeIn(channelToAdd, volume, fadeDur) {
        let startPos = this.basslib.BASS_ChannelSetSync(
            channelToAdd,
            this.basslib.BASS_SYNC_POS,
            0,
            (handle, channel, data, user) => {
                this.basslib.BASS_ChannelSetAttribute(
                    channelToAdd,
                    this.basslib.BASS_ATTRIB_VOL,
                    0
                );

                //this.basslib.BASS_ChannelSlideAttribute(channelToAdd, this.basslib.BASS_ATTRIB_VOL, 0.8,5000);
                //check here
                this.basslib.BASS_ChannelSlideAttribute(
                    channelToAdd,
                    this.basslib.BASS_ATTRIB_VOL,
                    volume / 100,
                    fadeDur
                );
                //console.log('fade in started, current Vol: ', this.getVolume(channelToAdd));
                this.emit(this.playerEvents.fadeInStarted, channelToAdd);
                setTimeout(() => {
                    this.basslib.BASS_ChannelRemoveSync(channelToAdd, startPos);
                    //console.log('fade in finished, new Vol: ', this.getVolume(channelToAdd));
                }, fadeDur + 1000);
            }
        );
    }

    setFadeOut(channelToAdd, fadeDur) {
        let dur = this.basslib.BASS_ChannelBytes2Seconds(
            channelToAdd,
            this.basslib.BASS_ChannelGetLength(channelToAdd, 0)
        );
        let lastFadeSecs = parseInt(dur - fadeDur / 1000) * 1;
        let currentPos = this.basslib.BASS_ChannelSeconds2Bytes(
            channelToAdd,
            lastFadeSecs
        );
        let endPos = this.basslib.BASS_ChannelSetSync(
            channelToAdd,
            this.basslib.BASS_SYNC_POS,
            currentPos,
            (handle, channel, data, user) => {
                this.basslib.BASS_ChannelSlideAttribute(
                    channelToAdd,
                    this.basslib.BASS_ATTRIB_VOL,
                    0,
                    fadeDur
                );
                //console.log('fade out started, current Vol: ', this.getVolume(channelToAdd));
                this.emit(this.playerEvents.fadeOutStarted, channelToAdd);
                setTimeout(() => {
                    this.basslib.BASS_ChannelRemoveSync(channelToAdd, endPos);
                    //console.log('fade out finished, new Vol: ', this.getVolume(channelToAdd));
                }, fadeDur + 1000);
            }
        );

        let sync = this.basslib.BASS_ChannelSetSync(
            this.channel,
            this.basslib.BASS_SYNC_END,
            0,
            (handle, channel, data, user) => {
                clearInterval(this.checkPosition);
                this.basslib.BASS_ChannelRemoveSync(this.channel, sync);
                this.emit(this.playerEvents.playbackFinished, this.channel);
                let streamFree = this.basslib.BASS_StreamFree(this.channel);
                //console.log('song finished: ', streamFree);
            }
        );
    }

    setFadeOutForAnnounce(channelToFade, fadeDuration) {
        return this.basslib.BASS_ChannelSlideAttribute(
            channelToFade,
            this.basslib.BASS_ATTRIB_VOL,
            0,
            fadeDuration
        );
    }

    setFadeInForAnnounce(channelToFade, fadeDuration) {
        return this.basslib.BASS_ChannelSlideAttribute(
            channelToFade,
            this.basslib.BASS_ATTRIB_VOL,
            1,
            fadeDuration
        );
    }

    createMixer(channelCount, flags) {
        this.channelCount = channelCount;
        this.flags = flags;
        this.mixer = this.basslib.BASS_Mixer_StreamCreate(
            44100,
            this.channelCount,
            this.flags
        );
        return this.mixer;
    }

    addChannelToMixer(channelToAdd, flags) {
        this.channelToAdd = channelToAdd;
        this.flags = flags;
        let result = this.basslib.BASS_Mixer_StreamAddChannel(
            this.mixer,
            this.channelToAdd,
            this.flags
        );

        return result;
    }

    clearPositionEvent() {
        clearInterval(this.checkPosition);
    }

    getVolume(channel) {
        this.channel = channel;
        if (channel == 0) {
            return 0;
        }
        return (
            this.basslib.BASS_ChannelGetAttribute(
                this.channel,
                this.basslib.BASS_ATTRIB_VOL
            ) * 100
        );
    }

    getCpu(channel) {
        if (channel != 0) {
            return this.basslib.BASS_ChannelGetAttribute(
                channel,
                this.basslib.BASS_ATTRIB_CPU
            );
        }
    }

    getLength(channel) {
        return this.basslib.BASS_ChannelGetLength(channel, 0);
    }

    streamFree(channel) {
        return this.basslib.BASS_StreamFree(channel);
    }

    getVumeter(mixer) {
        let level = this.basslib.BASS_Mixer_ChannelGetLevel(mixer);
        let tmp = {};
        tmp.left = this.basslib.BASS_ChannelGetLeftLevel(level);
        tmp.right = this.basslib.BASS_ChannelGetRightLevel(level);
        level = null;
        return tmp;
    }

    loadPlugin(pluginName) {
        let os = require("os");
        let path = require("path");
        let platform = os.platform();
        let plugin = `bass_${pluginName}`;
        let fs = require("fs");
        switch (platform) {
            case "win32":
                plugin = plugin + ".dll";
                break;
            case "linux":
                plugin = "lib" + plugin + ".so";
                break;
            case "darwin":
                plugin = "lib" + plugin + ".dylib";
                break;
        }
        let pluginPath = path.join(process.cwd(), "plugins", plugin);
        let exists = fs.existsSync(pluginPath);
        if (exists) {
            let result = this.basslib.BASS_PluginLoad(pluginPath);
            let err = this.getError();
            if (err != 0) {
                this.logger.System(`${plugin} result: ${result}, err: ${err}`);
            }
            return result;
        } else {
            let pluginPath = path.join(process.cwd(), plugin);
            this.logger.System(
                `${plugin} doesnt exist in plugins folder, looking for it in current working directory`
            );
            let exists = fs.existsSync(pluginPath);
            if (exists) {
                let result = this.basslib.BASS_PluginLoad(pluginPath);
                let err = this.getError();
                if (err != 0) {
                    this.logger.System(`${plugin} result: ${result}, err: ${err}`);
                }
                return result;
            }
        }
        this.logger.System(
            `${plugin} could not be found in plugins folder and current working directory`
        );
    }

    //------- icecast

    iceCastEncoder(mixer, lameOptions) {
        this.mixer = mixer;
        this.encoderChannel = this.basslib.BASS_Encode_Start(
            this.mixer,
            lameOptions,
            this.basslib.BASS_ENCODE_NOHEAD
        );
        return this.encoderChannel;
    }

    encodeStop(encoder) {
        let stop = this.basslib.BASS_Encode_Stop(encoder);
        return stop;
    }

    encodeCastInit(
        encoder,
        serverAdress,
        serverPass,
        serverName,
        url,
        genre,
        desc,
        serverHeader,
        sampleRate,
        isPublic
    ) {
        let init = this.basslib.BASS_Encode_CastInit(
            encoder,
            serverAdress,
            serverPass,
            this.basslib.BASS_ENCODE_TYPE_MP3,
            serverName,
            url,
            genre,
            desc,
            serverHeader,
            sampleRate,
            isPublic
        );
        return init;
    }

    isEncoderActive(encoder) {
        let status = this.basslib.BASS_Encode_IsActive(encoder);
        return status;
    }

    setStreamTitle(encoder, title) {
        if (encoder != 0) {
            this.basslib.BASS_Encode_CastSetTitle(encoder, title, null);
        }
    }

    setNotify(chan, proc) {
        let status = this.basslib.BASS_Encode_SetNotify(chan, proc);
        return status;
    }

    //------- icecast
}

util.inherits(clsPlayer, EventEmitter);
module.exports = {
    clsPlayer: clsPlayer
};
