define(['jquery', 'core/log', 'mod_minilesson/ttwavencoder', 'mod_minilesson/ttstreamer', 'mod_minilesson/ttiflytek', 'mod_minilesson/ttazure'],
    function ($, log, wavencoder, audiostreamer, iflytekstreamer, azurestreamer) {
        "use strict"; // jshint ;_;
        /*
        This file is the engine that drives audio rec and canvas drawing. TT Recorder is the just the glory kid
         */

        log.debug('TT Audio Helper initialising');

        return {
            encodingconfig: null,
            streamer: null,
            encoder: null,
            microphone: null,
            isRecording: false,
            audioContext: null,
            processor: null,
            uniqueid: null,
            alreadyhadsound: false, //only start silence detection after we got a sound. Silence detection is end of speech.
            silencecount: 0, //how many intervals of consecutive silence so far
            silenceintervals: 15, //how many consecutive silence intervals (100ms) = silence detected
            silencelevel: 25, //below this volume level = silence
            enablesilencedetection: true,

            // wav config for encoding to wav
            wavconfig: {
                bufferLen: 4096,
                numChannels: 2,
                desiredSampleRate: 48000,
                mimeType: 'audio/wav'
            },
            //streaming config for encoding to pcm and later base64
            // TO DO: wav config might work just as well. test.
            streamingconfig: {
                bufferLen: 4096,
                numChannels: 1,
                desiredSampleRate: 16000,
                mimeType: 'audio/wav'
            },

            //for making multiple instances
            clone: function () {
                return $.extend(true, {}, this);
            },


            init: function (waveHeight, uniqueid, therecorder) {

                this.waveHeight = waveHeight;
                this.uniqueid = uniqueid;
                this.therecorder = therecorder;
                this.region = therecorder.region;
                if (this.therecorder.is_streaming) {
                    this.encodingconfig = this.streamingconfig;
                } else {
                    this.encodingconfig = this.wavconfig;
                }
                this.prepare_html();
                window.AudioContext = window.AudioContext || window.webkitAudioContext;
            },

            onStop: function () { },
            onStream: function () { },
            onSocketReady: function () { },
            onError: function () { },
            onfinalspeechcapture: function (speechtext) { },
            oninterimspeechcapture: function (speechtext) { },


            prepare_html: function () {
                this.canvas = $('#' + this.uniqueid + "_waveform");
                this.canvasCtx = this.canvas[0].getContext("2d");
            },

            start: function () {

                var that = this;

                // Audio context
                this.audioContext = new AudioContext(
                    {
                        sampleRate: this.encodingconfig.desiredSampleRate
                    });

                this.processor = this.audioContext.createScriptProcessor(
                    this.encodingconfig.bufferLen,
                    this.encodingconfig.numChannels,
                    this.encodingconfig.numChannels);

                this.processor.connect(this.audioContext.destination);


                var gotStreamMethod = function (stream) {

                    that.isRecording = true;
                    that.tracks = stream.getTracks();

                    //lets check the noise suppression and echo reduction on these
                    for (var i = 0; i < that.tracks.length; i++) {
                        var track = that.tracks[i];
                        if (track.kind == "audio") {
                            var settings = track.getSettings();
                            if (settings.noiseSuppression) {
                                log.debug("Noise Suppression is on");
                            } else {
                                log.debug("Noise Suppression is off");
                            }
                            if (settings.echoCancellation) {
                                log.debug("Echo Cancellation is on");
                            } else {
                                log.debug("Echo Cancellation is off");
                            }
                        }
                    }

                    // Create a MediaStreamAudioSourceNode for the microphone
                    that.microphone = that.audioContext.createMediaStreamSource(stream);

                    // Connect the AudioBufferSourceNode to the gainNode
                    that.microphone.connect(that.processor);

                    //if we have a streaming transcriber we need to initialize it
                    if (that.therecorder.is_streaming) {
                        if (that.therecorder.speechtokentype === 'iflytek') {
                            that.streamer = iflytekstreamer.clone();
                        } else if (that.therecorder.speechtokentype === 'azure') {
                            that.streamer = azurestreamer.clone();
                        } else {
                            that.streamer = audiostreamer.clone();
                        }
                        that.streamer.init(that.therecorder.speechtoken, that);
                        that.enablesilencedetection = false;
                    }
                    //Alert TT recorder that we are ready to go (it will do visuals and manage state of recorder)
                    that.onStream(stream);

                    // Init WAV encoder
                    that.encoder = wavencoder.clone();
                    that.encoder.init(that.audioContext.sampleRate, that.encodingconfig.numChannels);

                    // Give the node a function to process audio events
                    that.processor.onaudioprocess = function (event) {
                        var thebuffers = that.getBuffers(event);
                        that.encoder.audioprocess(thebuffers);
                        if (that.streamer) {
                            that.streamer.audioprocess(thebuffers);
                        }
                    };

                    that.listener = that.audioContext.createAnalyser();
                    that.microphone.connect(that.listener);
                    that.listener.fftSize = 2048; // 256

                    that.bufferLength = that.listener.frequencyBinCount;
                    that.analyserData = new Uint8Array(that.bufferLength);
                    that.volumeData = new Uint8Array(that.bufferLength);

                    //reset canvas and silence detection
                    that.canvasCtx.clearRect(0, 0, that.canvas.width() * 2, that.waveHeight * 2);
                    that.alreadyhadsound = false;
                    that.silencecount = 0;

                    that.interval = setInterval(function () {
                        that.drawWave();
                        that.detectSilence();
                    }, 100);

                };

                //for ios we need to do this to keep playback volume high
                if ("audioSession" in navigator) {
                    navigator.audioSession.type = 'play-and-record';
                    console.log("AudioSession API is supported");
                }

                // Mic permission
                navigator.mediaDevices.getUserMedia({
                    audio: true,
                    video: false
                }).then(gotStreamMethod).catch(this.onError);
            },

            stop: function () {
                var that = this;
                clearInterval(this.interval);
                this.canvasCtx.clearRect(0, 0, this.canvas.width() * 2, this.waveHeight * 2);
                this.isRecording = false;
                this.silencecount = 0;
                this.alreadyhadsound = false;
                this.therecorder.update_audio('isRecording', false);
                //we set a timeout to allow the audiocontext buffer to fill up since we can't flush it
                //if we don't we may miss 1s of audio at the end
                setTimeout(function () {
                    //we check audiocontext is not in an odd state before closing
                    //superclickers can get it in an odd state
                    if (that.audioContext !== null && that.audioContext.state !== "closed") {
                        that.audioContext.close();
                    }
                    that.processor.disconnect();
                    that.tracks.forEach(track => track.stop());
                    that.onStop(that.encoder.finish());
                    if (that.streamer) {
                        that.streamer.finish();
                    }
                }, 1000);

            },

            getBuffers: function (event) {
                var buffers = [];
                for (var ch = 0; ch < this.encodingconfig.numChannels; ++ch) {
                    buffers[ch] = event.inputBuffer.getChannelData(ch);
                }
                return buffers;
            },

            detectSilence: function () {

                if (!this.enablesilencedetection) { return; }

                this.listener.getByteFrequencyData(this.volumeData);

                let sum = 0;
                for (var vindex = 0; vindex < this.volumeData.length; vindex++) {
                    sum += this.volumeData[vindex] * this.volumeData[vindex];
                }

                var volume = Math.sqrt(sum / this.volumeData.length);
                // log.debug("volume: " + volume + ', hadsound: ' + this.alreadyhadsound);
                //if we already had a sound, we are looking for end of speech
                if (volume < this.silencelevel && this.alreadyhadsound) {
                    this.silencecount++;
                    if (this.silencecount >= this.silenceintervals) {
                        this.therecorder.silence_detected();
                    }
                    //if we have a sound, reset silence count to zero, and flag that we have started
                } else if (volume > this.silencelevel) {
                    this.alreadyhadsound = true;
                    this.silencecount = 0;
                }
            },

            drawWave: function () {

                var width = this.canvas.width() * 2;
                this.listener.getByteTimeDomainData(this.analyserData);

                this.canvasCtx.fillStyle = '#F5F5FE';
                this.canvasCtx.fillRect(0, 0, width, this.waveHeight * 2);

                this.canvasCtx.lineWidth = 5;
                this.canvasCtx.strokeStyle = 'gray';
                this.canvasCtx.beginPath();

                var slicewaveWidth = width / this.bufferLength;
                var x = 0;

                for (var i = 0; i < this.bufferLength; i++) {

                    var v = this.analyserData[i] / 128.0;
                    var y = v * this.waveHeight;

                    if (i === 0) {
                        // this.canvasCtx.moveTo(x, y);
                    } else {
                        this.canvasCtx.lineTo(x, y);
                    }

                    x += slicewaveWidth;
                }

                this.canvasCtx.lineTo(width, this.waveHeight);
                this.canvasCtx.stroke();

            }
        }; //end of this declaration


    });