define(['jquery',
    'core/log',
    'core/ajax',
    'mod_minilesson/definitions',
    'mod_minilesson/pollyhelper',
    'mod_minilesson/ttrecorder',
    'mod_minilesson/animatecss',
], function($,  log, Ajax, def, polly, ttrecorder, anim) {
  "use strict"; // jshint ;_;

  /*
  This is for the speech test helper
   */

  log.debug('MiniLesson Speech Test Helper: initialising');

var app = {
    passmark: 90,
    pointer: 1,
    jsondata: null,
    props: null,
    dryRun: false,
    language: 'en-US',
    terms: [],
    phonetics: [],
    displayterms: [],
    results: [],
    controls: {},
    ttrec: null, //a handle on the tt recorder


    init: function(props) {
      var dd = this;

      //pick up opts from html
      var theid='#amdopts_' + props.widgetid;
      var configcontrol = $(theid).get(0);
      if(configcontrol){
        dd.activitydata = JSON.parse(configcontrol.value);
        $(theid).remove();
      }else{
        //if there is no config we might as well give up
        log.debug('MiniLesson Speech Test helper: No config found on page. Giving up.');
        return;
      }

          this.init_polly();
          this.init_controls();
          this.initComponents();
          this.register_events();
    },
    init_polly: function() {
        //get the polly token
        var pollytoken = this.activitydata.token;
        var pollyregion = this.activitydata.region;
        var pollycloudpoodllurl = this.activitydata.cloudpoodllurl;
        var pollyowner = 'poodll';
        polly.init(pollytoken, pollyregion, pollyowner, pollycloudpoodllurl);
        log.debug('polly initialised');
    },

    init_controls: function() {
            log.debug('sthelper init controls');
          app.controls = {};
          app.controls.pollybutton = $("#speechtester_pollybutton");
          app.controls.pollyvoice = $("#speechtester_voice");
          app.controls.pollylanguage = $("#speechtester_language");
          app.controls.pollytext = $("#speechtester_text");
          app.controls.audioplayer = $("#speechtester_audioplayer");
          app.controls.transcribebutton = $("#speechtester_transcribebutton");
          app.controls.transcription = $("#speechtester_transcription");
          app.controls.transcriptioncoverage = $("#speechtester_transcription_coverage");
          app.controls.stt_guided = $("#speechtester_stt_guided");
          app.controls.forcestreaming = $("#speechtester_forcestreaming");
          app.controls.recorder = $("#uniqueidforspeechtester_recorderdiv");
    },

        register_events: function() {
            log.debug('sthelper register events');

            //polly button
          app.controls.pollybutton.on('click',function() {
              log.debug('pollybutton clicked');
              log.debug(app.controls.pollytext.val());
              log.debug(app.controls.pollyvoice.val());
                polly.fetch_polly_url(app.controls.pollytext.val(),'text', app.controls.pollyvoice.val()).then(function(audiourl) {
                    app.controls.audioplayer.attr('src',audiourl);
                    log.debug(audiourl);
                });
          });
          //transcribe button
            app.controls.transcribebutton.on('click',function() {
                log.debug('transcribebutton clicked');
                log.debug(app.controls.audioplayer.attr('src'));
                app.downloadAndSubmitMP3(app.controls.audioplayer.attr('src'),app.doTranscribe);
            });
        },


    // Function to download the MP3 file and submit it for transcription
    downloadAndSubmitMP3: function(url, submitFunction) {
        // Fetch the MP3 file from the URL
        fetch(url)
            .then(response => {
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }
                // Log the response headers for debugging
                log.debug('Response headers:', response.headers);
                return response.blob();
            })
            .then(blob => {
                // Check the blob type for debugging
                log.debug('Blob size:', blob.size);
                log.debug('Blob type:', blob.type);
                if (!blob.type.startsWith('audio/')) {
                    throw new Error('The fetched file is not an audio file.');
                }
                // Call the submit function with the Blob as an argument
                app.convertMP3ToWAV(blob).then(wavblob => {
                    submitFunction(wavblob)
                });
            })
            .catch(error => {
                log.debug('Error downloading MP3:', error);
            });
    },

    doTranscribe: function(blob) {
        //clear the existing results
       // app.controls.transcriptioncoverage.html('');
        //init the transcription results div
        app.controls.transcription.html('<i class="fa fa-spinner fa-spin" style="font-size:24px;"></i>');
        app.controls.transcriptioncoverage.html('');
        app.controls.transcription.show();

        //set the scorer if we have one
        var scorer = app.controls.recorder.data('passagehash');
        //build the form data
        var bodyFormData = new FormData();
        var blobname = Math.floor(Math.random() * 100) +  '.mp3';
        var guided = app.controls.stt_guided.prop('checked')==true;
        log.debug('guided is: ' + guided);
        var prompt = app.controls.pollytext.val();
        bodyFormData.append('audioFile', blob, blobname);
        bodyFormData.append('scorer', scorer);
        if(guided) {
            bodyFormData.append('strictmode', 'false');
        }else{
            bodyFormData.append('strictmode', 'true');
        }
        //prompt is used by whisper and other transcibers down the line
        if(guided) {
            bodyFormData.append('prompt', app.controls.pollytext.val());
        }
        bodyFormData.append('lang', app.controls.pollylanguage.val());
        bodyFormData.append('wwwroot', M.cfg.wwwroot);

        var oReq = new XMLHttpRequest();
        oReq.open("POST", app.activitydata.asrurl, true);
        oReq.onUploadProgress= function(progressEvent) {};
        oReq.onload = function(oEvent) {
            if (oReq.status === 200) {
                var respObject = JSON.parse(oReq.response);
                if(respObject.data.hasOwnProperty('transcript')) {
                    var transcript = respObject.data.transcript;
                    app.controls.transcription.text(transcript);
                    //do a passage comparison
                    app.comparePassageToTranscript(prompt,transcript).then(function(ajaxresult) {
                        var comparison = JSON.parse(ajaxresult);
                        if (comparison) {
                            var allCorrect = comparison.filter(function(e){return !e.matched;}).length==0;
                            var coverage = comparison.filter(function(e){return e.matched;}).length/comparison.length;
                            coverage = coverage * 100;
                            coverage = Math.round(coverage);
                            var tc_report = 'All correct: ' + allCorrect + '<br>';
                            tc_report += 'Coverage: ' + coverage + '%<br>';
                            if(coverage<100){
                                $.each(comparison, function (index, value) {
                                    if (!value.matched) {
                                        tc_report += 'unmatched word: ' + value.word + '<br>';
                                        //var start = value.start;
                                        //var end = value.end;
                                    }
                                });
                            }
                            app.controls.transcriptioncoverage.html(tc_report);
                        }
                    });
                    
                }else{
                    app.controls.transcription.text("no transcript was in the result");
                }
            } else {
                app.controls.transcription.text( "error");
                log.debug(oReq.error);
            }
            app.controls.transcription.show();
        };
        try {
            oReq.send(bodyFormData);

        }catch(err){
            app.controls.transcription.text( "error");
            log.debug(err);
        }
    },

    comparePassageToTranscript: function (passage,transcript){
        return Ajax.call([{
            methodname: 'mod_minilesson_compare_passage_to_transcript',
            args: {
                passage: passage,
                transcript: transcript,
                alternatives: '',
                phonetic: '',
                language: app.controls.pollylanguage.val(),
                region: app.activitydata.region,
                cmid: app.activitydata.cmid
            },
            async: false
        }])[0];
    },

    convertMP3ToWAV: function (mp3Blob){
        return new Promise((resolve, reject) => {
            const reader = new FileReader();

            reader.onload = (event) => {
                const arrayBuffer = event.target.result;
                const audioContext = new (window.AudioContext || window.webkitAudioContext)();

                audioContext.decodeAudioData(arrayBuffer)
                    .then((audioBuffer) => {
                        const numberOfChannels = audioBuffer.numberOfChannels;
                        const length = audioBuffer.length * numberOfChannels;
                        const sampleRate = audioBuffer.sampleRate;
                        const buffer = audioContext.createBuffer(numberOfChannels, audioBuffer.length, sampleRate);

                        for (let channel = 0; channel < numberOfChannels; channel++) {
                            const channelData = audioBuffer.getChannelData(channel);
                            buffer.copyToChannel(channelData, channel);
                        }

                        const wavBlob = app.bufferToWave(buffer);
                        resolve(wavBlob);
                    })
                    .catch((error) => {
                        reject(error);
                    });
            };
            reader.onerror = (error) => {
                reject(error);
            }
            reader.readAsArrayBuffer(mp3Blob);
        });
    },

    bufferToWave: function(audioBuffer) {
        const numberOfChannels = audioBuffer.numberOfChannels;
        const sampleRate = audioBuffer.sampleRate;
        const length = audioBuffer.length * numberOfChannels * 2 + 44;
        const buffer = new ArrayBuffer(length);
        const view = new DataView(buffer);
        let pos = 0;

        const writeString = (view, offset, string) => {
            for (let i = 0; i < string.length; i++) {
                view.setUint8(offset + i, string.charCodeAt(i));
            }
        };

        const writeUint32 = (view, offset, value) => {
            view.setUint32(offset, value, true);
        };

        const writeUint16 = (view, offset, value) => {
            view.setUint16(offset, value, true);
        };

        writeString(view, pos, 'RIFF'); pos += 4;
        writeUint32(view, pos, length - 8); pos += 4;
        writeString(view, pos, 'WAVE'); pos += 4;
        writeString(view, pos, 'fmt '); pos += 4;
        writeUint32(view, pos, 16); pos += 4;
        writeUint16(view, pos, 1); pos += 2;
        writeUint16(view, pos, numberOfChannels); pos += 2;
        writeUint32(view, pos, sampleRate); pos += 4;
        writeUint32(view, pos, sampleRate * numberOfChannels * 2); pos += 4;
        writeUint16(view, pos, numberOfChannels * 2); pos += 2;
        writeUint16(view, pos, 16); pos += 2;
        writeString(view, pos, 'data'); pos += 4;
        writeUint32(view, pos, length - pos - 4); pos += 4;

        function floatTo16BitPCM(output, offset, input) {
            for (let i = 0; i < input.length; i++, offset += 2) {
                const s = Math.max(-1, Math.min(1, input[i]));
                output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
            }
        }

        for (let channel = 0; channel < numberOfChannels; channel++) {
            floatTo16BitPCM(view, pos, audioBuffer.getChannelData(channel));
            pos += audioBuffer.length * 2;
        }

        return new Blob([buffer], { type: 'audio/wav' });
    },

     initComponents: function() {

              var theCallback = function(message) {

                switch (message.type) {
                  case 'recording':
                   app.controls.transcription.html('<i class="fa fa-spinner fa-spin" style="font-size:24px;"></i>');
                   break;

                  case 'speech':
                    var speechtext = message.capturedspeech;
                    log.debug('speechtext:',speechtext);
                    app.controls.transcription.text(speechtext);
                    app.controls.transcription.show();

                } //end of switch message type
              };

              //init tt recorder
              var opts = {};
              opts.uniqueid = app.activitydata.uniqueid;
              opts.callback = theCallback;
              opts.stt_guided= app.controls.stt_guided.prop('checked')==true;
              app.ttrec = ttrecorder.clone();
              app.ttrec.init(opts);

        },


      }; //end of app definition
      return app;


});