import {qs, qsa, qsp, indexOfElementNode} from "./utils/core";

/**
 * Open Packaging Format Parser
 * @class
 * @param {document} packageDocument OPF XML
 */
class Packaging {
	constructor(packageDocument) {
		this.manifest = {};
		this.navPath = '';
		this.ncxPath = '';
		this.coverPath = '';
		this.spineNodeIndex = 0;
		this.spine = [];
		this.metadata = {};

		if (packageDocument) {
			this.parse(packageDocument);
		}
	}

	/**
	 * Parse OPF XML
	 * @param  {document} packageDocument OPF XML
	 * @return {object} parsed package parts
	 */
	parse(packageDocument){
		var metadataNode, manifestNode, spineNode;

		if(!packageDocument) {
			throw new Error("Package File Not Found");
		}

		metadataNode = qs(packageDocument, "metadata");
		if(!metadataNode) {
			throw new Error("No Metadata Found");
		}

		manifestNode = qs(packageDocument, "manifest");
		if(!manifestNode) {
			throw new Error("No Manifest Found");
		}

		spineNode = qs(packageDocument, "spine");
		if(!spineNode) {
			throw new Error("No Spine Found");
		}

		this.manifest = this.parseManifest(manifestNode);
		this.navPath = this.findNavPath(manifestNode);
		this.ncxPath = this.findNcxPath(manifestNode, spineNode);
		this.coverPath = this.findCoverPath(packageDocument);

		this.spineNodeIndex = indexOfElementNode(spineNode);

		this.spine = this.parseSpine(spineNode, this.manifest);

		this.metadata = this.parseMetadata(metadataNode);

		this.metadata.direction = spineNode.getAttribute("page-progression-direction");


		return {
			"metadata" : this.metadata,
			"spine"    : this.spine,
			"manifest" : this.manifest,
			"navPath"  : this.navPath,
			"ncxPath"  : this.ncxPath,
			"coverPath": this.coverPath,
			"spineNodeIndex" : this.spineNodeIndex
		};
	}

	/**
	 * Parse Metadata
	 * @private
	 * @param  {document} xml
	 * @return {object} metadata
	 */
	parseMetadata(xml){
		var metadata = {};

		metadata.title = this.getElementText(xml, "title");
		metadata.creator = this.getElementText(xml, "creator");
		metadata.description = this.getElementText(xml, "description");

		metadata.pubdate = this.getElementText(xml, "date");

		metadata.publisher = this.getElementText(xml, "publisher");

		metadata.identifier = this.getElementText(xml, "identifier");
		metadata.language = this.getElementText(xml, "language");
		metadata.rights = this.getElementText(xml, "rights");

		metadata.modified_date = this.getPropertyText(xml, "dcterms:modified");

		metadata.layout = this.getPropertyText(xml, "rendition:layout");
		metadata.orientation = this.getPropertyText(xml, "rendition:orientation");
		metadata.flow = this.getPropertyText(xml, "rendition:flow");
		metadata.viewport = this.getPropertyText(xml, "rendition:viewport");
		// metadata.page_prog_dir = packageXml.querySelector("spine").getAttribute("page-progression-direction");

		return metadata;
	}

	/**
	 * Parse Manifest
	 * @private
	 * @param  {document} manifestXml
	 * @return {object} manifest
	 */
	parseManifest(manifestXml){
		var manifest = {};

		//-- Turn items into an array
		// var selected = manifestXml.querySelectorAll("item");
		var selected = qsa(manifestXml, "item");
		var items = Array.prototype.slice.call(selected);

		//-- Create an object with the id as key
		items.forEach(function(item){
			var id = item.getAttribute("id"),
					href = item.getAttribute("href") || "",
					type = item.getAttribute("media-type") || "",
					properties = item.getAttribute("properties") || "";

			manifest[id] = {
				"href" : href,
				// "url" : href,
				"type" : type,
				"properties" : properties.length ? properties.split(" ") : []
			};

		});

		return manifest;

	}

	/**
	 * Parse Spine
	 * @param  {document} spineXml
	 * @param  {Packaging.manifest} manifest
	 * @return {object} spine
	 */
	parseSpine(spineXml, manifest){
		var spine = [];

		var selected = qsa(spineXml, "itemref");
		var items = Array.prototype.slice.call(selected);

		// var epubcfi = new EpubCFI();

		//-- Add to array to mantain ordering and cross reference with manifest
		items.forEach(function(item, index){
			var idref = item.getAttribute("idref");
			// var cfiBase = epubcfi.generateChapterComponent(spineNodeIndex, index, Id);
			var props = item.getAttribute("properties") || "";
			var propArray = props.length ? props.split(" ") : [];
			// var manifestProps = manifest[Id].properties;
			// var manifestPropArray = manifestProps.length ? manifestProps.split(" ") : [];

			var itemref = {
				"idref" : idref,
				"linear" : item.getAttribute("linear") || "yes",
				"properties" : propArray,
				// "href" : manifest[Id].href,
				// "url" :  manifest[Id].url,
				"index" : index
				// "cfiBase" : cfiBase
			};
			spine.push(itemref);
		});

		return spine;
	}

	/**
	 * Find TOC NAV
	 * @private
	 */
	findNavPath(manifestNode){
		// Find item with property "nav"
		// Should catch nav irregardless of order
		// var node = manifestNode.querySelector("item[properties$='nav'], item[properties^='nav '], item[properties*=' nav ']");
		var node = qsp(manifestNode, "item", {"properties":"nav"});
		return node ? node.getAttribute("href") : false;
	}

	/**
	 * Find TOC NCX
	 * media-type="application/x-dtbncx+xml" href="toc.ncx"
	 * @private
	 */
	findNcxPath(manifestNode, spineNode){
		// var node = manifestNode.querySelector("item[media-type='application/x-dtbncx+xml']");
		var node = qsp(manifestNode, "item", {"media-type":"application/x-dtbncx+xml"});
		var tocId;

		// If we can't find the toc by media-type then try to look for id of the item in the spine attributes as
		// according to http://www.idpf.org/epub/20/spec/OPF_2.0.1_draft.htm#Section2.4.1.2,
		// "The item that describes the NCX must be referenced by the spine toc attribute."
		if (!node) {
			tocId = spineNode.getAttribute("toc");
			if(tocId) {
				// node = manifestNode.querySelector("item[id='" + tocId + "']");
				node = manifestNode.getElementById(tocId);
			}
		}

		return node ? node.getAttribute("href") : false;
	}

	/**
	 * Find the Cover Path
	 * <item properties="cover-image" id="ci" href="cover.svg" media-type="image/svg+xml" />
	 * Fallback for Epub 2.0
	 * @param  {document} packageXml
	 * @return {string} href
	 */
	findCoverPath(packageXml){
		var pkg = qs(packageXml, "package");
		var epubVersion = pkg.getAttribute("version");

		if (epubVersion === "2.0") {
			var metaCover = qsp(packageXml, "meta", {"name":"cover"});
			if (metaCover) {
				var coverId = metaCover.getAttribute("content");
				// var cover = packageXml.querySelector("item[id='" + coverId + "']");
				var cover = packageXml.getElementById(coverId);
				return cover ? cover.getAttribute("href") : "";
			}
			else {
				return false;
			}
		}
		else {
			// var node = packageXml.querySelector("item[properties='cover-image']");
			var node = qsp(packageXml, "item", {"properties":"cover-image"});
			return node ? node.getAttribute("href") : "";
		}
	}

	/**
	 * Get text of a namespaced element
	 * @private
	 * @param  {document} xml
	 * @param  {string} tag
	 * @return {string} text
	 */
	getElementText(xml, tag){
		var found = xml.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/", tag);
		var el;

		if(!found || found.length === 0) return "";

		el = found[0];

		if(el.childNodes.length){
			return el.childNodes[0].nodeValue;
		}

		return "";

	}

	/**
	 * Get text by property
	 * @private
	 * @param  {document} xml
	 * @param  {string} property
	 * @return {string} text
	 */
	getPropertyText(xml, property){
		var el = qsp(xml, "meta", {"property":property});

		if(el && el.childNodes.length){
			return el.childNodes[0].nodeValue;
		}

		return "";
	}

	/**
	 * Load JSON Manifest
	 * @param  {document} packageDocument OPF XML
	 * @return {object} parsed package parts
	 */
	load(json) {
		this.metadata = json.metadata;

		this.spine = json.spine.map((item, index) =>{
			item.index = index;
			return item;
		});

		json.resources.forEach((item, index) => {
			this.manifest[index] = item;

			if (item.rel && item.rel[0] === "cover") {
				this.coverPath = item.href;
			}
		});

		this.spineNodeIndex = 0;

		this.toc = json.toc.map((item, index) =>{
			item.label = item.title;
			return item;
		});

		return {
			"metadata" : this.metadata,
			"spine"    : this.spine,
			"manifest" : this.manifest,
			"navPath"  : this.navPath,
			"ncxPath"  : this.ncxPath,
			"coverPath": this.coverPath,
			"spineNodeIndex" : this.spineNodeIndex,
			"toc" : this.toc
		};
	}

	destroy() {
		this.manifest = undefined;
		this.navPath = undefined;
		this.ncxPath = undefined;
		this.coverPath = undefined;
		this.spineNodeIndex = undefined;
		this.spine = undefined;
		this.metadata = undefined;
	}
}

export default Packaging;
