import EpubCFI from "./epubcfi";
import {
  qs,
  qsa,
  querySelectorByType,
  indexOfSorted,
  locationOf,
} from "./utils/core";

/**
 * Page List Parser
 * @param {document} [xml]
 */
class PageList {
  constructor(xml) {
    this.pages = [];
    this.locations = [];
    this.epubcfi = new EpubCFI();

    this.firstPage = 0;
    this.lastPage = 0;
    this.totalPages = 0;

    this.toc = undefined;
    this.ncx = undefined;

    if (xml) {
      this.pageList = this.parse(xml);
    }

    if (this.pageList && this.pageList.length) {
      this.process(this.pageList);
    }
  }

  /**
   * Parse PageList Xml
   * @param  {document} xml
   */
  parse(xml) {
    var html = qs(xml, "html");
    var ncx = qs(xml, "ncx");

    if (html) {
      return this.parseNav(xml);
    } else if (ncx) {
      return this.parseNcx(xml);
    }
  }

  /**
   * Parse a Nav PageList
   * @private
   * @param  {node} navHtml
   * @return {PageList.item[]} list
   */
  parseNav(navHtml) {
    var navElement = querySelectorByType(navHtml, "nav", "page-list");
    var navItems = navElement ? qsa(navElement, "li") : [];
    var length = navItems.length;
    var i;
    var list = [];
    var item;

    if (!navItems || length === 0) return list;

    for (i = 0; i < length; ++i) {
      item = this.item(navItems[i]);
      list.push(item);
    }

    return list;
  }

  parseNcx(navXml) {
    var list = [];
    var i = 0;
    var item;
    var pageList;
    var pageTargets;
    var length = 0;

    pageList = qs(navXml, "pageList");
    if (!pageList) return list;

    pageTargets = qsa(pageList, "pageTarget");
    length = pageTargets.length;

    if (!pageTargets || pageTargets.length === 0) {
      return list;
    }

    for (i = 0; i < length; ++i) {
      item = this.ncxItem(pageTargets[i]);
      list.push(item);
    }

    return list;
  }

  ncxItem(item) {
    var navLabel = qs(item, "navLabel");
    var navLabelText = qs(navLabel, "text");
    var pageText = navLabelText.textContent;
    var content = qs(item, "content");

    var href = content.getAttribute("src");
    var page = parseInt(pageText, 10);

    return {
      href: href,
      page: page,
    };
  }

  /**
   * Page List Item
   * @private
   * @param  {node} item
   * @return {object} pageListItem
   */
  item(item) {
    var content = qs(item, "a"),
      href = content.getAttribute("href") || "",
      text = content.textContent || "",
      page = parseInt(text),
      isCfi = href.indexOf("epubcfi"),
      split,
      packageUrl,
      cfi;

    if (isCfi != -1) {
      split = href.split("#");
      packageUrl = split[0];
      cfi = split.length > 1 ? split[1] : false;
      return {
        cfi: cfi,
        href: href,
        packageUrl: packageUrl,
        page: page,
      };
    } else {
      return {
        href: href,
        page: page,
      };
    }
  }

  /**
   * Process pageList items
   * @private
   * @param  {array} pageList
   */
  process(pageList) {
    pageList.forEach(function (item) {
      this.pages.push(item.page);
      if (item.cfi) {
        this.locations.push(item.cfi);
      }
    }, this);
    this.firstPage = parseInt(this.pages[0]);
    this.lastPage = parseInt(this.pages[this.pages.length - 1]);
    this.totalPages = this.lastPage - this.firstPage;
  }

  /**
   * Get a PageList result from a EpubCFI
   * @param  {string} cfi EpubCFI String
   * @return {number} page
   */
  pageFromCfi(cfi) {
    var pg = -1;

    // Check if the pageList has not been set yet
    if (this.locations.length === 0) {
      return -1;
    }

    // TODO: check if CFI is valid?

    // check if the cfi is in the location list
    // var index = this.locations.indexOf(cfi);
    var index = indexOfSorted(cfi, this.locations, this.epubcfi.compare);
    if (index != -1) {
      pg = this.pages[index];
    } else {
      // Otherwise add it to the list of locations
      // Insert it in the correct position in the locations page
      //index = EPUBJS.core.insert(cfi, this.locations, this.epubcfi.compare);
      index = locationOf(cfi, this.locations, this.epubcfi.compare);
      // Get the page at the location just before the new one, or return the first
      pg = index - 1 >= 0 ? this.pages[index - 1] : this.pages[0];
      if (pg !== undefined) {
        // Add the new page in so that the locations and page array match up
        //this.pages.splice(index, 0, pg);
      } else {
        pg = -1;
      }
    }
    return pg;
  }

  /**
   * Get an EpubCFI from a Page List Item
   * @param  {string | number} pg
   * @return {string} cfi
   */
  cfiFromPage(pg) {
    var cfi = -1;
    // check that pg is an int
    if (typeof pg != "number") {
      pg = parseInt(pg);
    }

    // check if the cfi is in the page list
    // Pages could be unsorted.
    var index = this.pages.indexOf(pg);
    if (index != -1) {
      cfi = this.locations[index];
    }
    // TODO: handle pages not in the list
    return cfi;
  }

  /**
   * Get a Page from Book percentage
   * @param  {number} percent
   * @return {number} page
   */
  pageFromPercentage(percent) {
    var pg = Math.round(this.totalPages * percent);
    return pg;
  }

  /**
   * Returns a value between 0 - 1 corresponding to the location of a page
   * @param  {number} pg the page
   * @return {number} percentage
   */
  percentageFromPage(pg) {
    var percentage = (pg - this.firstPage) / this.totalPages;
    return Math.round(percentage * 1000) / 1000;
  }

  /**
   * Returns a value between 0 - 1 corresponding to the location of a cfi
   * @param  {string} cfi EpubCFI String
   * @return {number} percentage
   */
  percentageFromCfi(cfi) {
    var pg = this.pageFromCfi(cfi);
    var percentage = this.percentageFromPage(pg);
    return percentage;
  }

  /**
   * Destroy
   */
  destroy() {
    this.pages = undefined;
    this.locations = undefined;
    this.epubcfi = undefined;

    this.pageList = undefined;

    this.toc = undefined;
    this.ncx = undefined;
  }
}

export default PageList;
