var H5P = H5P || {};

/**
 * Constructor.
 *
 * @param {Object} params Options for this library.
 * @param {Number} id Content identifier
 * @returns {undefined}
 */
(function ($) {
  H5P.Image = function (params, id, extras) {
    H5P.EventDispatcher.call(this);
    this.extras = extras;

    if (params.file === undefined || !(params.file instanceof Object)) {
      this.placeholder = true;
    }
    else {
      this.source = H5P.getPath(params.file.path, id);
      this.width = params.file.width;
      this.height = params.file.height;
    }

    this.alt = (!params.decorative && params.alt !== undefined) ?
      this.stripHTML(this.htmlDecode(params.alt)) :
      '';

    if (params.title !== undefined) {
      this.title = this.stripHTML(this.htmlDecode(params.title));
    }
  };

  H5P.Image.prototype = Object.create(H5P.EventDispatcher.prototype);
  H5P.Image.prototype.constructor = H5P.Image;

  /**
   * Wipe out the content of the wrapper and put our HTML in it.
   *
   * @param {jQuery} $wrapper
   * @returns {undefined}
   */
  H5P.Image.prototype.attach = function ($wrapper) {
    var self = this;
    var source = this.source;

    if (self.$img === undefined) {
      if(self.placeholder) {
        self.$img = $('<div>', {
          width: '100%',
          height: '100%',
          class: 'h5p-placeholder',
          title: this.title === undefined ? '' : this.title,
          on: {
            load: function () {
              self.trigger('loaded');
            }
          }
        });
      } else {
        self.$img = $('<img>', {
          width: '100%',
          height: '100%',
          src: source,
          alt: this.alt,
          title: this.title === undefined ? '' : this.title,
          on: {
            load: function () {
              self.trigger('loaded');
            }
          }
        });
      }
    }

    $wrapper.addClass('h5p-image').html(self.$img);
  };

  /**
   * Retrieve decoded HTML encoded string.
   *
   * @param {string} input HTML encoded string.
   * @returns {string} Decoded string.
   */
  H5P.Image.prototype.htmlDecode = function (input) {
    const dparser = new DOMParser().parseFromString(input, 'text/html');
    return dparser.documentElement.textContent;
  };

  /**
   * Retrieve string without HTML tags.
   *
   * @param {string} input Input string.
   * @returns {string} Output string.
   */
  H5P.Image.prototype.stripHTML = function (html) {
    const div = document.createElement('div');
    div.innerHTML = html;
    return div.textContent || div.innerText || '';
  };

  return H5P.Image;
}(H5P.jQuery));
;
var H5P = H5P || {};

H5P.ImageSlide = (function ($) {
  /**
   * Constructor function.
   */
  function C(options, contentId, extras) {
    var self = this;
    this.$ = $(this);
    H5P.EventDispatcher.call(this);

    this.aspectRatio = this.originalAspectRatio = extras.aspectRatio;
    // Extend defaults with provided options
    this.options = $.extend(true, {}, {
      image: null,
    }, options);
    // Keep provided id.
    this.contentId = contentId;

    this.image = H5P.newRunnable(this.options.image, this.contentId);
    this.image.on('loaded', function() {
      self.trigger('loaded');
      self.trigger('resize');
    });
  }

  C.prototype = Object.create(H5P.EventDispatcher.prototype);
  C.prototype.constructor = C;


  /**
   * Attach function called by H5P framework to insert H5P content into
   * page
   *
   * @param {jQuery} $container
   */
  C.prototype.attach = function ($container) {
    this.$container = $container;

    // Set class on container to identify it as a greeting card
    // container.  Allows for styling later.
    $container.addClass("h5p-image-slide");

    this.$imageHolder = $('<div>', {
      class: 'h5p-image-slide-image-holder',
    });

    $container.append(this.$imageHolder);

    // Add image
    this.image.attach(this.$imageHolder);

    this.adjustSize();
  };

  /**
   * Set the ascpect ratio for this slide
   *
   * @param {Integer} newAspectRatio the aspect ratio
   */
  C.prototype.setAspectRatio = function(newAspectRatio) {
    this.aspectRatio = newAspectRatio;
    // Adjust size if image has been attached
    if (this.$imageHolder) {
      this.adjustSize();
    }
  };

  /**
   * Reset the aspect ratio to the previously set aspect ratio
   *
   * Typically used when exiting fullscreen mode
   */
  C.prototype.resetAspectRatio = function() {
    this.aspectRatio = this.originalAspectRatio;
    // Adjust size if image has been attached
    if (this.$imageHolder) {
      this.adjustSize();
    }
  };

  /**
   * Update the size of the slide
   *
   * Typically used when the screen resizes, goes to fullscreen or similar
   */
  C.prototype.adjustSize = function() {
    var imageHeight = this.options.image.params.file.height;
    var imageWidth = this.options.image.params.file.width;

    var imageAspectRatio = imageWidth / imageHeight;
    if (this.aspectRatio >= imageAspectRatio) {
      // image too tall - Make it smaller and center it
      var widthInPercent = imageAspectRatio / this.aspectRatio * 100;
      var borderSize = (100 - widthInPercent) / 2 + '%';
      this.$imageHolder.css({
        height: '100%',
        width: imageAspectRatio / this.aspectRatio * 100 + '%',
        paddingLeft: borderSize,
        paddingRight: borderSize,
        paddingTop: 0,
        paddingBottom: 0
      });
    }
    else if (this.aspectRatio < imageAspectRatio) {
      // image too wide
      var heightInPercent = this.aspectRatio / imageAspectRatio * 100;

      // Note: divide by aspect ratio since padding top/bottom is relative to width
      var borderSize = (100 - heightInPercent) / 2 / this.aspectRatio + '%';

      this.$imageHolder.css({
        width: '100%',
        height: heightInPercent + '%',
        paddingTop: borderSize,
        paddingBottom: borderSize,
        paddingLeft: 0,
        paddingRight: 0
      });
    }
    else if (this.aspectRatio === undefined) {
      this.$imageHolder.css({
        width: '100%',
        height: '',
        paddingTop: 0,
        paddingBottom: 0,
        paddingLeft: 0,
        paddingRight: 0
      });
    }
  };

  return C;
})(H5P.jQuery);
;
var H5P = H5P || {};

H5P.ImageSlider = (function ($) {
  /**
   * Constructor function.
   */
  function C(options, id) {
    this.$ = $(this);
    var self = this;

    H5P.EventDispatcher.call(this);
    // Extend defaults with provided options
    this.options = $.extend(true, {}, {
      imageSlides: [
        {
          imageSlide: null
        }
      ],
      a11y: {
        nextSlide: 'Next Image',
        prevSlide: 'Previous Image',
        gotoSlide: 'Go to image %slide'
      },
      aspectRatioMode: 'auto',
      aspectRatio: {
        aspectWidth: 4,
        aspectHeight: 3
      }
    }, options);

    // Filter out slides without image
    this.options.imageSlides = this.options.imageSlides.filter(function (slide) {
      return slide.params && slide.params.image && slide.params.image.params && slide.params.image.params.file;
    });

    if (this.options.imageSlides.length === 0) {
      return;
    }

    // Keep provided id.
    this.id = id;
    this.currentSlideId = 0;
    this.imageSlides = [];
    this.imageSlideHolders = [];
    this.determineAspectRatio();

    for (var i = 0; i < this.options.imageSlides.length; i++) {
      this.imageSlides[i] = H5P.newRunnable(this.options.imageSlides[i], this.id, undefined, undefined, {
        aspectRatio: this.aspectRatio
      });
      this.imageSlides[i].on('loaded', function() {
        self.trigger('resize');
      });
      this.imageSlideHolders[i] = false;
    }

    this.on('enterFullScreen', function() {
      self.enterFullScreen();
    });
    this.on('exitFullScreen', function(){
      self.exitFullScreen();
    });

    this.on('resize', function() {
      var fullScreenOn = self.$container.hasClass('h5p-fullscreen') || self.$container.hasClass('h5p-semi-fullscreen');
      if (fullScreenOn) {
        self.$slides.css('height', '');
        var newAspectRatio = window.innerWidth / (window.innerHeight - self.$progressBar.outerHeight());
        for (var i = 0; i < self.imageSlides.length; i++) {
          self.imageSlides[i].setAspectRatio(newAspectRatio);
        }
      }
      else {
        if (self.aspectRatio && self.$slides) {
          self.$slides.height(self.$slides.width() / self.aspectRatio);
        }
      }
      self.updateNavButtons();
      self.updateProgressBar();
    });
  }

  C.prototype = Object.create(H5P.EventDispatcher.prototype);
  C.prototype.constructor = C;

  /**
   * Set the aspect ratio for this image-slider
   */
  C.prototype.determineAspectRatio = function() {
    // Set aspectRatio to default
    this.aspectRatio = 4/3;

    // Try to identify aspectRatio according to settings
    switch (this.options.aspectRatioMode) {
      case 'auto':
        var imageRatios = [];
        for (var i = 0; i < this.options.imageSlides.length; i++) {
          var imageFile = this.options.imageSlides[i].params.image.params.file;
          imageRatios[i] = imageFile.width / imageFile.height;
        }
        imageRatios.sort(function (a, b) {
          return a - b;
        });
        // Get the median image ratio
        this.aspectRatio = imageRatios[Math.round(imageRatios.length / 2) - 1];
        break;

      case 'custom':
        if (this.options.aspectRatio.aspectWidth && this.options.aspectRatio.aspectHeight) {
          this.aspectRatio = this.options.aspectRatio.aspectWidth / this.options.aspectRatio.aspectHeight;
        }
        break;

      case 'notFixed':
        this.aspectRatio = undefined;
        break;
    }
  };

  /**
   * Attach function called by H5P framework to insert H5P content into
   * page
   *
   * @param {jQuery} $container
   */
  C.prototype.attach = function ($container) {
    if (this.options.imageSlides.length === 0) {
      return;
    }

    this.$container = $container;
    // Set class on container to identify it as a greeting card
    // container.  Allows for styling later.
    $container.addClass("h5p-image-slider").addClass('h5p-image-slider-using-mouse');

    $container.bind('keydown', function(e) {
      var keyboardNavKeys = [32, 13, 9];
      if (keyboardNavKeys.indexOf(e.which) !== -1) {
        $container.removeClass('h5p-image-slider-using-mouse');
      }
    });
    $container.bind('mousedown', function() {
      $container.addClass('h5p-image-slider-using-mouse');
    });

    this.$slidesHolder = $('<div>', {
      class: 'h5p-image-slider-slides-holder'
    }).appendTo($container);

    this.$screenReaderAnnouncement = $('<div>', {
      class: 'h5p-image-slider-sr-only',
      'aria-atomic': 'true',
      'aria-live': 'polite'
    }).appendTo(this.$slidesHolder);

    this.$slides = $('<div>', {
      class: 'h5p-image-slider-slides'
    }).appendTo(this.$slidesHolder);

    // If the H5P is embedded e.g. inside a non H5P accordion, the
    // slides DOM element might end up with no height. Using a
    // intersectionOberver to overcome this issue.
    const observer = new IntersectionObserver(() => {
      if (this.$slides.height() === 0) {
        this.trigger('resize');
        observer.disconnect();
      }
    }, {
      threshold: [0]
    });
    observer.observe($container.get(0));

    this.loadImageSlides();

    this.$currentSlide = this.imageSlideHolders[0].addClass('h5p-image-slider-current');

    this.attachControls();
  };

  /**
   * Update aria-live region with the current image's alt text.
   */
  C.prototype.announceCurrentSlide = function () {
    this.$screenReaderAnnouncement.text(this.imageSlides[this.currentSlideId].image.alt);
  };

  /**
   * Update layout when entering fullscreen.
   *
   * Many layout changes are handled on resize.
   */
  C.prototype.enterFullScreen = function() {
    this.updateNavButtons();
    this.updateProgressBar();
  };

  /**
   * Update layout when entering fullscreen.
   *
   * Many layout changes are handled on resize.
   */
  C.prototype.exitFullScreen = function() {
    for (var i = 0; i < this.imageSlides.length; i++) {
      this.imageSlides[i].resetAspectRatio();
    }
    this.updateNavButtons();
    this.updateProgressBar();
  };

  /**
   * Adds the HTML for the next three slides to the DOM
   */
  C.prototype.loadImageSlides = function() {
    // Load next three imageSlides (not all for performance reasons)
    for (var i = this.currentSlideId; i < this.imageSlides.length && i < this.currentSlideId + 3; i++) {
      if (this.imageSlideHolders[i] === false) {
        this.imageSlideHolders[i] = $('<div>', {
          'class': 'h5p-image-slide-holder'
        });
        if (i > 0) {
          this.imageSlideHolders[i].attr('aria-hidden', true);
        }
        if (i > this.currentSlideId) {
          this.imageSlideHolders[i].addClass('h5p-image-slider-future');
        }
        this.imageSlides[i].attach(this.imageSlideHolders[i]);
        this.$slides.append(this.imageSlideHolders[i]);
      }
    }
  };

  /**
   * Attaches controls to the DOM
   */
  C.prototype.attachControls = function() {
    var self = this;
    this.$leftButton = this.createControlButton(this.options.a11y.prevSlide, 'left');
    this.$rightButton = this.createControlButton(this.options.a11y.nextSlide, 'right');
    C.handleButtonClick(this.$leftButton, function () {
      if (!self.dragging) {
        self.gotoSlide(self.currentSlideId - 1);
      }
    });

    C.handleButtonClick(this.$rightButton, function() {
      if (!self.dragging) {
        self.gotoSlide(self.currentSlideId + 1);
      }
    });

    this.$slidesHolder.append(this.$leftButton);
    this.$slidesHolder.append(this.$rightButton);
    this.updateNavButtons();
    this.attachProgressBar();
    this.initDragging();
    this.initKeyEvents();
  };

  /**
   * Attaches the progress bar to the DOM
   */
  C.prototype.attachProgressBar = function() {
    this.$progressBar = $('<ul>', {
      class: 'h5p-image-slider-progress'
    });
    for (var i = 0; i < this.imageSlides.length; i++) {
      this.$progressBar.append(this.createProgressBarElement(i));
    }
    this.$slidesHolder.append(this.$progressBar);
  };

  /**
   * Creates a progress bar button
   *
   * @param {Integer} index  - slide index the progress bare element corresponds to
   * @return {jQuery} - progress bar button
   */
  C.prototype.createProgressBarElement = function(index) {
    var self = this;

    var $progressBarButton = $('<button>', {
      class: 'h5p-image-slider-progress-button',
      "aria-label": self.options.a11y.gotoSlide.replace('%slide', index + 1),
      tabindex: 0,
    });

    var $progressBarElement = $('<li>', {
      class: 'h5p-image-slider-progress-element',
    });

    $progressBarElement.append($progressBarButton);

    C.handleButtonClick($progressBarButton, function() {
      self.gotoSlide(index);
    });

    if (index === 0) {
      $progressBarElement.addClass('h5p-image-slider-current-progress-element');
    }
    return $progressBarElement;
  };

  /**
   * Creates a next or previous button
   *
   * @param {string} text - label for the button
   * @param {string} dir - next or prev
   * @return {jQuery} control button
   */
  C.prototype.createControlButton = function(text, dir) {
    var $controlButton = $('<div>', {
      class: 'h5p-image-slider-button ' + 'h5p-image-slider-' + dir + '-button',
    });

    var $controlBg = $('<div>', {
      class: 'h5p-image-slider-button-background'
    });
     $controlButton.append($controlBg);

    var $controlText = $('<div>', {
      class: 'h5p-image-slider-button-text',
      'aria-label': text,
      'role': 'button',
      'tabindex': 0
    });
    $controlButton.append($controlText);

    return $controlButton;
  };

  /**
   * Go to a specific slide
   *
   * @param {Integer} slideId the index of the slide we want to go to
   * @return {Boolean} false if failed(typically the slide didn't exist), true if not
   */
  C.prototype.gotoSlide = function(slideId) {
    if (slideId < 0 || slideId >= this.imageSlideHolders.length) {
      return false;
    }
    $('.h5p-image-slider-removing', this.$container).removeClass('.h5p-image-slider-removing');
    var nextSlideDirection = (this.currentSlideId < slideId) ? 'future' : 'past';
    var prevSlideDirection = nextSlideDirection === 'past' ? 'future' : 'past';
    this.currentSlideId = slideId;
    this.loadImageSlides();
    var $prevSlide = this.$currentSlide;
    var $nextSlide = (this.imageSlideHolders[slideId]);
    if (!this.dragging) {
      this.prepareNextSlideForAnimation($nextSlide, nextSlideDirection);
    }
    setTimeout(function() {
      $nextSlide.removeClass('h5p-image-slider-no-transition');
      $prevSlide.removeClass('h5p-image-slider-current')
        .addClass('h5p-image-slider-removing')
        .removeClass('h5p-image-slider-' + nextSlideDirection)
        .addClass('h5p-image-slider-' + prevSlideDirection)
        .attr('aria-hidden', true);
      $nextSlide.removeClass('h5p-image-slider-past')
        .removeClass('h5p-image-slider-future')
        .addClass('h5p-image-slider-current')
        .removeAttr('aria-hidden');
    }, 1);

    this.$currentSlide = $nextSlide;

    this.announceCurrentSlide();
    this.updateNavButtons();
    this.updateProgressBar();
    return true;
  };

  /**
   * Position the next slide correctly so that it is ready to be aimated in
   *
   * @param {jQuery} $nextSlide the slide to be prepared
   * @param {String} direction prev or next
   */
  C.prototype.prepareNextSlideForAnimation = function($nextSlide, direction) {
    $nextSlide.removeClass('h5p-image-slider-past')
      .removeClass('h5p-image-slider-future')
      .addClass('h5p-image-slider-no-transition')
      .addClass('h5p-image-slider-' + direction);
  };

  /**
   * Updates all navigation buttons, typically toggling and positioning
   */
  C.prototype.updateNavButtons = function() {
    if (this.currentSlideId >= this.imageSlides.length - 1) {
      this.$rightButton.hide();
    }
    else {
      this.$rightButton.show();
    }
    if (this.currentSlideId <= 0) {
      this.$leftButton.hide();
    }
    else {
      this.$leftButton.show();
    }
    var heightInPercent = 100;
    var fullScreenOn = this.$container.hasClass('h5p-fullscreen') || this.$container.hasClass('h5p-semi-fullscreen');
    if (!fullScreenOn) {
      heightInPercent = this.$currentSlide.height() / this.$slides.height() * 100;
    }
    this.$leftButton.css('height', heightInPercent + '%');
    this.$rightButton.css('height', heightInPercent + '%');
  };

  /**
   * Update the progress bar
   *
   * Highlights the element in the progress bar corresponding to the current slide
   * and reposition the progress bar
   */
  C.prototype.updateProgressBar = function () {
    const oldProgressElement = $('.h5p-image-slider-current-progress-element', this.$container).removeClass('h5p-image-slider-current-progress-element');
    const newProgressElement = $('.h5p-image-slider-progress-element', this.$container).eq(this.currentSlideId).addClass('h5p-image-slider-current-progress-element');

    if (oldProgressElement.children('.h5p-image-slider-progress-button').is(':focus')) {
      newProgressElement.children('.h5p-image-slider-progress-button').focus();
    }

    oldProgressElement.children('.h5p-image-slider-progress-button').attr('aria-current', 'false');
    newProgressElement.children('.h5p-image-slider-progress-button').attr('aria-current', 'true');

    var heightInPercent = this.$currentSlide.height() / this.$slides.height() * 100;
    $('.h5p-image-slider-progress', this.$container).css('top', heightInPercent + '%');
  };

  /**
   * Make a slide draggable
   */
  C.prototype.initDragging = function () {
    var self = this;
    this.$slidesHolder.on('touchstart', function(event) {
      self.dragging = true;
      self.dragStartX = event.originalEvent.touches[0].pageX;
      self.$currentSlide.addClass('h5p-image-slider-dragging');
      if (self.isButton(event.target)) {
        event.preventDefault();
        event.stopPropagation();
        var d = new Date();
        self.dragStartTime = d.getTime();
      }
    });

    this.$slidesHolder.on('touchmove', function(event) {
      event.preventDefault();
      self.dragActionUpdate(event.originalEvent.touches[0].pageX);
    });

    this.$slidesHolder.on('touchend', function(event) {
      self.finishDragAction();
      if (self.dragStartTime !== false && self.isButton(event.target)) {
        // This was possibly a click
        var d = new Date();
        if (d.getTime() - self.dragStartTime < 300) {
          if (self.isRightButton(event.target)) {
            self.gotoSlide(self.currentSlideId + 1);
          }
          else {
            self.gotoSlide(self.currentSlideId - 1);
          }
        }
      }
      self.dragStartTime = false;
    });

    this.$slidesHolder.on('touchcancel', function() {
      self.finishDragAction();
      self.dragStartTime = false;
    });
  };

  /**
   * Initialize key press events.
   *
   * @returns {undefined} Nothing.
   */
  C.prototype.initKeyEvents = function () {
    if (this.keydown !== undefined) {
      return;
    }

    var self = this;

    this.keydown = function (event) {
      // Left
      if (event.which === 37) {
        self.gotoSlide(self.currentSlideId - 1);
      }

      // Right
      else if (event.which === 39) {
        self.gotoSlide(self.currentSlideId + 1);
      }
    };
    H5P.jQuery('body').keydown(this.keydown);
  };

  /**
   * Is the domElement a button?
   *
   * @param {DOMElement} domElement the element to be checked
   * @return {Boolean} whether or not the element is a button
   */
  C.prototype.isButton = function (domElement) {
    var $target = $(domElement);
    return $target.hasClass('h5p-image-slider-button-background')
      || $target.hasClass('h5p-image-slider-button-text')
      || $target.hasClass('h5p-image-slider-button');
  };

  /**
   * Is the element the right/next button?
   *
   * @param {DOMElement} domElement the DOM element to be checked
   * @return {Boolean} Whether or not the element is the right button
   */
  C.prototype.isRightButton = function (domElement) {
    var $target = $(domElement);
    return $target.hasClass('h5p-image-slider-right-button')
      || $target.parent().hasClass('h5p-image-slider-right-button');
  };

  /**
   * Update the current and next slide in response to a drag action
   */
  C.prototype.dragActionUpdate = function(x) {
    this.dragXMovement = x - this.dragStartX;
    this.$currentSlide.css('transform', 'translateX(' + this.dragXMovement + 'px)');
    if (this.currentSlideId > 0) {
      var $prevSlide = this.imageSlideHolders[this.currentSlideId - 1].addClass('h5p-image-slider-dragging');
      if (this.dragXMovement < 0) {
        $prevSlide.css('transform', 'translateX(-100.001%)');
      }
      else {
        $prevSlide.css('transform', 'translateX(' + (this.dragXMovement - $prevSlide.width()) + 'px)');
      }
    }
    if (this.currentSlideId < this.imageSlideHolders.length - 1) {
      var $nextSlide = this.imageSlideHolders[this.currentSlideId + 1].addClass('h5p-image-slider-dragging');
      if (this.dragXMovement > 0) {
        $nextSlide.css('transform', 'translateX(100.001%)');
      }
      else {
        $nextSlide.css('transform', 'translateX(' +(this.dragXMovement + $nextSlide.width()) + 'px)');
      }
    }
  };

  /**
   * Actions to be done when a drag action is finished
   *
   * Will either go back to the current slide or finish the transition to the next slide
   */
  C.prototype.finishDragAction = function() {
    $('.h5p-image-slider-dragging', this.$container).removeClass('h5p-image-slider-dragging').each(function() {
      this.style.removeProperty('transform');
    });
    this.dragStartX = undefined;
    var xInPercent = this.dragXMovement / this.$currentSlide.width();
    if (xInPercent < -0.3) {
      if (this.currentSlideId < this.imageSlideHolders.length - 1) {
        this.gotoSlide(this.currentSlideId + 1);
      }
      else {
        this.$currentSlide.css('transform', 'translateX(0%)');
      }
    }
    else if (xInPercent > 0.3) {
      if (this.currentSlideId > 0) {
        this.gotoSlide(this.currentSlideId - 1);
      }
      else {
        this.$currentSlide.css('transform', 'translateX(0%)');
      }
    }
    this.dragging = false;
    this.dragXMovement = 0;
  };

  /**
   * Make a non-button element behave as a button. I.e handle enter and space
   * keydowns as click
   *
   * @param  {H5P.jQuery} $element The "button" element
   * @param  {Function} callback
   */
  C.handleButtonClick = function ($element, callback) {
    $element.click(function (event) {
      callback.call(this, event);
    });
    $element.keydown(function (event) {
      // 32 - space, 13 - enter
      if ([32, 13].indexOf(event.which) !== -1) {
        event.preventDefault();
        callback.call(this, event);
      }
    });
  };

  return C;
})(H5P.jQuery);
;
