import HTMLManager from './HTMLManager.js';
import Loader from '../Loader.js';

/**
 * @memberof Public.Html.Elements
 */
class ScreenElementManager {

	/**
	 * @typedef Listener
	 * @property {ScreenElement} elm 
	 * @property {string} type 
	 * @property {function} handeler 
	 * @property {boolean} isStatic 
	 * @memberof ScreenElementManager
	 */

	/**
	 * @type {Listener[]} 
	 */
	static get listeners() {return this._listeners || (this._listeners = [])};

	/**
	 * 
	 * @param {ScreenElement} elm 
	 * @param {string} type 
	 * @param {function} handeler 
	 * @param {boolean} isStatic 
	 */
	static addListener(elm, type, handeler, isStatic = false)
	{
		elm.element.addEventListener(type, handeler);
		this.listeners.push({elm, type, handeler, isStatic});

		return elm;
	}

	static allowStaticListener()
	{
		for (let i = this.listeners.length - 1; i >= 0; i--) {
			let lElement = this.listeners[i];

			if (!lElement.isStatic) 
			{
				continue;
			}

			lElement.elm.element.addEventListener(lElement.type, lElement.handeler);
		}
		
		HTMLManager.overlay.addClass("disabled");
	}

	/**
	 * 
	 * @param {ScreenElement} elm
	 */
	static removeListeners(elm)
	{
		for (let i = this.listeners.length - 1; i >= 0; i--) {
			let lElement = this.listeners[i];

			if (lElement.elm != elm) continue;
			
			this.listeners.splice(i, 1);
			lElement.elm.element.removeEventListener(lElement.type, lElement.handeler);
		}

		return elm;
	}

	/**
	 * 
	 * @param {ScreenElement} elm 
	 * @param {string} type 
	 */
	static removeListener(elm, type)
	{
		for (let i = this.listeners.length - 1; i >= 0; i--) {
			let lElement = this.listeners[i];

			if (lElement.elm != elm && lElement.type == type) continue;
			
			this.listeners.splice(i, 1);
			lElement.elm.element.removeEventListener(lElement.type, lElement.handeler);
		}
	}

	static removeListenersOnAllElements(removeStatic = false)
	{
		for (let i = this.listeners.length - 1; i >= 0; i--) {
			let lElement = this.listeners[i];

			if (!lElement.isStatic || removeStatic) 
			{
				this.listeners.splice(i, 1);
			}

			lElement.elm.element.removeEventListener(lElement.type, lElement.handeler);
		}
		
		HTMLManager.overlay.removeClass("disabled");
	}
}

/**
 * Base class for any ScreenElement
 * @example 
 * let body = new ScreenElement("div");
 * body.append( new ScreenElement("pre").setText("My text") );
 * 
 * @memberof Public.Html.Elements
 */
class ScreenElement {

	/**
	 * 
	 * @param {string} tagName 
	 */
	constructor(tagName) 
	{
		/**
		 * @type {HTMLElement}
		 */
		this.element = document.createElement(tagName);
	}

	/**
	 * 
	 * @param  {string} txt 
	 */
	setText(txt) 
	{
		this.element.innerText = txt;
		return this;
	}

	/**
	 * 
	 * @param  {(string | ScreenElement)[]} elements 
	 * @param  {(string | ScreenElement | function)[]} [join]
	 */
	appendList(elements, join = undefined)
	{
		if (join != undefined) 
		{
			join = join.map(m => {
				if (m instanceof Function) return m();
				return m;
			});

			for (let i = elements.length - 2; i >= 1; i--) {
				let joinArgs = Array.from(join)
				joinArgs.unshift(i, 0);

				let lElement = elements[i];
				elements.splice.apply(elements, joinArgs);
			}
		}

		this.append.apply(this, elements);
		return this;
	} 

	/**
	 * 
	 * @param  {...string | ScreenElement} elements 
	 */
	append(...elements) 
	{
		for (let i = 0; i < elements.length; i++)
		{
			let elm = elements[i];
			this.element.append(typeof(elm) == "string" ? elm : elm.element);
		}
		return this;
	}

	

	/**
	 * 
	 * @param  {string[]} classes 
	 */
	addClassList(classes)
	{
		this.append.addClass(this, classes);
		return this;
	}

	/**
	 * 
	 * @param  {...string} classes 
	 */
	addClass(...classes) 
	{
		for (let i = 0; i < classes.length; i++)
		{
			this.element.classList.add(classes[i]);
		}
		return this;
	}

	/**
	 * 
	 * @param  {...string} classes 
	 */
	removeClass(...classes) 
	{
		for (let i = 0; i < classes.length; i++)
		{
			this.element.classList.remove(classes[i]);
		}
		return this;
	}

	setId(id)
	{
		this.element.id = id;
		return this;
	}

	clear()
	{
		this.element.innerHTML = "";
		return this;
	}
}

/**
 * Creates a ScreenElement using an HTMLElement
 * @memberof Public.Html.Elements 
 */
class ScreenElementFromElement extends ScreenElement {
	
	/**
	 * 
	 * @param {HTMLElement} element 
	 */
	constructor(element)
	{
		super();
		this.element = element;
	}
}

/**
 * Creates a screenElement that has a SRC attribute
 * @memberof Public.Html.Elements 
 */
class SrcElement extends ScreenElement 
{
	constructor(tagName, src)
	{
		super(tagName);
		this.setSrc(src);
	}

	setSrc(src)
	{
		this.element.src = src;
	}

	/**
	 * 
	 * @param {boolean} bool 
	 */
	setControls(bool)
	{
		this.element.controls = bool;
		return this;
	}

	/**
	 * 
	 * @param {boolean} bool 
	 */
	setAutoplay(bool)
	{
		this.element.autoplay = bool;
		return this;
	}

	/**
	 * 
	 * @param {string} src 
	 */
	setPoster(src)
	{
		this.element.poster = src;
		return this;
	}
}

/**
 * Creates a button element listening to click event
 * @memberof Public.Html.Elements 
 */
class ButtonElement extends ScreenElement 
{
	/**
	 * 
	 * @param {Function} handeler 
	 * @param {boolean} [isStatic=false] 
	 */
	constructor(handeler, isStatic = false)
	{
		super("button");
		this.handeler = handeler;
		this.isStatic = isStatic;
		this.listen();
	}

	setHandeler(handeler, isStatic = false)
	{
		this.unlisten();
		this.handeler = handeler;
		this.isStatic = isStatic;
		this.listen();
	}

	unlisten() 
	{
		ScreenElementManager.removeListener(this, "click");
	}

	listen() 
	{
		ScreenElementManager.addListener(this, "click", this.handeler, this.isStatic);
	}
}

/**
 * Creates an input
 * @memberof Public.Html.Elements 
 */
class InputElement extends ScreenElement 
{
	/**
	 * 
	 * @param {*} startValue 
	 * @param {string} placeholder
	 */
	constructor(startValue, placeholder)
	{
		super("input");

		/**
		 * @type {HTMLInputElement}
		 */
		this.element;

		this.setValue(startValue);
		this.getValue();
		this.setPlaceolder(placeholder);
	}

	setValue(value)
	{
		this.element.value = value;
		return this;
	}

	getValue()
	{
		return this.element.value;
	}

	setPlaceolder(placeholder)
	{
		this.element.placeholder = placeholder;
		return this;
	}
}

/**
 * Creates button in the main menu (top bar)
 * @memberof Public.Html.Elements 
 */
class MenuButtonElement extends ScreenElement 
{
	/**
	 * 
	 * @param {Function} onclick
	 * @param {string} name
	 */
	constructor(name, onclick)
	{
		super("li");
		this.append(
			new ButtonElement(onclick, true)
			.append(
				new ScreenElement("span").setText(name)
			)
		);
	}
}

/**
 * Creates a div with text that indicates progress
 * @memberof Public.Html.Elements 
 * @abstract
 */
class ProgressIndicator extends ScreenElement
{
	constructor()
	{
		super("div");

		this.setUpProgress();

		this.setProgress(0);
	}

	/**
	 * @virtual
	 * @protected
	 * @abstract
	 */
	setUpProgress(){}

	/**
	 * 
	 * @param {number} p Progress
	 * @returns {this}
	 */
	setProgress(p) 
	{
		this.setText("Progress: "+parseInt(p*100)+"%");
		return this;
	}
}

/**
 * Creates a div with a bar that indicates progress
 * @memberof Public.Html.Elements
 */
class ProgressBarIndicator extends ProgressIndicator
{
	constructor()
	{
		super();
	}

	/**
	 * @virtual
	 * @protected
	 */
	setUpProgress()
	{
		this.addClass("external");
		this.progressContent = new ScreenElement("div").addClass("content");

		this.append(this.progressContent);
	}

	/**
	 * 
	 * @param {number} p Progress
	 * @returns {this}
	 */
	setProgress(p) 
	{
		this.progressContent.element.style.width=`${p*100}%`;
		return this;
	}
}


/*//////////////////////////////////*/
/*        PERSONALISED CLASS        */
/*//////////////////////////////////*/

/**
 * An anime button in the anime list
 * @memberof Public.Html.Elements.Personalised
 */
class AnimeElement extends ScreenElement
{
	/**
	 * @param {*} anime
	 * @param {Function} onclick
	 */
	constructor(anime, onclick)
	{
		super("li");

		this.addClass("anime");
		this.setId(anime.id);
		
		let btn = new ButtonElement(() => {onclick(anime);})
		
		if (anime.thumbnailLink) 
			btn.append(new SrcElement("img", anime.thumbnailLink))
		
		btn.append(new ScreenElement("h1").setText(anime.name))
		this.append(btn);
	}
}

/**
 * An episode button in the anime's episode list
 * @memberof Public.Html.Elements.Personalised
 */
class EpisodeElement extends ScreenElement
{
	/**
	 * @param {*} anime
	 * @param {*} episode
	 * @param {boolean} isEpisodeLocal
	 * @param {Function} onclick
	 */
	constructor(anime, episode, isEpisodeLocal, listIsEpisodeLocal, onclick)
	{
		super("li");

		this.addClass("episode");
		this.setId(`episode ${anime.id}-${episode.episodeId}`);
		
		let btn = new ButtonElement(() => {onclick(anime, episode);})
		
		if (anime.thumbnailLink) 
			btn.append(new SrcElement("img", episode.posterLink))
		
		btn.append(new ScreenElement("h2").setText(episode.name || "Episode " + episode.episodeId));
		this.append(btn);

		if (isEpisodeLocal)
		{
			this.append(new EpisodeWatchButton(anime, episode, listIsEpisodeLocal, "h2"));
		}
	}
}

/**
 * A button to watch the episode
 * @memberof Public.Html.Elements.Personalised
 */
class EpisodeWatchButton extends ButtonElement
{
	/**
	 * @param {*} anime
	 * @param {*} episode
	 * @param {"h1" | "h2" | "h3" | "h4" | "h5" | "h6"} innerTextElementTag
	 */
	constructor(anime, episode, listIsEpisodeLocal, innerTextElementTag="h4")
	{
		super(() => {
			ScreenElementManager.removeListeners();
			Loader.loadLocalEpisode(anime.id, episode.episodeId, listIsEpisodeLocal);
		});

		this.addClass("watch");
		this.append(new ScreenElement(innerTextElementTag).setText("Watch local"));
	}
}

/**
 * A button to return to previous screen 
 * @memberof Public.Html.Elements.Personalised
 */
class ReturnButton extends ButtonElement
{
	/**
	 * @param {Function} handeler
	 */
	constructor(handeler)
	{
		super(handeler);
		this.addClass("return");
		this.append(new ScreenElement("h4").setText("Return"));
	}
}

/**
 * A button to download all the episodes
 * @memberof Public.Html.Elements.Personalised
 * @deprecated
 */
class DownloadAllButton extends ButtonElement
{
	/**
	 * @param {Function} handeler
	 */
	constructor(handeler)
	{
		super(handeler);
		this.addClass("downloadAll");
		this.append(new ScreenElement("h4").setText("Download All"));
	}
}

/**
 * A text that contains infos about the episode
 * @memberof Public.Html.Elements.Personalised
 */
class EpisodeInfoElement extends ScreenElement
{
	/**
	 * @param {*} anime
	 * @param {Function} ondownload
	 */
	constructor(info, oncomplete, catchError)
	{
		super("div");

		this.append(
			`Name : ${info.name}`, new ScreenElement("br"),
			`AnimeId : ${info.animeId}`, new ScreenElement("br"),
			`EpisodeId : ${info.episodeId}`, new ScreenElement("br"),
			`PosterLink : ${info.posterLink}`, new ScreenElement("br"),
			`IsLocal : ${info.isLocal}`, new ScreenElement("br"),
			`HasPoster : ${info.hasPoster}`, new ScreenElement("br")
		)

		for (let i = 0; i < info.players.length; i++) {
			const player = info.players[i];
			this.append(new PlayerInfoElement(info, player, i, oncomplete, catchError));
		}
	}
}

/**
 * A text that contains infos about the videoplayer
 * @memberof Public.Html.Elements.Personalised
 */
class PlayerInfoElement extends ScreenElement
{
	/**
	 * 
	 * @param {string} src
	 * @returns {IframeDownloadPromiseElement}
	 */
	static setCurrentIFrame(src) 
	{
		PlayerInfoElement.closeIframe();
		
		
		PlayerInfoElement.currentIframe = new IframeDownloadPromiseElement(src);
		PlayerInfoElement.currentIframe
		.onEnd( () => {
			PlayerInfoElement.closeIframe();
		});

		HTMLManager.iframeContainer.append(PlayerInfoElement.currentIframe);

		return PlayerInfoElement.currentIframe;
	}

	constructor(info, player, id, oncomplete, catchError)
	{
		super("div");
		this.addClass("tab-1");
		this.append(
			`URL : ${player.url}`, new ScreenElement("br"),
			`isYoutube : ${Boolean(player.isYoutube)}`, new ScreenElement("br"),
			`isNatif : ${player.player.isNatif}`, new ScreenElement("br"),
			`downloadable : ${player.player.downloadable}`, new ScreenElement("br"),
			`autoDownload : ${player.player.autoDownload}`, new ScreenElement("br"),
			`id : ${player.player.id}`, new ScreenElement("br")
		);

		if (!player.player.downloadable)
		{
			this.append(
				new ButtonElement(() => {
					open(player.url, "_blank ", "toolbar=no,menubar=no", false)
				})
				.addClass("iframe-watch")
				.setText("Watch in iFrame")
			)
		}
		else if (player.ytInfo) 
		{
			let ytdlInfoList = [];
			let formats = player.ytInfo.formats;

			for (let index = 0; index < formats.length; index++) {
				const format = formats[index];
				ytdlInfoList.push(new YtDlFormatElement(format, index, () => {
					ScreenElementManager.removeListeners();
					Loader.download(info.animeId, info.episodeId, player.player.id, player.url, format)
					.then(oncomplete)
					.catch(catchError);
				}));
			}

			this.append(
				new ScreenElement("div").addClass("ytInfo").appendList(ytdlInfoList, [new ScreenElement("br"),new ScreenElement("br")]),
				new ScreenElement("br")
			);
		}
		else if (player.player.autoDownload) {
			this.append(
				new ButtonElement( () => {
					ScreenElementManager.removeListeners();
					dlPromise = Loader.download(info.animeId, info.episodeId, player.player.id, player.url)
					.then(oncomplete)
					.catch(catchError); 
				} )
				.addClass("download","autoDownload")
				.setId(`dl ${id}`)
				.setText("Download")
			);
		}
		else {
			this.append(
				new ButtonElement( () => { 
					/*Iframe*/
					PlayerInfoElement.setCurrentIFrame(player.url)
					.onconfirm((it) => {
						alert(`URL : "${it.inputElm.getValue()}"`);
						Loader.download(info.animeId, info.episodeId, player.player.id, it.inputElm.getValue())
						.then(oncomplete)
						.catch(catchError);
					});
				})
				.addClass("download")
				.setId(`dl ${id}`)
				.setText("Show iFrame for download")
			);
		}
	}

	static closeIframe()
	{
		if(PlayerInfoElement.currentIframe) PlayerInfoElement.currentIframe.removeHandelers();
		PlayerInfoElement.currentIframe = null;
		HTMLManager.iframeContainer.clear();
	}
}

/**
 * A text that formats ytdl string
 * @memberof Public.Html.Elements.Personalised
 */
class YtDlFormatElement extends ScreenElement 
{
	constructor(format, index, ondownload) 
	{
		super("div");
		
		this.addClass("tab-1","code");
		this.append(new ScreenElement("pre").addClass("tab-1").setText(JSON.stringify(format,"",3)));
		this.append(
			new ButtonElement( ondownload )
				.addClass("download","yt")
				.setId(index+"-yt")
				.setText("Download")
		);
	}
}

/**
 * An element that opens an Iframe and wait for user input
 * @memberof Public.Html.Elements.Personalised
 */
class IframeDownloadPromiseElement extends ScreenElement 
{
	/**
	 * @callback onConfirmCb
	 * @param {IframeDownloadPromiseElement} th this
	 * @returns {void}
	 * @memberof IframeDownloadPromiseElement
	 */
	
	 /**
	 * @callback onCancelCb
	 * @param {IframeDownloadPromiseElement} th this
	 * @returns {void}
	 * @memberof IframeDownloadPromiseElement
	 */
	
	 /**
	 * @callback onEndCb
	 * @returns {void}
	 * @memberof IframeDownloadPromiseElement
	 */

	/**
	 * 
	 * @param {string} src 
	 */
	constructor(src) 
	{
		super("div");
		this.srcElm = new SrcElement("iframe", src);
		this.inputElm = new InputElement("", "Video Url");
		this.append(
			this.srcElm,
			this.inputElm,
			new ButtonElement(() => {
				//Confirm button
				if (this._confirm) {
					let arr = this._confirm;

					for (let i = 0; i < arr.length; i++) {
						arr[i](this);
					}
				}
				
				this.sendOnEnd();
			})
			.append(
				new ScreenElement("h4").setText("Confirm")
			),
			new ButtonElement(() => {
				//Cansel button
				if (this._cancel) {
					let arr = this._cancel;
	
					for (let i = 0; i < arr.length; i++) {
						arr[i](this);
					}
				}

				this.sendOnEnd();
			})
			.append(
				new ScreenElement("h4").setText("Cansel")
			),
			new ButtonElement(() => {
				open(src, "_blank ", "toolbar=no,menubar=no", false)
			})
			.append(
				new ScreenElement("h4").setText("Open Iframe in new tab")
			),
			new ScreenElement("hr")
		)
	}

	/**
	 * 
	 * @param {string} src 
	 */
	setSrc(src) 
	{
		this.srcElm.setSrc(src);
	}

	/**
	 * 
	 * @param {onConfirmCb} handeler 
	 */
	onconfirm(handeler) 
	{
		/**
		 * @type {onConfirmCb[]}
		 */
		(this._confirm = this._confirm || []).push(handeler);
		return this;
	}

	/**
	 * 
	 * @param {onCancelCb} handeler 
	 */
	oncancel(handeler) 
	{
		/**
		 * @type {onCancelCb[]}
		 */
		(this._cancel = this._cancel || []).push(handeler);
		return this;
	}

	/**
	 * 
	 * @param {onEndCb} handeler 
	 */
	onEnd(handeler) 
	{
		/**
		 * @type {onEndCb[]}
		 */
		(this._end = this._end || []).push(handeler);
		return this;
	}

	/**
	 * @protected
	 */
	sendOnEnd() 
	{
		if (!this._end) return;

		let arr = this._end;

		for (let i = 0; i < arr.length; i++) {
			arr[i]();
		}
	}

	removeHandelers()
	{
		this._cancel = null;
		this._confirm = null;
		this._end = null;
	}
}

/**
 * A progressbar fo
 * @memberof Public.Html.Elements.Personalised
 */
class EpisodeDlProgress extends ScreenElement 
{
	/**
	 * 
	 * @param {string} name 
	 * @param {number} progress 
	 */
	constructor(name, progress)
	{
		super("div");
		
		this.progressBar = new ProgressBarIndicator().setProgress(progress); 
		this.progressText = new ProgressIndicator().setProgress(progress);
		
		this.append(
			this.progressBar,
			this.progressText,
			" - " + name
		);
	}
}

/**
 * When an episode has errors
 * @memberof Public.Html.Elements.Personalised
 */
class EpisodeDlErrorProgress extends ScreenElement 
{
	/**
	 * 
	 * @param {string} name 
	 * @param {string} error 
	 */
	constructor(name, error)
	{
		super("div");
		
		this.nameElm = new ScreenElement("span").addClass("dlError").setText(name); 
		this.errorElm = new ScreenElement("span").setText(error);
		
		this.append(
			this.nameElm,
			this.errorElm,
		);
	}
}

export 
{
	ScreenElementManager,
	ScreenElement,
	ScreenElementFromElement,
	SrcElement,
	ButtonElement,
	InputElement,
	MenuButtonElement,
	ProgressIndicator,
	ProgressBarIndicator,
	AnimeElement,
	EpisodeElement,
	EpisodeWatchButton,
	ReturnButton,
	DownloadAllButton,
	EpisodeInfoElement,
	PlayerInfoElement,
	YtDlFormatElement,
	EpisodeDlProgress,
	EpisodeDlErrorProgress
};