/*
 * Sim handler object.
 * Typically called from sim.html.
 *
 * Uses Object Literal format.
 *
 * @package    mod_wfsim
 * @copyright 2015 onwards Catalyst IT Europe Ltd
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 *
 */

// Object in global namespace.
var sim = {

  // Vars.
  caseDate: null,
  caseDateOrigin: null,
  variableGapMm: 0, // Control-file time gap (for Vbl insts)(mins).
  classroomHours: null,
  timeMultiplier: null,
  timeIncrement: null,
  paused: true,
  lev1: null,
  prevSlot: null,
  curSlot: null,
  slotslist: [],  // List of slots from slotslist.json.
  notesDtms: [],
  imageBase: '',
  doCscCallback: false,
  debug: true,

  setup: function(dtm, crh, tm, ti, pau, wwwroot, dbg) {
    sim.debug = dbg;
    sim.wwwroot = wwwroot;
    sim.pageBase = ''; // Gets changed to '' when exported.
    console.log('sim.js:setup() v2.0 casedate arg=' + dtm + ' debug=' + sim.debug);
    if (arguments.length != 7) {
      alert('sim.setup() invalid number of args (' + arguments.length +
              ') should be 7) - Please ask Teacher/admin to re-save sim module');
      return;
    }

    // Set the case date to passed-in value.
    // note: BST is 1 hr ahead of UTC (ie GMT:6, BST:7). offset is -60 in summer.
    sim.caseDate = new Date(dtm); // '2015-05-14T06:00:00Z'.
    var tzmsec = sim.caseDate.getTimezoneOffset() * 60000;
    sim.caseDate = new Date(sim.caseDate.getTime() + tzmsec);
    sim.debug && console.log('off=' + sim.caseDate.getTimezoneOffset()); // -60 min.
    sim.debug && console.log('sim.caseDate=' + sim.caseDate);

    // Get caseDate at previous midnight as base for variable interval slot calculations.
    var dd = sim.caseDate.getDate(); // Day of month (1-31).
    var mt = sim.caseDate.getMonth(); // 0-11.
    var yyyy = sim.caseDate.getFullYear();
    sim.caseDateOrigin = new Date(yyyy, mt, dd, 0, 0, 0); // Midnight.

    // Set number of hour buttons on classroom control bar.
    sim.classroomHours = crh;

    // Classroom time muliplier, 60 = *60 ie 1 classroom hr per min.
    sim.timeMultiplier = tm;

    // Set how smoothly the clock will run, number of milliseconds between clock updates.
    sim.timeIncrement = ti; // 10 works well for x5 speed above.

    // Classroom time pause.
    sim.paused = pau;

  },

  // Called on page load.
  startSim: function() {

    if (sim.caseDate == null) {
      alert('startSim: caseDate not defined, setup() called?');
      return;
    }

    // Go! start the timer callback and keep it going.
    setInterval(this.nextStep, sim.timeIncrement);

    // Classroom time control.
    sim.buildCRTimeControl();

    // Instantiate clock.
    sim.setClock();

    // Initial call.
    sim.processNotes();

    // Handlers.

    // When top-level menu item is clicked its id is used to build id of second level menu, to show.
    $('#simpage li.nav1 button').click(function(e) {
      $('#simpage li.nav1 button').removeClass('active');
      $(this).addClass('active');
      // When .nav2 button gets clicked, this gets called too (but with empty target.id). We dont want to be called.
      if (e.target.id != '') {
        $('#simpage .nav2 li').css('display', 'none'); // Hide all.
        $('#simpage .nav2#' + e.target.id + '2 li').css('display', 'block');
        sim.lev1 = e.target.id;
      }
    });

    // When second level menu item clicked, get param attributes, and call showData.
    $('#simpage .nav2 li button').click(function(e) {
      e.stopPropagation();
      $('#simpage .nav2 li button').removeClass('active');
      $(this).addClass('active');
      $('#simpage .nav1 #' + sim.lev1).addClass('active'); // Set nav1 class again as gets cleared somewhere.
      sim.showData(e.target.attributes['data-path'].value,
                    e.target.attributes['data-fv'].value,
                    e.target.attributes['data-fv-gap'].value);
    });

  },

  // Core timekeeping function.
  nextStep: function() {
    // CaseDate is a div to put the date in under the clock (div has same name as variable!).
    $('#casedate').html(sim.caseDate.toLocaleDateString()); // Language sensitive representation.

    // If the classroom time is not paused, increment the time.
    if (!sim.paused) {
      // Add the right number of seconds to the current simulation date.
      sim.caseDate = new Date(sim.caseDate.getTime() + (sim.timeMultiplier * sim.timeIncrement));

      // Any other actions needed at a specific point in sim or classroom time go here ...
    if (sim.caseDate > new Date(2017,9,15,12,05,00)) {
        document.getElementById('b-modeldata2-fileds').style.visibility = "visible"
        document.getElementById('b-modeldata2-crossection').style.visibility = "visible"
      }
    if (sim.caseDate > new Date(2017,9,15,16,00)) {
    	document.getElementById('ln3').style.visibility = "visible"
    	sim.paused = true;
    }
    }
  },

  // Show selected hanis control file in the activity pane iframe.
  showData: function(imagebase, fv, fvgap) {
    var page = null;
    sim.imageBase = imagebase;
    sim.debug && console.log('showData: imagebase=' + imagebase + ' fv=' + fv + ' fv-gap(mm)=' + fvgap);

    if (fv == 'f') {
      page = sim.pageBase + 'images/' + imagebase + '/fixed.html';
      sim.variableGapMm = 0; // Clear as used to check if f/v in checkSlotChangedCallback.
      this.loadPage(imagebase, fv, '', page);

    } else { // Variable v.

      // Get variable control file time interval and store globally.
      sim.variableGapMm = fvgap; // Mins.
      if (isNaN(sim.variableGapMm)) {
        sim.debug && console.log('showData: invalid fv-gap[' + fvgap + '] imagebase=' + imagebase);
        return;
      }

      slot = this.calculateSlot('ddhhmm'); // Time slot to show.
      sim.debug && console.log('showData: slot=' + slot);
      page = sim.pageBase + 'images/' + imagebase + '/' + slot + '.html';

      // Set up to notify user if slot has changed, if not standalone.
      if (sim.pageBase != '') {
        // Get list of slots.
        $.get(sim.pageBase + 'images/' + imagebase + '/slotslist.json').done(function(data) {
          sim.slotslist = $.parseJSON(data);
          sim.debug && console.log('slotslist=' + sim.slotslist);
          if (sim.slotslist.length == 0) {
            alert("showData: Missing slotslist, imagebasae= " + imagebase);
          }
        }).fail(function() {
          alert('showData: Sim instance configuration issue. Please contact administrator');
          console.log('showData: no slotslist.json file imagebase=[' + imagebase + ']');
        }).always(function() {
          sim.debug && console.log('showData: slotslist done');

          $('#simpage #liveupdates li').remove(); // Clear any lines from last vbl inst.
          sim.checkSlotChangedCallback();
          sim.loadPage(imagebase, fv, slot, page);
        });
      } else {
        // Standalone.
        sim.loadPage(imagebase, fv, slot, page);
      }
    }
  },

  loadPage: function(imagebase, fv, slot, page) {
    sim.debug && console.log('loadPage: slot=' + slot + ' try=' + page);
    // Eg page: /mod/wfsim/wffile.php?p=images/img02/140600.html .

    // Check page exists (Online).
    var pp = page.split('=');
    if (sim.pageBase != '') {
      if (fv == 'v') {
        // Vbl.
        if (! sim.slotslist.includes(slot)) {
          // Fuzzy: if vbl use 'previous' slot.
          // There are 2 scenarious:
          // a) lev2 menu item just clicked - so selecting slot for first time,
          // b) advancing from previous slot via checkSlotChangedCallback().
          // Because of a, try to use slotlist to get 'previous' slot from current clocktime.
          var ddhhmm = sim.prevSlotFromList(slot);
          if (ddhhmm == 0) {
            // No controlfile before current slot.
            // Force error return from wffile.php by using passed in page.
          } else {
            page = sim.pageBase + 'images/' + imagebase + '/' + ddhhmm + '.html';
            pp = page.split('=');
            sim.debug && console.log('showData: current slot control file not present - using prev: page=' + page);
          }
        }
        // Else derived slot is in list of slots.
      } else {
        // Fixed.
        // Dont need to check as just one file - generated from instance.
      }

      // Debug - live check to server.
      /*
      if (sim.debug) {
        $.get(page).done(function() {
          // OK.
          console.log("loadPage: found control file " + pp[pp.length - 1]);
        })
        .fail(function() {
          console.log("loadPage: no control file " + pp[pp.length - 1]);
        })
        .always(function() {
          console.log("loadPage: get now finished");
        });
      }
      */
    }

    // Put control file name on CRTimeControl.
    $('#ctrlfile').html(pp[pp.length - 1]);

    // Set page.

    // Set to catch load event, and then add the title attr from the button text, so get a tooltip.
    // Do on-hover as some buttons change state (and text).
    $('#sim-panel').on('load', function() {
      // To work on iframe content - set iframe doc in jquery...
      $(this.contentWindow.document).find('#handiv div button').each(function() {
        $(this).hover(function() {
          // console.log("cur calss " + $(this).attr('class') + ' txt='+$(this).text());
          var labl = $(this).text();
          // Hack for first/last as not supported in sim setup. Convert default string to nicer text.
          if (labl == '|<') {
            labl = 'First';
          } else if (labl == '>|') {
            labl = 'Last';
          }
          $(this).attr('title', labl);
        });
      });
    });

    // Load page into iframe.
    $('#simpage #sim-panel').attr('src', page);

  },

  // Find previous slot to the one passed in.
  // If no slots in slotslist >= curslot, then use last one found in list.
  // If no slots in slotslist, return 0.
  prevSlotFromList: function(curslot) {
    var i;
    for (i = 0; i < sim.slotslist.length; i++) {
      // Should be in collating order of ddhhmm.
      if (sim.slotslist[i] >= curslot) {
        break;
      }
    }
    i--;
    // In worst case return error.
    if (i < 0) {
      return 0;
    }

    return sim.slotslist[i];
  },

  // Report in notes col if slot vbl have changed (for Vbl insts).
  // If so - set in animation pane.
  // If un-paused, re-paused, called-back early etc - will adjust itself to correct cr time.
  checkSlotChangedCallback: function() {
    // sim.debug && console.log('checkSlotChangedCallback: caseDate='+sim.caseDate+' variableGapMm='+sim.variableGapMm
    // +' curSlot='+sim.curSlot+' imageBase='+sim.imageBase+' pageBase='+sim.pageBase);
    // Not paused, not Fixed, imagebase set, and not standalone.
    if (sim.paused || sim.variableGapMm == 0 || sim.imageBase == '' || sim.pageBase == '') {
      return;
    }

    sim.curSlot = sim.calculateSlot('millisecs');
    // sim.debug && console.log('checkSlotChangedCb: prevSlot='+sim.dateToDdhhmm(new Date(sim.prevSlot))
    //                                                        +' curSlot='+sim.dateToDdhhmm(new Date(sim.curSlot)));
    // Only check if new slot val otherwise just setup to callback.
    if (sim.prevSlot != sim.curSlot) {
      // Dont check for first file.
      if (sim.prevSlot != null) {

        // Check page exists.
        var slot = sim.dateToDdhhmm(new Date(sim.curSlot));

        if (sim.slotslist.includes(slot)) {
          sim.debug && console.log('checkSlotChangedCb: slot (' + slot + ') exists in slotslist');
          $('#liveupdates').append('<li class="alert">New data (' + slot + ')</li>');
          sim.doCscCallback = true;

          // Now change control file! Dont need to test (as in showData) as comes from slots list.
          var page = sim.pageBase + 'images/' + sim.imageBase + '/' + slot + '.html';
          $('#simpage #sim-panel').attr('src', page);

          // Put control file name on CRTimeControl.
          var pp = page.split('=');
          $('#ctrlfile').html(pp[pp.length - 1]);

          sim.debug && console.log('checkSlotChangedCb: ctrl file now ' + pp[pp.length - 1]);
        } else {
          sim.debug && console.log('checkSlotChangedCb: slot (' + slot + ') not in slotslist ie a gap');
          $('#liveupdates').append('<li class="alert">No New data (' + slot + ')</li>');
          // Could give up trying after a missing slot  – but do need to continue after missing
          //  one(s), so keep trying at each new potential slot time.
          sim.doCscCallback = true;
        }
      } else {
        sim.doCscCallback = true; // Force callback.
      }
      sim.prevSlot = sim.curSlot;
    } else {
      sim.doCscCallback = true; // Force callback.
    }

    // Work out next slot time from current time and gap, then work out real-time timeout required.
    var cbinterval = ((sim.curSlot + (sim.variableGapMm * 60000)) - sim.caseDate.getTime()) / sim.timeMultiplier;
    // sim.debug && console.log('checkSlotChangedCb: timeout='+cbinterval/60000+' mins');
    // sim.debug && console.log('checkSlotChangedCb: doCscCallback='+sim.doCscCallback);

    // Single callback.
    if (sim.doCscCallback) {
      setTimeout(function() {
        sim.checkSlotChangedCallback();
      }, cbinterval); // ms.
    }
  },

  // Find the slot that has most recently passed the current simulator time (sim.caseDate),
  // eg if the interval is 1hr, then at 0610 it will be 0600, if 6hr at 1330 it will be 1200.
  // Assumes start at 0000.
  // Used to build the file name eg images/img01/ddhhmm.html.
  calculateSlot: function(rettype) {
    var slotDate = new Date(sim.caseDateOrigin.getTime()); // Midnight.
    var varintms = 60000 * sim.variableGapMm;
    // sim.debug && console.log('calculateSlot: slotDateori='+slotDate+' caseDate='+sim.caseDate+' variableGapMm='+sim.variableGapMm+' varintms='+varintms);
    var c = 0;
    while (true) {
      if (slotDate.getTime() > sim.caseDate.getTime()) {
        slotDate = new Date(slotDate.getTime() - varintms); // Back-up to previous value.
        break;
      }
      slotDate = new Date(slotDate.getTime() + varintms);
      c++;
      if (c > 400) {
        alert('calculateSlot: looping?');
        return;
      }
    }
    if (rettype == 'millisecs') {
      return slotDate.getTime();
    }

    // Convert to filename.
    var ddhhmm = sim.dateToDdhhmm(slotDate);

    sim.debug && console.log("calculateSlot: ddhhmm=" + ddhhmm);
    return ddhhmm;
  },

  dateToDdhhmm: function(dtm) {
    return sim.padNumber(dtm.getDate()) + // dDay of month 1-31.
           sim.padNumber(dtm.getHours()) + // 0-23.
           sim.padNumber(dtm.getMinutes()); // 0-60.
  },

  // Create a popup window, eg for intro.html.
  popupCenter: function(pageURL, title, w, h) {
    var left = (screen.width / 2) - (w / 2);
    var top = (screen.height / 2) - (h / 2);
    window.open(pageURL, title,
      'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=yes, resizable=yes, copyhistory=no, width=' +
      w * 1.75 + ', height=' + h + ', top=' + top + ', left=' + left);
  },

  setClock: function() {
    var clock = new StationClock("clock");

    clock.body = StationClock.NoBody;
    clock.dial = StationClock.AustriaStrokeDial;
    clock.hourHand = StationClock.BarHourHand;
    clock.minuteHand = StationClock.BarSecondHand;
    clock.secondHand = StationClock.BarSecondHand;
    clock.boss = StationClock.NoBoss;
    clock.minuteHandBehavoir = StationClock.BouncingMinuteHand;
    clock.secondHandBehavoir = StationClock.CreepingSecondHand;

    $("#clock").attr('width', '200');
    $("#clock").attr('height', '200');

    // Call clock refresh ev 50ms.
    setInterval(function() {
      clock.draw();
    }, 50);
  },

  // Build the classroom time control block, based on params already passed in.
  // params: gap (mins)
  buildCRTimeControl: function() {

    var chtml = '';
    var crdtm = new Date(sim.caseDate);
    //console.log('buildCRTimeControl: initial ISOString='+crdtm.toISOString()); // -1hr
    var tzmsec = sim.caseDate.getTimezoneOffset() * 60000;
    // Reverse normal time adjustment so ISOString works.
    crdtm = new Date(sim.caseDate.getTime() - tzmsec);
    var crdtmtxt;

    chtml += '<div id="buttons">';
    chtml += '<button id="pause">Pause clock</button>';
    chtml += '<button id="run">Start clock</button>';

    // Build set of time select options, with date value for each as attribute (2014-05-14T06:00:00).
    chtml += '<select class="crtcontrol">';
    chtml += '<option value="0" class="crtoption"> -select- </option>'; // Dummy option.
    var gap = 15; // TODO dynamic update from current inst.
    //var gap = 60;
    for (var i = 0; i <= sim.classroomHours; i++) {
      // Do minutes - multiple if less than 60. Use separate date vbl as user may not have set gap as
      // exact fraction of an hour and we want to include whole hour boundaries.
      var crdtmmin = new Date(crdtm.getTime());
      // crdtmmin.setMinutes(crdtmmin.getMinutes() - (4 * 60)); // Start 4hrs back.
      var mins = 0;
      while (mins < 60) {
        crdtmtxt = crdtmmin.toISOString(); // toISOString() 2011-10-05T14:48:00.000Z
        //console.log('buildCRTimeControl: ISOString='+crdtmtxt);
        chtml += '<option value="' + crdtmtxt.substr(0, 19) + '" class="crtoption">' + crdtmtxt.substr(8, 2) + ' ' +
                 crdtmtxt.substr(11, 2) + crdtmtxt.substr(14, 2) + '</option>';
        crdtmmin.setMinutes(crdtmmin.getMinutes() + gap);
        mins += gap;
      }
      crdtm.setHours(crdtm.getHours() + 1);
    }
    chtml += '</select>';
    chtml += '<div id="ctrlfile">-ctrlfile-</div>';
    chtml += '</div>';

    $('#pause-run').append(chtml);
    $('#pause-run #buttons').css('display', 'none');

    // Handlers.
    $('#pause-run').hover(function() {
        $('#pause-run #buttons').fadeIn(500);
    }, function() {
      $('#pause-run #buttons').fadeOut(100);
    });

    $('#pause-run #pause').click(function() {
      sim.paused = true;
      scroll(0, 0);
    });

    $('#pause-run #run').click(function() {
      sim.paused = false;
      sim.processNotesCallback(); // Display any notes that should be seen now.
      sim.checkSlotChangedCallback();
      scroll(0, 0);
    });

    $('#pause-run .crtcontrol').change(function(e) {
      if ($(this).val() == 0) { // Dummy option.
        return;
      }
      $selval = $(this).val() + 'Z';
      sim.debug && console.log('selected: ' + $selval);
      sim.caseDate = new Date($selval);
      var tzmsec = sim.caseDate.getTimezoneOffset() * 60000;
      sim.caseDate = new Date(sim.caseDate.getTime() + tzmsec);

      sim.processNotesCallback(); // Display any notes that should be seen now.
      sim.checkSlotChangedCallback();
      scroll(0, 0);

      // Force selected option back to dummy so re-selecting a same time will trigger change.
      $('#pause-run .crtcontrol').val(0);
    });
  },

  // Display notes selectively based on time.
  // Work out time next note is to be displayed and callback then.
  processNotes: function() {
    $('#left-nav #lessonnotes li.note').each(function() {
      var tzmsec = sim.caseDate.getTimezoneOffset() * 60000; // Min to ms.
      var attrdtm = new Date($(this).attr('data-dtm')); // Convert 2014-05-14T06:00:00Z to unixtime.
      var notesdtm = new Object();
      notesdtm.dtm = attrdtm.getTime() + tzmsec;
      notesdtm.noteid = $(this).attr('id');
      notesdtm.dtm_attr = $(this).attr('data-dtm');
      sim.notesDtms.push(notesdtm);
    });

    // Ensure in ascending time order.
    sim.notesDtms.sort(function(a, b) {
      if (a.dtm < b.dtm) {
        return -1;
      }
      if (a.dtm > b.dtm) {
        return 1;
      }
      return 0;
    });

    sim.processNotesCallback(); // Initialise.
  },

  // Show all items with dtm less than current casedate and work out when to callback for next.
  // Note - seems callback triggers in shorter time than expected - but OK as new short interval calculated.
  processNotesCallback: function() {
    cDate = new Date(sim.caseDate.getTime());

    // Do all past ones, as will catch any that were missed due to this being called too late etc.
    for (var i = 0; i < sim.notesDtms.length; i++) {
      if (cDate.getTime() >= sim.notesDtms[i].dtm) {
        $('#left-nav #lessonnotes #' + sim.notesDtms[i].noteid).css('display', 'block');
      } else {
        // Next one in future (if any left).
        if (! sim.paused && (typeof sim.notesDtms[i] !== 'undefined')) {
          var interval = sim.notesDtms[i].dtm - cDate.getTime(); // Theoretical time (ms).
          var siminterval = interval / sim.timeMultiplier;  // Sim real time.

          // Single call - not repeated as with setInterval().
          setTimeout(function() {
            sim.processNotesCallback();
          }, siminterval); // Units: ms.

          break;
        }
      }
    }
  },

  padNumber: function(number) {
    if (number < 10) {
      return '0' + number;
    }
    return number.toString();
  }
};
