import EventEmitter from "event-emitter";
import { extend, defer, windowBounds, isNumber } from "../../utils/core";
import Mapping from "../../mapping";
import Queue from "../../utils/queue";
import Stage from "../helpers/stage";
import Views from "../helpers/views";
import { EVENTS } from "../../utils/constants";

class DefaultViewManager {
  constructor(options) {
    this.name = "default";
    this.optsSettings = options.settings;
    this.View = options.view;
    this.request = options.request;
    this.renditionQueue = options.queue;
    this.q = new Queue(this);

    this.settings = extend(this.settings || {}, {
      infinite: true,
      hidden: false,
      width: undefined,
      height: undefined,
      axis: undefined,
      flow: "scrolled",
      ignoreClass: "",
      fullsize: undefined,
    });

    extend(this.settings, options.settings || {});

    this.viewSettings = {
      ignoreClass: this.settings.ignoreClass,
      axis: this.settings.axis,
      flow: this.settings.flow,
      layout: this.layout,
      method: this.settings.method, // srcdoc, blobUrl, write
      width: 0,
      height: 0,
      forceEvenPages: true,
    };

    this.rendered = false;
  }

  render(element, size) {
    let tag = element.tagName;

    if (
      typeof this.settings.fullsize === "undefined" &&
      tag &&
      (tag.toLowerCase() == "body" || tag.toLowerCase() == "html")
    ) {
      this.settings.fullsize = true;
    }

    if (this.settings.fullsize) {
      this.settings.overflow = "visible";
      this.overflow = this.settings.overflow;
    }

    this.settings.size = size;

    // Save the stage
    this.stage = new Stage({
      width: size.width,
      height: size.height,
      overflow: this.overflow,
      hidden: this.settings.hidden,
      axis: this.settings.axis,
      fullsize: this.settings.fullsize,
      direction: this.settings.direction,
    });

    this.stage.attachTo(element);

    // Get this stage container div
    this.container = this.stage.getContainer();

    // Views array methods
    this.views = new Views(this.container);

    // Calculate Stage Size
    this._bounds = this.bounds();
    this._stageSize = this.stage.size();

    // Set the dimensions for views
    this.viewSettings.width = this._stageSize.width;
    this.viewSettings.height = this._stageSize.height;

    // Function to handle a resize event.
    // Will only attach if width and height are both fixed.
    this.stage.onResize(this.onResized.bind(this));

    this.stage.onOrientationChange(this.onOrientationChange.bind(this));

    // Add Event Listeners
    this.addEventListeners();

    // Add Layout method
    // this.applyLayoutMethod();
    if (this.layout) {
      this.updateLayout();
    }

    this.rendered = true;
  }

  addEventListeners() {
    var scroller;

    window.addEventListener(
      "unload",
      function (e) {
        this.destroy();
      }.bind(this)
    );

    if (!this.settings.fullsize) {
      scroller = this.container;
    } else {
      scroller = window;
    }

    this._onScroll = this.onScroll.bind(this);
    scroller.addEventListener("scroll", this._onScroll);
  }

  removeEventListeners() {
    var scroller;

    if (!this.settings.fullsize) {
      scroller = this.container;
    } else {
      scroller = window;
    }

    scroller.removeEventListener("scroll", this._onScroll);
    this._onScroll = undefined;
  }

  destroy() {
    clearTimeout(this.orientationTimeout);
    clearTimeout(this.resizeTimeout);
    clearTimeout(this.afterScrolled);

    this.clear();

    this.removeEventListeners();

    this.stage.destroy();

    this.rendered = false;

    /*

			clearTimeout(this.trimTimeout);
			if(this.settings.hidden) {
				this.element.removeChild(this.wrapper);
			} else {
				this.element.removeChild(this.container);
			}
		*/
  }

  onOrientationChange(e) {
    let { orientation } = window;

    if (this.optsSettings.resizeOnOrientationChange) {
      this.resize();
    }

    // Per ampproject:
    // In IOS 10.3, the measured size of an element is incorrect if the
    // element size depends on window size directly and the measurement
    // happens in window.resize event. Adding a timeout for correct
    // measurement. See https://github.com/ampproject/amphtml/issues/8479
    clearTimeout(this.orientationTimeout);
    this.orientationTimeout = setTimeout(
      function () {
        this.orientationTimeout = undefined;

        if (this.optsSettings.resizeOnOrientationChange) {
          this.resize();
        }

        this.emit(EVENTS.MANAGERS.ORIENTATION_CHANGE, orientation);
      }.bind(this),
      500
    );
  }

  onResized(e) {
    this.resize();
  }

  resize(width, height, epubcfi) {
    let stageSize = this.stage.size(width, height);

    // For Safari, wait for orientation to catch up
    // if the window is a square
    this.winBounds = windowBounds();
    if (
      this.orientationTimeout &&
      this.winBounds.width === this.winBounds.height
    ) {
      // reset the stage size for next resize
      this._stageSize = undefined;
      return;
    }

    if (
      this._stageSize &&
      this._stageSize.width === stageSize.width &&
      this._stageSize.height === stageSize.height
    ) {
      // Size is the same, no need to resize
      return;
    }

    this._stageSize = stageSize;

    this._bounds = this.bounds();

    // Clear current views
    this.clear();

    // Update for new views
    this.viewSettings.width = this._stageSize.width;
    this.viewSettings.height = this._stageSize.height;

    this.updateLayout();

    this.emit(
      EVENTS.MANAGERS.RESIZED,
      {
        width: this._stageSize.width,
        height: this._stageSize.height,
      },
      epubcfi
    );
  }

  createView(section) {
    return new this.View(section, this.viewSettings);
  }

  display(section, target) {
    var displaying = new defer();
    var displayed = displaying.promise;

    // Check if moving to target is needed
    if (target === section.href || isNumber(target)) {
      target = undefined;
    }

    // Check to make sure the section we want isn't already shown
    var visible = this.views.find(section);

    // View is already shown, just move to correct location in view
    if (visible && section) {
      let offset = visible.offset();

      if (this.settings.direction === "ltr") {
        this.scrollTo(offset.left, offset.top, true);
      } else {
        let width = visible.width();
        this.scrollTo(offset.left + width, offset.top, true);
      }

      if (target) {
        let offset = visible.locationOf(target);
        this.moveTo(offset);
      }

      displaying.resolve();
      return displayed;
    }

    // Hide all current views
    this.clear();

    this.add(section)
      .then(
        function (view) {
          // Move to correct place within the section, if needed
          if (target) {
            let offset = view.locationOf(target);
            this.moveTo(offset);
          }
        }.bind(this),
        (err) => {
          displaying.reject(err);
        }
      )
      .then(
        function () {
          var next;
          if (
            this.layout.name === "pre-paginated" &&
            this.layout.divisor > 1 &&
            section.index > 0
          ) {
            // First page (cover) should stand alone for pre-paginated books
            next = section.next();
            if (next) {
              return this.add(next);
            }
          }
        }.bind(this)
      )
      .then(
        function () {
          this.views.show();

          displaying.resolve();
        }.bind(this)
      );
    // .then(function(){
    // 	return this.hooks.display.trigger(view);
    // }.bind(this))
    // .then(function(){
    // 	this.views.show();
    // }.bind(this));
    return displayed;
  }

  afterDisplayed(view) {
    this.emit(EVENTS.MANAGERS.ADDED, view);
  }

  afterResized(view) {
    this.emit(EVENTS.MANAGERS.RESIZE, view.section);
  }

  moveTo(offset) {
    var distX = 0,
      distY = 0;

    if (!this.isPaginated) {
      distY = offset.top;
    } else {
      distX = Math.floor(offset.left / this.layout.delta) * this.layout.delta;

      if (distX + this.layout.delta > this.container.scrollWidth) {
        distX = this.container.scrollWidth - this.layout.delta;
      }
    }
    this.scrollTo(distX, distY, true);
  }

  add(section) {
    var view = this.createView(section);

    this.views.append(view);

    // view.on(EVENTS.VIEWS.SHOWN, this.afterDisplayed.bind(this));
    view.onDisplayed = this.afterDisplayed.bind(this);
    view.onResize = this.afterResized.bind(this);

    view.on(EVENTS.VIEWS.AXIS, (axis) => {
      this.updateAxis(axis);
    });

    return view.display(this.request);
  }

  append(section) {
    var view = this.createView(section);
    this.views.append(view);

    view.onDisplayed = this.afterDisplayed.bind(this);
    view.onResize = this.afterResized.bind(this);

    view.on(EVENTS.VIEWS.AXIS, (axis) => {
      this.updateAxis(axis);
    });

    return view.display(this.request);
  }

  prepend(section) {
    var view = this.createView(section);

    view.on(EVENTS.VIEWS.RESIZED, (bounds) => {
      this.counter(bounds);
    });

    this.views.prepend(view);

    view.onDisplayed = this.afterDisplayed.bind(this);
    view.onResize = this.afterResized.bind(this);

    view.on(EVENTS.VIEWS.AXIS, (axis) => {
      this.updateAxis(axis);
    });

    return view.display(this.request);
  }

  counter(bounds) {
    if (this.settings.axis === "vertical") {
      this.scrollBy(0, bounds.heightDelta, true);
    } else {
      this.scrollBy(bounds.widthDelta, 0, true);
    }
  }

  // resizeView(view) {
  //
  // 	if(this.settings.globalLayoutProperties.layout === "pre-paginated") {
  // 		view.lock("both", this.bounds.width, this.bounds.height);
  // 	} else {
  // 		view.lock("width", this.bounds.width, this.bounds.height);
  // 	}
  //
  // };

  next() {
    var next;
    var left;

    let dir = this.settings.direction;

    if (!this.views.length) return;

    if (
      this.isPaginated &&
      this.settings.axis === "horizontal" &&
      (!dir || dir === "ltr")
    ) {
      this.scrollLeft = this.container.scrollLeft;

      left =
        this.container.scrollLeft +
        this.container.offsetWidth +
        this.layout.delta;

      if (left <= this.container.scrollWidth) {
        this.scrollBy(this.layout.delta, 0, true);
      } else {
        next = this.views.last().section.next();
      }
    } else if (
      this.isPaginated &&
      this.settings.axis === "horizontal" &&
      dir === "rtl"
    ) {
      this.scrollLeft = this.container.scrollLeft;

      left = this.container.scrollLeft;

      if (left > 0) {
        this.scrollBy(this.layout.delta, 0, true);
      } else {
        next = this.views.last().section.next();
      }
    } else if (this.isPaginated && this.settings.axis === "vertical") {
      this.scrollTop = this.container.scrollTop;

      let top = this.container.scrollTop + this.container.offsetHeight;

      if (top < this.container.scrollHeight) {
        this.scrollBy(0, this.layout.height, true);
      } else {
        next = this.views.last().section.next();
      }
    } else {
      next = this.views.last().section.next();
    }

    if (next) {
      this.clear();

      return this.append(next)
        .then(
          function () {
            var right;
            if (
              this.layout.name === "pre-paginated" &&
              this.layout.divisor > 1
            ) {
              right = next.next();
              if (right) {
                return this.append(right);
              }
            }
          }.bind(this),
          (err) => {
            return err;
          }
        )
        .then(
          function () {
            this.views.show();
          }.bind(this)
        );
    }
  }

  prev() {
    var prev;
    var left;
    let dir = this.settings.direction;

    if (!this.views.length) return;

    if (
      this.isPaginated &&
      this.settings.axis === "horizontal" &&
      (!dir || dir === "ltr")
    ) {
      this.scrollLeft = this.container.scrollLeft;

      left = this.container.scrollLeft;

      if (left > 0) {
        this.scrollBy(-this.layout.delta, 0, true);
      } else {
        prev = this.views.first().section.prev();
      }
    } else if (
      this.isPaginated &&
      this.settings.axis === "horizontal" &&
      dir === "rtl"
    ) {
      this.scrollLeft = this.container.scrollLeft;

      left =
        this.container.scrollLeft +
        this.container.offsetWidth +
        this.layout.delta;

      if (left <= this.container.scrollWidth) {
        this.scrollBy(-this.layout.delta, 0, true);
      } else {
        prev = this.views.first().section.prev();
      }
    } else if (this.isPaginated && this.settings.axis === "vertical") {
      this.scrollTop = this.container.scrollTop;

      let top = this.container.scrollTop;

      if (top > 0) {
        this.scrollBy(0, -this.layout.height, true);
      } else {
        prev = this.views.first().section.prev();
      }
    } else {
      prev = this.views.first().section.prev();
    }

    if (prev) {
      this.clear();

      return this.prepend(prev)
        .then(
          function () {
            var left;
            if (
              this.layout.name === "pre-paginated" &&
              this.layout.divisor > 1
            ) {
              left = prev.prev();
              if (left) {
                return this.prepend(left);
              }
            }
          }.bind(this),
          (err) => {
            return err;
          }
        )
        .then(
          function () {
            if (this.isPaginated && this.settings.axis === "horizontal") {
              if (this.settings.direction === "rtl") {
                this.scrollTo(0, 0, true);
              } else {
                this.scrollTo(
                  this.container.scrollWidth - this.layout.delta,
                  0,
                  true
                );
              }
            }
            this.views.show();
          }.bind(this)
        );
    }
  }

  current() {
    var visible = this.visible();
    if (visible.length) {
      // Current is the last visible view
      return visible[visible.length - 1];
    }
    return null;
  }

  clear() {
    // this.q.clear();

    if (this.views) {
      this.views.hide();
      this.scrollTo(0, 0, true);
      this.views.clear();
    }
  }

  currentLocation() {
    if (this.settings.axis === "vertical") {
      this.location = this.scrolledLocation();
    } else {
      this.location = this.paginatedLocation();
    }
    return this.location;
  }

  scrolledLocation() {
    let visible = this.visible();
    let container = this.container.getBoundingClientRect();
    let pageHeight =
      container.height < window.innerHeight
        ? container.height
        : window.innerHeight;

    let offset = 0;
    let used = 0;

    if (this.settings.fullsize) {
      offset = window.scrollY;
    }

    let sections = visible.map((view) => {
      let { index, href } = view.section;
      let position = view.position();
      let height = view.height();

      let startPos = offset + container.top - position.top + used;
      let endPos = startPos + pageHeight - used;
      if (endPos > height) {
        endPos = height;
        used = endPos - startPos;
      }

      let totalPages = this.layout.count(height, pageHeight).pages;

      let currPage = Math.ceil(startPos / pageHeight);
      let pages = [];
      let endPage = Math.ceil(endPos / pageHeight);

      pages = [];
      for (var i = currPage; i <= endPage; i++) {
        let pg = i + 1;
        pages.push(pg);
      }

      let mapping = this.mapping.page(
        view.contents,
        view.section.cfiBase,
        startPos,
        endPos
      );

      return {
        index,
        href,
        pages,
        totalPages,
        mapping,
      };
    });

    return sections;
  }

  paginatedLocation() {
    let visible = this.visible();
    let container = this.container.getBoundingClientRect();

    let left = 0;
    let used = 0;

    if (this.settings.fullsize) {
      left = window.scrollX;
    }

    let sections = visible.map((view) => {
      let { index, href } = view.section;
      let offset = view.offset().left;
      let position = view.position().left;
      let width = view.width();

      // Find mapping
      let start = left + container.left - position + used;
      let end = start + this.layout.width - used;

      let mapping = this.mapping.page(
        view.contents,
        view.section.cfiBase,
        start,
        end
      );

      // Find displayed pages
      //console.log("pre", end, offset + width);
      // if (end > offset + width) {
      // 	end = offset + width;
      // 	used = this.layout.pageWidth;
      // }
      // console.log("post", end);

      let totalPages = this.layout.count(width).pages;
      let startPage = Math.floor(start / this.layout.pageWidth);
      let pages = [];
      let endPage = Math.floor(end / this.layout.pageWidth);

      // start page should not be negative
      if (startPage < 0) {
        startPage = 0;
        endPage = endPage + 1;
      }

      // Reverse page counts for rtl
      if (this.settings.direction === "rtl") {
        let tempStartPage = startPage;
        startPage = totalPages - endPage;
        endPage = totalPages - tempStartPage;
      }

      for (var i = startPage + 1; i <= endPage; i++) {
        let pg = i;
        pages.push(pg);
      }

      return {
        index,
        href,
        pages,
        totalPages,
        mapping,
      };
    });

    return sections;
  }

  isVisible(view, offsetPrev, offsetNext, _container) {
    var position = view.position();
    var container = _container || this.bounds();

    if (
      this.settings.axis === "horizontal" &&
      position.right > container.left - offsetPrev &&
      position.left < container.right + offsetNext
    ) {
      return true;
    } else if (
      this.settings.axis === "vertical" &&
      position.bottom > container.top - offsetPrev &&
      position.top < container.bottom + offsetNext
    ) {
      return true;
    }

    return false;
  }

  visible() {
    var container = this.bounds();
    var views = this.views.displayed();
    var viewsLength = views.length;
    var visible = [];
    var isVisible;
    var view;

    for (var i = 0; i < viewsLength; i++) {
      view = views[i];
      isVisible = this.isVisible(view, 0, 0, container);

      if (isVisible === true) {
        visible.push(view);
      }
    }
    return visible;
  }

  scrollBy(x, y, silent) {
    let dir = this.settings.direction === "rtl" ? -1 : 1;

    if (silent) {
      this.ignore = true;
    }

    if (!this.settings.fullsize) {
      if (x) this.container.scrollLeft += x * dir;
      if (y) this.container.scrollTop += y;
    } else {
      window.scrollBy(x * dir, y * dir);
    }
    this.scrolled = true;
  }

  scrollTo(x, y, silent) {
    if (silent) {
      this.ignore = true;
    }

    if (!this.settings.fullsize) {
      this.container.scrollLeft = x;
      this.container.scrollTop = y;
    } else {
      window.scrollTo(x, y);
    }
    this.scrolled = true;
  }

  onScroll() {
    let scrollTop;
    let scrollLeft;

    if (!this.settings.fullsize) {
      scrollTop = this.container.scrollTop;
      scrollLeft = this.container.scrollLeft;
    } else {
      scrollTop = window.scrollY;
      scrollLeft = window.scrollX;
    }

    this.scrollTop = scrollTop;
    this.scrollLeft = scrollLeft;

    if (!this.ignore) {
      this.emit(EVENTS.MANAGERS.SCROLL, {
        top: scrollTop,
        left: scrollLeft,
      });

      clearTimeout(this.afterScrolled);
      this.afterScrolled = setTimeout(
        function () {
          this.emit(EVENTS.MANAGERS.SCROLLED, {
            top: this.scrollTop,
            left: this.scrollLeft,
          });
        }.bind(this),
        20
      );
    } else {
      this.ignore = false;
    }
  }

  bounds() {
    var bounds;

    bounds = this.stage.bounds();

    return bounds;
  }

  applyLayout(layout) {
    this.layout = layout;
    this.updateLayout();
    // this.manager.layout(this.layout.format);
  }

  updateLayout() {
    if (!this.stage) {
      return;
    }

    this._stageSize = this.stage.size();

    if (!this.isPaginated) {
      this.layout.calculate(this._stageSize.width, this._stageSize.height);
    } else {
      this.layout.calculate(
        this._stageSize.width,
        this._stageSize.height,
        this.settings.gap
      );

      // Set the look ahead offset for what is visible
      this.settings.offset = this.layout.delta;

      // this.stage.addStyleRules("iframe", [{"margin-right" : this.layout.gap + "px"}]);
    }

    // Set the dimensions for views
    this.viewSettings.width = this.layout.width;
    this.viewSettings.height = this.layout.height;

    this.setLayout(this.layout);
  }

  setLayout(layout) {
    this.viewSettings.layout = layout;

    this.mapping = new Mapping(
      layout.props,
      this.settings.direction,
      this.settings.axis
    );

    if (this.views) {
      this.views.forEach(function (view) {
        if (view) {
          view.setLayout(layout);
        }
      });
    }
  }

  updateAxis(axis, forceUpdate) {
    if (!this.isPaginated) {
      axis = "vertical";
    }

    if (!forceUpdate && axis === this.settings.axis) {
      return;
    }

    this.settings.axis = axis;

    this.stage && this.stage.axis(axis);

    this.viewSettings.axis = axis;

    if (this.mapping) {
      this.mapping = new Mapping(
        this.layout.props,
        this.settings.direction,
        this.settings.axis
      );
    }

    if (this.layout) {
      if (axis === "vertical") {
        this.layout.spread("none");
      } else {
        this.layout.spread(this.layout.settings.spread);
      }
    }
  }

  updateFlow(flow, defaultScrolledOverflow = "auto") {
    let isPaginated = flow === "paginated" || flow === "auto";

    this.isPaginated = isPaginated;

    if (
      flow === "scrolled-doc" ||
      flow === "scrolled-continuous" ||
      flow === "scrolled"
    ) {
      this.updateAxis("vertical");
    } else {
      this.updateAxis("horizontal");
    }

    this.viewSettings.flow = flow;

    if (!this.settings.overflow) {
      this.overflow = isPaginated ? "hidden" : defaultScrolledOverflow;
    } else {
      this.overflow = this.settings.overflow;
    }

    this.stage && this.stage.overflow(this.overflow);

    this.updateLayout();
  }

  getContents() {
    var contents = [];
    if (!this.views) {
      return contents;
    }
    this.views.forEach(function (view) {
      const viewContents = view && view.contents;
      if (viewContents) {
        contents.push(viewContents);
      }
    });
    return contents;
  }

  direction(dir = "ltr") {
    this.settings.direction = dir;

    this.stage && this.stage.direction(dir);

    this.viewSettings.direction = dir;

    this.updateLayout();
  }

  isRendered() {
    return this.rendered;
  }
}

//-- Enable binding events to Manager
EventEmitter(DefaultViewManager.prototype);

export default DefaultViewManager;
