const curseforge = require("../index");

const fs = require("fs");
const ph = require("path");
const crypto = require("crypto");
const https = require("https");

module.exports = class {
	/**
	 * @method ModFile.download
	 * @description Download the file to a specific file
	 * @param {string} path - absolute path to save the mod to.
	 * @param {boolean} override - Should the file be overwritten if it already exists? Defaults to false.
	 * @param {boolean} simulate - Doesn't download a file it just tries to find the proper website. Used for testing.
	 * @param {function} callback - Optional callback to use as alternative to Promise.
	 * @returns {Promise.<path>} A Promise containing the selected absolute path for convenience.
	 */
	download(
		path,
		override = false,
		simulate = false,
		callback,
		url = this.download_url,
		tries = 10
	) {
		if (override && typeof override === "function") {
			callback = override;
			override = false;
		} else if (override && typeof override === "object") {
			override = override.override;
		}

		let promise = new Promise((resolve, reject) => {
			if (tries < 1) reject("Download canceled after 10 redirects.");
			if (!ph.isAbsolute(path)) reject("Path is not absolute.");
			if (fs.existsSync(path)) {
				if (override) fs.unlinkSync(path);
				else reject("File exists and override is false");
			}
			https.get(url, (response) => {
				if (response.statusCode >= 300 && response.statusCode < 400) {
					if (response.headers["location"])
						resolve(
							this.download(
								path,
								override,
								simulate,
								callback,
								response.headers["location"],
								tries - 1
							)
						);
					return;
				} else if (response.statusCode !== 200) {
					reject("File couldn't be downloaded.");
				}
				if (simulate) {
					resolve(path);
					return;
				} else {
					response.pipe(fs.createWriteStream(path));
					response.on("end", () => {
						resolve(path);
					});
				}
			});
		});

		if (callback && typeof callback === "function")
			promise.then(callback.bind(null, null), callback);

		return promise;
	}

	/**
	 * @private
	 * @param {curseforge.getMod} method
	 * @param {function} callback
	 */
	__please_dont_hate_me(method, callback, dependencies) {
		let promise = new Promise((resolve, reject) => {
			let mods = [];
			let amount = dependencies.length;
			if(amount <= 0){
				resolve([]);	
			}
			for (let dep of dependencies) {
				method(dep.addonId)
					.then((res) => {
						mods.push(res);
						if (--amount === 0) {
							resolve(mods);
						}
					})
					.catch((err) => reject);
			}
		});
		if (callback && typeof callback === "function")
			promise.then(callback.bind(null, null), callback);

		return promise;
	}

	/**
	 * @method ModFile.getDependencies
	 * @description Get all dependencies required by this mod.
	 * @param {function} callback - Optional callback to use as alternative to Promise
	 * @param {array} [categories=[1,3]] - Array of categories to get the Dependencies for. @see CurseForgeAPI.DEPENDENCY_TYPE
	 * @returns {Promise.<Mod[], Error>} Array of Mods who are marked as dependency or an empty array if no dependencies exist.
	 */
	getDependencies(callback, categories=[1, 3]) {
		if(typeof callback == "array")
			categories = callback;

		let dependenciesToLoad = this.mod_dependencies.filter(mod => {
			return categories.includes(mod.type);
		})
		
		return this.__please_dont_hate_me(curseforge.getMod, callback, dependenciesToLoad);
	}

	/**
	 * @method ModFile.getDependenciesFiles
	 * @description Get all dependencies required by this mod.
	 * @param {function} callback - Optional callback to use as alternative to Promise
	 * @param {array} [categories=[3]] - Array of categories to get the Dependencies for. @see CurseForgeAPI.DEPENDENCY_TYPE
	 * @returns {Promise.<ModFile[], Error>} Array of ModFiles who are marked as dependency or an empty array if no dependencies exist.
	 */
	getDependenciesFiles(callback, categories=[1, 3]) {
		if(typeof callback == "array")
			categories = callback;
		let dependenciesToLoad = this.mod_dependencies.filter(mod => {
			return categories.includes(mod.type);
		})
		return this.__please_dont_hate_me(curseforge.getModFiles, callback, dependenciesToLoad);
	}

	/**
	 * @name ModFile
	 * @class ModFile
	 * @description A File Object representing a file of a specific mod
	 * @param {Object} file_object - File object to create object from
	 * @property {string[]} minecraft_versions - The minecraft versions this mod file is compatible with.
	 * @property {string} file_name - The name of the mod file it got stored with.
	 * @property {string} file_size - The size of the mod file as string. (Yeah it's gross)
	 * @property {string} release_type - the type of the mod file release.
	 * @property {string} mod_key - The Curse slug of the mod the file belongs to.
	 * @property {string} download_url - The url to the mod file to download.
	 * @property {number} downloads - The amount of downloads of this mod file.
	 * @property {timestamp} timestamp - A timestamp of the time the file got uploaded.
	 * @property {string[]} mod_dependencies - A list of dependencies for this file.
	 * @property {boolean} available - true if the file is available.
	 */
	constructor(file_object) {
		this.id = file_object.id;
		this.minecraft_versions = file_object.gameVersion;
		this.file_name = file_object.file_name;
		this.file_size = file_object.fileLength;
		this.timestamp = file_object.fileDate;
		this.release_type = file_object.releaseType;
		this.download_url = file_object.downloadUrl;
		this.downloads = file_object.download_count;
		this.mod_dependencies = file_object.dependencies || [];
		this.alternate = file_object.isAlternate;
		this.alternate_id = file_object.alternateFileId;
		this.available = file_object.isAvailable;
	}
};