/**
 * @module Book
 */

import Image from './image';
import Tag from './tag';

/**
 * Book object from API.
 * @global
 * @typedef {object} APIBook
 * @property {object}        title          Book title.
 * @property {string}        title.english  Book english title.
 * @property {string}        title.japanese Book japanese title.
 * @property {string}        title.pretty   Book short title.
 * @property {number|string} id             Book ID.
 * @property {number|string} media_id       Book Media ID.
 * @property {number|string} num_favorites  Book favours count.
 * @property {number|string} num_pages      Book pages count.
 * @property {string}        scanlator      Book scanlator.
 * @property {number|string} uploaded       Upload UNIX timestamp.
 * @property {APIImage}      cover          Book cover image.
 * @property {APIImage[]}    images         Book pages' images.
 * @property {APITag[]}      tags           Book tags.
 */

/**
 * Book title.
 * @typedef {object} BookTitle
 * @property {string} english  Book english title.
 * @property {string} japanese Book japanese title.
 * @property {string} pretty   Book short title.
 */

/**
 * Class representing Book.
 * @class
 */
class Book {
	/**
	 * Unknown book instance.
	 * @type {UnknownBook}
	 * @static
	 */
	static Unknown;

	/**
	 * UnknownBook class.
	 * @type {UnknownBook}
	 * @static
	 */
	static UnknownBook;

	/**
	 * Parse book object into class instance.
	 * @param {APIBook} book Book.
	 * @returns {Book} Book instance.
	 * @static
	 */
	static parse(book) {
		return new this({
			title    : book.title,
			id       : +book.id,
			media    : +book.media_id,
			favorites: +book.num_favorites,
			scanlator: book.scanlator,
			uploaded : new Date(+book.upload_date * 1000),
			tags     : book.tags.map(tag => new Tag(tag)),
			cover    : Image.parse(book.images.cover),
			pages    : book.images.pages.map(
				(image, id) => Image.parse(image, ++id)
			),
		});
	}

	/**
	 * Book title.
	 * @type {BookTitle}
	 */
	title = {
		english : '',
		japanese: '',
		pretty  : '',
	};

	/**
	 * Book ID.
	 * @type {number}
	 */
	id = 0;

	/**
	 * Book Media ID.
	 * @type {number}
	 */
	media = 0;

	/**
	 * Book favours count.
	 * @type {number}
	 */
	favorites = 0;

	/**
	 * Book scanlator.
	 * @type {string}
	 */
	scanlator = '';

	/**
	 * Book upload date.
	 * @type {Date}
	 */
	uploaded = new Date(0);

	/**
	 * Book tags.
	 * @type {Tag[]}
	 */
	tags = [];

	/**
	 * Book cover.
	 * @type {Image}
	 */
	cover = new Image({ id: 0, book: this, });

	/**
	 * Book pages.
	 * @type {Image[]}
	 */
	pages = [];

	/**
	 * Create book.
	 * @param {object}    [params]              Book parameters.
	 * @param {BookTitle} [params.title]        Book title.
	 * @param {number}    [params.id=0]         Book ID.
	 * @param {number}    [params.media=0]      Book Media ID.
	 * @param {number}    [params.favorites=0]  Book favours count.
	 * @param {string}    [params.scanlator=''] Book scanlator.
	 * @param {Date}      [params.uploaded]     Book upload date.
	 * @param {Tag[]}     [params.tags=[]]      Book tags.
	 * @param {Image}     [params.cover]        Book cover.
	 * @param {Image[]}   [params.pages=[]]     Book pages.
	 */
	constructor({
		title     = {
			english : '',
			japanese: '',
			pretty  : '',
		},
		id        = 0,
		media     = 0,
		favorites = 0,
		scanlator = '',
		uploaded  = new Date(0),
		tags      = [],
		cover     = new Image({ id: 0, book: this, }),
		pages     = [],
	} = {}) {
		this.setCover(cover);

		if (Array.isArray(pages))
			pages.forEach(this.pushPage.bind(this));

		if (Array.isArray(tags))
			tags.forEach(this.pushTag.bind(this));

		Object.assign(this, {
			title,
			id,
			media,
			favorites,
			scanlator,
			uploaded,
		});
	}

	/**
	 * Check whatever book is known.
	 * @type {boolean}
	 */
	get isKnown() {
		return !(this instanceof UnknownBook);
	}

	/**
	 * Set book cover image.
	 * @param {Image} cover Image.
	 * @returns {boolean} Whatever cover was set.
	 * @private
	 */
	setCover(cover) {
		if (cover instanceof Image) {
			cover.book = this;
			this.cover = cover;
			return true;
		}
		return false;
	}

	/**
	 * Push image to book pages.
	 * @param {Image} page Image.
	 * @returns {boolean} Whatever page was added.
	 * @private
	 */
	pushPage(page) {
		if (page instanceof Image) {
			page.book = this;
			this.pages.push(page);
			return true;
		}
		return false;
	}

	/**
	 * Push tag to book tags.
	 * @param {Tag} tag Tag.
	 * @returns {boolean} Whatever tag was added.
	 * @private
	 */
	pushTag(tag) {
		tag = Tag.get(tag);

		if (!this.hasTag(tag)) {
			this.tags.push(tag);
			return true;
		}
		return false;
	}

	/**
	 * Check if book has certain tag.
	 * @param {Tag}     tag            Tag
	 * @param {boolean} [strict=false] Strict comparison.
	 */
	hasTag(tag, strict = true) {
		tag = Tag.get(tag);

		if (tag instanceof Tag) {
			return this.tags.some(elem => elem.compare(tag, strict));
		}
		return false;
	}

	/**
	 * Check if book has any tags with certain properties.
	 * @param {object|Tag} tag Tag.
	 */
	hasTagWith(tag) {
		return this.hasTag(tag, false);
	}
}

/**
 * Class representing unknown book.
 * @class
 * @extends Book
 */
class UnknownBook extends Book {
	/**
	 * Create unknown book.
	 */
	constructor() {
		super({});
	}
}

Book.UnknownBook = UnknownBook;
Book.Unknown = new UnknownBook();

export default Book;