/*
	MIT License http://www.opensource.org/licenses/mit-license.php
*/

"use strict";

const path = require("path");

const WINDOWS_ABS_PATH_REGEXP = /^[a-zA-Z]:[\\/]/;
const SEGEMENTS_SPLIT_REGEXP = /([|!])/;
const WINDOWS_PATH_SEPARATOR_REGEXP = /\\/g;

/**
 * @typedef {Object} MakeRelativePathsCache
 * @property {Map<string, Map<string, string>>=} relativePaths
 */

/**
 * @param {string} context context for relative path
 * @param {string} maybeAbsolutePath path to make relative
 * @returns {string} relative path in request style
 */
const absoluteToRequest = (context, maybeAbsolutePath) => {
	if (maybeAbsolutePath[0] === "/") {
		if (
			maybeAbsolutePath.length > 1 &&
			maybeAbsolutePath[maybeAbsolutePath.length - 1] === "/"
		) {
			// this 'path' is actually a regexp generated by dynamic requires.
			// Don't treat it as an absolute path.
			return maybeAbsolutePath;
		}

		const querySplitPos = maybeAbsolutePath.indexOf("?");
		let resource =
			querySplitPos === -1
				? maybeAbsolutePath
				: maybeAbsolutePath.slice(0, querySplitPos);
		resource = path.posix.relative(context, resource);
		if (!resource.startsWith("../")) {
			resource = "./" + resource;
		}
		return querySplitPos === -1
			? resource
			: resource + maybeAbsolutePath.slice(querySplitPos);
	}

	if (WINDOWS_ABS_PATH_REGEXP.test(maybeAbsolutePath)) {
		const querySplitPos = maybeAbsolutePath.indexOf("?");
		let resource =
			querySplitPos === -1
				? maybeAbsolutePath
				: maybeAbsolutePath.slice(0, querySplitPos);
		resource = path.win32.relative(context, resource);
		if (!WINDOWS_ABS_PATH_REGEXP.test(resource)) {
			resource = resource.replace(WINDOWS_PATH_SEPARATOR_REGEXP, "/");
			if (!resource.startsWith("../")) {
				resource = "./" + resource;
			}
		}
		return querySplitPos === -1
			? resource
			: resource + maybeAbsolutePath.slice(querySplitPos);
	}

	// not an absolute path
	return maybeAbsolutePath;
};

/**
 * @param {string} context context for relative path
 * @param {string} relativePath path
 * @returns {string} absolute path
 */
const requestToAbsolute = (context, relativePath) => {
	if (relativePath.startsWith("./") || relativePath.startsWith("../"))
		return path.join(context, relativePath);
	return relativePath;
};

const makeCacheable = fn => {
	/** @type {WeakMap<Object, Map<string, Map<string, string>>>} */
	const cache = new WeakMap();

	/**
	 * @param {string} context context used to create relative path
	 * @param {string} identifier identifier used to create relative path
	 * @param {Object=} associatedObjectForCache an object to which the cache will be attached
	 * @returns {string} the returned relative path
	 */
	const cachedFn = (context, identifier, associatedObjectForCache) => {
		if (!associatedObjectForCache) return fn(context, identifier);

		let innerCache = cache.get(associatedObjectForCache);
		if (innerCache === undefined) {
			innerCache = new Map();
			cache.set(associatedObjectForCache, innerCache);
		}

		let cachedResult;
		let innerSubCache = innerCache.get(context);
		if (innerSubCache === undefined) {
			innerCache.set(context, (innerSubCache = new Map()));
		} else {
			cachedResult = innerSubCache.get(identifier);
		}

		if (cachedResult !== undefined) {
			return cachedResult;
		} else {
			const result = fn(context, identifier);
			innerSubCache.set(identifier, result);
			return result;
		}
	};

	return cachedFn;
};

/**
 *
 * @param {string} context context for relative path
 * @param {string} identifier identifier for path
 * @returns {string} a converted relative path
 */
const _makePathsRelative = (context, identifier) => {
	return identifier
		.split(SEGEMENTS_SPLIT_REGEXP)
		.map(str => absoluteToRequest(context, str))
		.join("");
};

exports.makePathsRelative = makeCacheable(_makePathsRelative);

/**
 * @param {string} context absolute context path
 * @param {string} request any request string may containing absolute paths, query string, etc.
 * @returns {string} a new request string avoiding absolute paths when possible
 */
const _contextify = (context, request) => {
	return request
		.split("!")
		.map(r => absoluteToRequest(context, r))
		.join("!");
};

const contextify = makeCacheable(_contextify);
exports.contextify = contextify;

/**
 * @param {string} context absolute context path
 * @param {string} request any request string
 * @returns {string} a new request string using absolute paths when possible
 */
const _absolutify = (context, request) => {
	return request
		.split("!")
		.map(r => requestToAbsolute(context, r))
		.join("!");
};

const absolutify = makeCacheable(_absolutify);
exports.absolutify = absolutify;
