var CodeOceanEditor = {
  //ACE-Editor-Path
  // ruby part adds the relative_url_root, if it is set.
  ACE_FILES_PATH: '<%= (defined? Rails.application.config.relative_url_root) && Rails.application.config.relative_url_root != nil && Rails.application.config.relative_url_root != "" ? Rails.application.config.relative_url_root : "" %>' + '/assets/ace/',
  THEME: 'ace/theme/textmate',

  //Color-Encoding for Percentages in Progress Bars (For submissions)
  ADEQUATE_PERCENTAGE: 50,
  SUCCESSFULL_PERCENTAGE: 90,

  //Key-Codes (for Hotkeys)
  ALT_R_KEY_CODE: 174,
  ALT_S_KEY_CODE: 8218,
  ALT_T_KEY_CODE: 8224,
  ENTER_KEY_CODE: 13,

  //Request-For-Comments-Configuration
  REQUEST_FOR_COMMENTS_DELAY: 3 * 60 * 1000,
  REQUEST_TOOLTIP_TIME: 5000,

  editors: [],
  editor_for_file: new Map(),
  regex_for_language: new Map(),
  tracepositions_regex: undefined,

  active_file: undefined,
  active_frame: undefined,
  running: false,

  lastCopyText: null,

  <% @config ||= CodeOcean::Config.new(:code_ocean).read(erb: false) %>
  sendLearningAnalyticEvents: <%= @config['codeocean_events'] ? @config['codeocean_events']['enabled'] : false %>,
  learningAnalyticsURL: "<%= @config['codeocean_events'] ? @config['codeocean_events']['url'] : "" %>",


configureEditors: function () {
    _.each(['modePath', 'themePath', 'workerPath'], function (attribute) {
      ace.config.set(attribute, this.ACE_FILES_PATH);
    }.bind(this));
  },

  confirmDestroy: function (event) {
    event.preventDefault();
    if (confirm($(this).data('message-confirm'))) {
      this.destroyFile();
    }
  },

  confirmReset: function (event) {
    event.preventDefault();
    if (confirm($('#start-over').data('message-confirm'))) {
      this.resetCode();
    }
  },

  fileActionsAvailable: function () {
    return this.isActiveFileRenderable() || this.isActiveFileRunnable() || this.isActiveFileStoppable() || this.isActiveFileTestable();
  },

  findOrCreateOutputElement: function (index) {
    if ($('#output-' + index).isPresent()) {
      return $('#output-' + index);
    } else {
      var element = $('<pre>').attr('id', 'output-' + index);
      $('#output').append(element);
      return element;
    }
  },

  findOrCreateRenderElement: function (index) {
    if ($('#render-' + index).isPresent()) {
      return $('#render-' + index);
    } else {
      var element = $('<div>').attr('id', 'render-' + index);
      $('#render').append(element);
      return element;
    }
  },

  getPanelClass: function (result) {
    if (result.stderr && !result.score) {
      return 'panel-danger';
    } else if (result.score < 1) {
      return 'panel-warning';
    } else {
      return 'panel-success';
    }
  },

  showOutput: function(event) {
    event.preventDefault();
    this.showOutputBar();
    $('body').scrollTo($(event.target).attr('href'));
  },

  renderProgressBar: function(score, maximum_score) {
    var percentage = score / maximum_score * 100;
    var progress_bar = $('#score .progress-bar');
    progress_bar.removeClass().addClass(this.getProgressBarClass(percentage));
    progress_bar.attr({
      'aria-valuemax': maximum_score,
      'aria-valuemin': 0,
      'aria-valuenow': score
    });
    progress_bar.css('width', percentage + '%');
  },

  showFirstFile: function() {
    var frame = $('.frame[data-role="main_file"]').isPresent() ? $('.frame[data-role="main_file"]') : $('.frame').first();
    var file_id = frame.find('.editor').data('file-id');
    this.setActiveFile(frame.data('filename'), file_id);
    $('#files').jstree().select_node(file_id);
    this.showFrame(frame);
    this.toggleButtonStates();
  },

  showFrame: function(frame) {
    this.active_frame = frame;
    $('.frame').hide();
    frame.show();
  },

  getProgressBarClass: function (percentage) {
    if (percentage < this.ADEQUATE_PERCENTAGE) {
      return 'progress-bar progress-bar-striped progress-bar-danger';
    } else if (percentage < this.SUCCESSFULL_PERCENTAGE) {
      return 'progress-bar progress-bar-striped progress-bar-warning';
    } else {
      return 'progress-bar progress-bar-striped progress-bar-success';
    }
  },

  handleKeyPress: function (event) {
    if (event.which === this.ALT_R_KEY_CODE) {
      $('#run').trigger('click');
    } else if (event.which === this.ALT_S_KEY_CODE) {
      $('#assess').trigger('click');
    } else if (event.which === this.ALT_T_KEY_CODE) {
      $('#test').trigger('click');
    } else {
      return;
    }
    event.preventDefault();
  },

  handleCopyEvent: function (text) {
    this.lastCopyText = text;
  },

  handlePasteEvent: function (pasteObject) {
    var same = (this.lastCopyText === pasteObject.text);

    // if the text is not copied from within the editor (from any file), send an event to lanalytics
    if (!same) {
      this.publishCodeOceanEvent("codeocean_editor_paste", {
        text: pasteObject.text,
        codeocean_user_id: $('#editor').data('user-id'),
        exercise: $('#editor').data('exercise-id'),
        file_id: "1"
      });
    }
  },

  hideSpinner: function () {
    $('button i.fa').show();
    $('button i.fa-spin').hide();
  },


  resizeAceEditors: function (){
    $('.editor').each(function (index, element) {
      this.resizeParentOfAceEditor(element);
    }.bind(this));
    window.dispatchEvent(new Event('resize'));
  },

  resizeParentOfAceEditor: function (element){
    // calculate needed size: window height - position of top of button-bar - 60 for bar itself and margins
    var windowHeight = window.innerHeight - $('#editor-buttons').offset().top - 60;
    $(element).parent().height(windowHeight);
  },

  initializeEditors: function () {
    this.editors = [];
    $('.editor').each(function (index, element) {

      // Resize frame on load
      this.resizeParentOfAceEditor(element);

      // Resize frame on window size change
      $(window).resize(function(){
        this.resizeParentOfAceEditor(element);
      }.bind(this));

      var editor = ace.edit(element);

      if (this.qa_api) {
        editor.getSession().on("change", function (deltaObject) {
          this.qa_api.executeCommand('syncEditor', [this.active_file, deltaObject]);
        }.bind(this));
      }

      var document = editor.getSession().getDocument();
      // insert pre-existing code into editor. we have to use insertLines, otherwise the deltas are not properly added
      var file_id = $(element).data('file-id');
      var content = $('.editor-content[data-file-id=' + file_id + ']');
      this.setActiveFile($(element).parent().data('filename'), file_id);

      document.insertLines(0, content.text().split(/\n/));
      // remove last (empty) that is there by default line
      document.removeLines(document.getLength() - 1, document.getLength() - 1);
      editor.setReadOnly($(element).data('read-only') !== undefined);
      editor.setShowPrintMargin(false);
      editor.setTheme(this.THEME);



      // set options for autocompletion
      if($(element).data('allow-auto-completion')){
        editor.setOptions({
          enableBasicAutocompletion: true,
          enableSnippets: false,
          enableLiveAutocompletion: true
        });
      }


      editor.commands.bindKey("ctrl+alt+0", null);
      this.editors.push(editor);
      this.editor_for_file.set($(element).parent().data('filename'), editor);
      var session = editor.getSession();
      session.setMode($(element).data('mode'));
      session.setTabSize($(element).data('indent-size'));
      session.setUseSoftTabs(true);
      session.setUseWrapMode(true);

      // set regex for parsing error traces based on the mode of the main file.
      if ($(element).parent().data('role') == "main_file") {
        this.tracepositions_regex = this.regex_for_language.get($(element).data('mode'));
      }

      var file_id = $(element).data('id');

      /*
       * Register event handlers
       */

      // editor itself
      editor.on("paste", this.handlePasteEvent.bind(this));
      editor.on("copy", this.handleCopyEvent.bind(this));

      // listener for autosave
      session.on("change", function (deltaObject) {
        this.resetSaveTimer();
      }.bind(this));
    }.bind(this));
  },

  initializeEventHandlers: function () {
    $(document).on('click', '#results a', this.showOutput.bind(this));
    $(document).on('keypress', this.handleKeyPress.bind(this));
    this.initializeFileTreeButtons();
    this.initializeWorkspaceButtons();
    this.initializeRequestForComments()
  },

  initializeFileTree: function () {
    $('#files').jstree($('#files').data('entries'));
    $('#files').on('click', 'li.jstree-leaf', function (event) {
      active_file = {
        filename: $(event.target).parent().text(),
        id: parseInt($(event.target).parent().attr('id'))
      };
      var frame = $('[data-file-id="' + active_file.id + '"]').parent();
      this.showFrame(frame);
      this.toggleButtonStates();
    }.bind(this));
  },

  initializeFileTreeButtons: function () {
    $('#create-file').on('click', this.showFileDialog.bind(this));
    $('#create-file-collapsed').on('click', this.showFileDialog.bind(this));
    $('#destroy-file').on('click', this.confirmDestroy.bind(this));
    $('#destroy-file-collapsed').on('click', this.confirmDestroy.bind(this));
    $('#download').on('click', this.downloadCode.bind(this));
    $('#download-collapsed').on('click', this.downloadCode.bind(this));
    $('#request-for-comments').on('click', this.requestComments.bind(this));
  },

  initializeSideBarCollapse: function() {
    $('#sidebar-collapse-collapsed').on('click',this.handleSideBarToggle.bind(this));
    $('#sidebar-collapse').on('click',this.handleSideBarToggle.bind(this))
  },

  handleSideBarToggle: function() {
    $('#sidebar').toggleClass('sidebar-col').toggleClass('sidebar-col-collapsed');
    $('#sidebar-collapsed').toggleClass('hidden');
    $('#sidebar-uncollapsed').toggleClass('hidden');
  },

  initializeRegexes: function () {
    this.regex_for_language.set("ace/mode/python", /File "(.+?)", line (\d+)/g);
    this.regex_for_language.set("ace/mode/java", /(.*\.java):(\d+):/g);
  },

  initializeTooltips: function () {
    $('[data-tooltip]').tooltip();
  },


  initializeWorkspaceButtons: function () {
    $('#submit').on('click', this.submitCode.bind(this));
    $('#assess').on('click', this.scoreCode.bind(this));
    $('#dropdown-render, #render').on('click', this.renderCode.bind(this));
    $('#dropdown-run, #run').on('click', this.runCode.bind(this));
    $('#dropdown-stop, #stop').on('click', this.stopCode.bind(this));
    $('#dropdown-test, #test').on('click', this.testCode.bind(this));
    $('#save').on('click', this.saveCode.bind(this));
    $('#start-over').on('click', this.confirmReset.bind(this));
    $('#start-over-collapsed').on('click', this.confirmReset.bind(this));
  },

  initializeRequestForComments: function () {
    var button = $('#requestComments');
    button.prop('disabled', true);
    button.on('click', function () {
      $('#rfc_intervention_text').hide()
      $('#comment-modal').modal('show');
    });

    $('#askForCommentsButton').on('click', this.requestComments.bind(this));
    $('#closeAskForCommentsButton').on('click', function(){
        $('#comment-modal').modal('hide');
    });

    setTimeout(function () {
      button.prop('disabled', false);
      button.tooltip('show');
      setTimeout(function() {
        button.tooltip('hide');
      }, this.REQUEST_TOOLTIP_TIME);
    }.bind(this), this.REQUEST_FOR_COMMENTS_DELAY);
  },

  isActiveFileRenderable: function () {
    return 'renderable' in this.active_frame.data();
  },

  isActiveFileRunnable: function () {
    return this.isActiveFileExecutable() && ['main_file', 'user_defined_file'].includes(this.active_frame.data('role'));
  },

  isActiveFileStoppable: function () {
    return this.isActiveFileRunnable() && this.running;
  },

  isActiveFileSubmission: function () {
    return ['Submission'].includes(this.active_frame.data('contextType'));
  },

  isActiveFileTestable: function () {
    return this.isActiveFileExecutable() && ['teacher_defined_test', 'user_defined_test'].includes(this.active_frame.data('role'));
  },

  isBrowserSupported: function () {
    //  websockets are used for run, score and test
    return Modernizr.websockets;
  },

  populatePanel: function (panel, result, index) {
    panel.removeClass('panel-default').addClass(this.getPanelClass(result));
    panel.find('.panel-title .filename').text(result.filename);
    panel.find('.panel-title .number').text(index + 1);
    panel.find('.row .col-sm-9').eq(0).find('.number').eq(0).text(result.passed);
    panel.find('.row .col-sm-9').eq(0).find('.number').eq(1).text(result.count);
    panel.find('.row .col-sm-9').eq(1).find('.number').eq(0).text(parseFloat((result.score * result.weight).toFixed(2)));
    panel.find('.row .col-sm-9').eq(1).find('.number').eq(1).text(result.weight);
    panel.find('.row .col-sm-9').eq(2).html(result.message);
    if (result.error_messages) panel.find('.row .col-sm-9').eq(3).text(result.error_messages.join(', '));
    panel.find('.row .col-sm-9').eq(4).find('a').attr('href', '#output-' + index);
  },

  // move URL to config file, only fire event if desired.
  publishCodeOceanEvent: function (eventName, contextData) {
    if(this.sendLearningAnalyticEvents){

      // enhance contextData hash with the user agent
      contextData['user_agent'] = navigator.userAgent;

      var payload = {
        user: {
          uuid: $('#editor').data('user-external-id')
        },
        verb: {
          type: eventName
        },
        resource: {
          type: 'page',
          uuid: document.location.href
        },
        timestamp: new Date().toISOString(),
        with_result: {},
        in_context: contextData
      };

      $.ajax(this.learningAnalyticsURL, {
        type: 'POST',
        cache: false,
        dataType: 'JSON',
        data: payload,
        success: {},
        error: {}
      })

    }
  },

  sendError: function (message, submission_id) {
    this.showSpinner($('#render'));
    var jqxhr = this.ajax({
      data: {
        error: {
          message: message,
          submission_id: submission_id
        }
      },
      url: $('#editor').data('errors-url')
    });
    jqxhr.always(this.hideSpinner);
    jqxhr.success(this.renderHint);
  },

  toggleButtonStates: function () {
    $('#destroy-file').prop('disabled', this.active_frame.data('role') !== 'user_defined_file');
    $('#dummy').toggle(!this.fileActionsAvailable());
    $('#render').toggle(this.isActiveFileRenderable());
    $('#run').toggle(this.isActiveFileRunnable() && !this.running);
    $('#stop').toggle(this.isActiveFileStoppable());
    $('#test').toggle(this.isActiveFileTestable());
  },

  jumpToSourceLine: function (event) {
    var file = $(event.target).data('file');
    var line = $(event.target).data('line');

    // set active file, only needed for codepilot, so skipped for now

    var frame = $('div.frame[data-filename="' + file + '"]');
    this.showFrame(frame);

    var editor = this.editor_for_file.get(file);
    editor.gotoLine(line, 0);

  },

  augmentStacktraceInOutput: function () {
    if (this.tracepositions_regex) {
      var element = $('#output>pre');
      var text = element.text();
      element.on("click", "a", this.jumpToSourceLine.bind(this));

      var matches;

      while (matches = this.tracepositions_regex.exec(text)) {
        var frame = $('div.frame[data-filename="' + matches[1] + '"]')

        if (frame.length > 0) {
          element.html(text.replace(matches[0], "<a href='#' data-file='" + matches[1] + "' data-line='" + matches[2] + "'>" + matches[0] + "</a>"));
        }
      }
    }
  },

  resetOutputTab: function () {
    this.clearOutput();
    $('#hint').fadeOut();
    $('#flowrHint').fadeOut();
    this.clearHints();
    this.showOutputBar();
  },

  isActiveFileBinary: function () {
    return 'binary' in this.active_frame.data();
  },

  isActiveFileExecutable: function () {
    return 'executable' in this.active_frame.data();
  },

  setActiveFile: function (filename, fileId) {
    this.active_file = {
      filename: filename,
      id: fileId
    };
  },

  showSpinner: function(initiator) {
    $(initiator).find('i.fa').hide();
    $(initiator).find('i.fa-spin').show();
  },

  showStatus: function(output) {
    if (output.status === 'timeout') {
      this.showTimeoutMessage();
    } else if (output.status === 'container_depleted') {
      this.showContainerDepletedMessage();
    } else if (output.stderr) {
      $.flash.danger({
        icon: ['fa', 'fa-bug'],
        text: $('#run').data('message-failure')
      });
    }
  },

  clearHints: function() {
    var container = $('#error-hints');
    container.find('ul.body > li.hint').remove();
    container.fadeOut();
  },

  showHint: function(message) {
    var template = function(description, hint) {
      return '\
           <li class="hint">\
             <div class="description">\
               ' + description + '\
             </div>\
             <div class="hint">\
               ' + hint + '\
             </div>\
           </li>\
         '
    };
    var container = $('#error-hints');
    container.find('ul.body').append(template(message.description, message.hint));
    container.fadeIn();
  },

  showContainerDepletedMessage: function() {
    $.flash.danger({
      icon: ['fa', 'fa-clock-o'],
      text: $('#editor').data('message-depleted')
    });
  },

  showTimeoutMessage: function() {
    $.flash.info({
      icon: ['fa', 'fa-clock-o'],
      text: $('#editor').data('message-timeout')
    });
  },

  showWebsocketError: function() {
    if (window.navigator.userAgent.indexOf('Edge') > -1) {
      // Mute errors in Microsoft Edge
      return;
    }
    $.flash.danger({
      text: $('#flash').data('message-failure')
    });
  },

  showFileDialog: function(event) {
    event.preventDefault();
    this.createSubmission('#create-file', null, function(response) {
      $('#code_ocean_file_context_id').val(response.id);
      $('#modal-file').modal('show');
    }.bind(this));
  },

  initializeOutputBarToggle: function() {
    $('#toggle-sidebar-output').on('click',this.hideOutputBar.bind(this));
    $('#toggle-sidebar-output-collapsed').on('click',this.showOutputBar.bind(this));
  },

  showOutputBar: function() {
    $('#output_sidebar_collapsed').addClass('hidden');
    $('#output_sidebar_uncollapsed').removeClass('hidden');
    $('#output_sidebar').removeClass('output-col-collapsed').addClass('output-col');
  },

  hideOutputBar: function() {
    $('#output_sidebar_collapsed').removeClass('hidden');
    $('#output_sidebar_uncollapsed').addClass('hidden');
    $('#output_sidebar').removeClass('output-col').addClass('output-col-collapsed');
  },

  initializeSideBarTooltips: function() {
    $('[data-toggle="tooltip"]').tooltip()
  },

  initializeDescriptionToggle: function() {
    $('#exercise-headline').on('click',this.toggleDescriptionPanel.bind(this))
  },

  toggleDescriptionPanel: function() {
    $('#description-panel').toggleClass('description-panel-collapsed');
    $('#description-panel').toggleClass('description-panel');
    $('#description-symbol').toggleClass('fa-chevron-down');
    $('#description-symbol').toggleClass('fa-chevron-right');
    // resize ace editors
    this.resizeAceEditors();

  },

    /**
     * interventions
     * */
    initializeInterventionTimer: function() {

        if ($('#editor').data('rfc-interventions') || $('#editor').data('break-interventions')) { // split in break or rfc intervention
            window.onblur = function() { window.blurred = true; };
            window.onfocus = function() { window.blurred = false; };

            var delta = 100; // time in ms to wait for window event before time gets stopped
            var tid;
            $.ajax({
                data: {
                    exercise_id: $('#editor').data('exercise-id'),
                    user_id: $('#editor').data('user-id')
                },
                dataType: 'json',
                method: 'GET',
                // get working times for this exercise
                url: $('#editor').data('working-times-url'),
                success: function (data) {
                    var percentile75 = data['working_time_75_percentile'];
                    var accumulatedWorkTimeUser = data['working_time_accumulated'];

                    var minTimeIntervention = 10 * 60 * 1000;

                    if ((accumulatedWorkTimeUser - percentile75) > 0) {
                        // working time is already over 75 percentile
                        var timeUntilIntervention = minTimeIntervention;
                    } else {
                        // working time is less than 75 percentile
                        // ensure we give user at least minTimeIntervention before we bother the user
                        var timeUntilIntervention = Math.max(percentile75 - accumulatedWorkTimeUser, minTimeIntervention);
                    }

                    tid = setInterval(function() {
                        if ( window.blurred ) { return; }
                        timeUntilIntervention -= delta;
                        if ( timeUntilIntervention <= 0 ) {
                            clearInterval(tid);
                            // timeUntilIntervention passed
                            if ($('#editor').data('break-interventions')) {
                                $('#break-intervention-modal').modal('show');
                                $.ajax({
                                    data: {
                                        intervention_type: 'BreakIntervention'
                                    },
                                    dataType: 'json',
                                    type: 'POST',
                                    url: $('#editor').data('intervention-save-url')
                                });
                            } else if ($('#editor').data('rfc-interventions')){
                                var button = $('#requestComments');
                                // only show intervention if user did not requested for a comment already
                                if (!button.prop('disabled')) {
                                    $('#rfc_intervention_text').show();
                                    $('#comment-modal').modal('show');
                                    $.ajax({
                                        data: {
                                            intervention_type: 'QuestionIntervention'
                                        },
                                        dataType: 'json',
                                        type: 'POST',
                                        url: $('#editor').data('intervention-save-url')
                                    });
                                };
                            }
                        }
                    }, delta);
                }
            });
        }
    },

    initializeSearchButton: function(){
        $('#btn-search-col').button().click(function(){
            var search = $('#search-input-text').val();
            var course_token = $('#editor').data('course_token')
            var save_search_url = $('#editor').data('search-save-url')
            window.open("https://open.hpi.de/courses/" + course_token + "/pinboard?query=" + search, '_blank');
            // save search
            $.ajax({
                data: {
                    search_text: search
                },
                dataType: 'json',
                type: 'POST',
                url: save_search_url});
        })

        $('#sidebar-search-collapsed').on('click',this.handleSideBarToggle.bind(this));
    },


  initializeEverything: function() {
    this.initializeRegexes();
    this.initializeCodePilot();
    $('.score, #development-environment').show();
    this.configureEditors();
    this.initializeEditors();
    this.initializeEventHandlers();
    this.initializeFileTree();
    this.initializeSideBarCollapse();
    this.initializeOutputBarToggle();
    this.initializeDescriptionToggle();
    this.initializeSideBarTooltips();
    this.initializeTooltips();
    this.initializeInterventionTimer();
    this.initializeSearchButton();
    this.initPrompt();
    this.renderScore();
    this.showFirstFile();

    $(window).on("beforeunload", this.unloadAutoSave.bind(this));
    // create autosave when the editor is opened the first time
    this.autosave();
  }
};
