/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of the Liferay Enterprise
 * Subscription License ("License"). You may not use this file except in
 * compliance with the License. You can obtain a copy of the License by
 * contacting Liferay, Inc. See the License for the specific language governing
 * permissions and limitations under the License, including but not limited to
 * distribution rights of the Software.
 *
 *
 *
 */

package com.liferay.frontend.js.loader.modules.extender.internal;

import aQute.bnd.osgi.Constants;

import com.liferay.petra.string.StringBundler;
import com.liferay.petra.string.StringPool;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.util.ArrayUtil;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.PredicateFilter;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.Validator;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;

import java.net.URL;

import java.util.Dictionary;
import java.util.List;
import java.util.Map;

import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;

import org.osgi.framework.Bundle;
import org.osgi.framework.Version;
import org.osgi.framework.wiring.BundleCapability;
import org.osgi.framework.wiring.BundleWire;
import org.osgi.framework.wiring.BundleWiring;

/**
 * @author Carlos Sierra Andrés
 */
public class JSLoaderModule {

	public JSLoaderModule(
		boolean applyVersioning, Bundle bundle, String contextPath) {

		_applyVersioning = applyVersioning;
		_bundle = bundle;
		_contextPath = contextPath;

		Version version = _bundle.getVersion();

		String jsVersion = version.toString();

		int pos = jsVersion.indexOf(".hotfix");

		if (pos > -1) {
			jsVersion = jsVersion.substring(0, pos);
		}

		_version = jsVersion;

		BundleWiring bundleWiring = _bundle.adapt(BundleWiring.class);

		List<BundleCapability> bundleCapabilities =
			bundleWiring.getCapabilities(Details.OSGI_WEBRESOURCE);

		if (bundleCapabilities.isEmpty()) {
			_name = _bundle.getSymbolicName();

			return;
		}

		BundleCapability bundleCapability = bundleCapabilities.get(0);

		Map<String, Object> attributes = bundleCapability.getAttributes();

		_name = (String)attributes.get(Details.OSGI_WEBRESOURCE);

		URL url = _bundle.getEntry(Details.CONFIG_JSON);

		urlToConfiguration(url, bundleWiring);
	}

	public String getContextPath() {
		return _contextPath;
	}

	public String getName() {
		return _name;
	}

	public String getUnversionedConfiguration() {
		return _unversionedConfiguration;
	}

	public String getUnversionedMapsConfiguration() {
		return _unversionedMapsConfiguration;
	}

	public String getVersion() {
		return _version;
	}

	public String getVersionedConfiguration() {
		return _versionedConfiguration;
	}

	protected String generateConfiguration(
		JSONObject jsonObject, BundleWiring bundleWiring,
		boolean versionedModuleName) {

		if (!_applyVersioning) {
			if (versionedModuleName) {
				return "";
			}

			return jsonObject.toString();
		}

		JSONArray namesJSONArray = jsonObject.names();

		if (namesJSONArray == null) {
			return jsonObject.toString();
		}

		List<BundleWire> bundleWires = bundleWiring.getRequiredWires(
			Details.OSGI_WEBRESOURCE);

		for (int i = 0; i < namesJSONArray.length(); i++) {
			String name = (String)namesJSONArray.get(i);

			int x = name.indexOf('/');

			if (x == -1) {
				continue;
			}

			String moduleName = name.substring(0, x);

			if (!moduleName.equals(getName())) {
				continue;
			}

			String modulePath = name.substring(x);

			moduleName = StringBundler.concat(
				getName(), "@", getVersion(), modulePath);

			JSONObject nameJSONObject = jsonObject.getJSONObject(name);

			JSONArray dependenciesJSONArray = nameJSONObject.getJSONArray(
				"dependencies");

			for (int j = 0; j < dependenciesJSONArray.length(); j++) {
				String dependency = dependenciesJSONArray.getString(j);

				int y = dependency.indexOf('/');

				if (y == -1) {
					continue;
				}

				String dependencyName = dependency.substring(0, y);
				String dependencyPath = dependency.substring(y);

				if (dependencyName.equals(getName())) {
					dependencyName = StringBundler.concat(
						getName(), "@", getVersion(), dependencyPath);

					dependenciesJSONArray.put(j, dependencyName);
				}
				else {
					normalizeDependencies(
						dependencyName, dependencyPath, dependenciesJSONArray,
						j, bundleWires);
				}
			}

			if (versionedModuleName) {
				jsonObject.remove(name);

				jsonObject.put(moduleName, nameJSONObject);
			}
			else {
				jsonObject.put(name, nameJSONObject);
			}
		}

		return jsonObject.toString();
	}

	protected String generateMapsConfiguration(
		String configuration, String[] jsSubmodulesExport) {

		boolean exportAll = ArrayUtil.contains(
			jsSubmodulesExport, StringPool.STAR);

		JSONObject mapsConfigurationJSONObject = new JSONObject();

		JSONObject configurationJSONObject = new JSONObject(
			"{" + configuration + "}");

		JSONArray namesJSONArray = configurationJSONObject.names();

		for (int i = 0; i < namesJSONArray.length(); i++) {
			String name = (String)namesJSONArray.get(i);

			int x = name.indexOf('/');

			String moduleRootPath = name.substring(0, x + 1);

			String submodulePath = name.substring(x + 1);

			int y = submodulePath.indexOf('/');

			if (y == -1) {
				continue;
			}

			final String submoduleName = submodulePath.substring(0, y);

			PredicateFilter<String> wildcardPredicateFilter =
				new PredicateFilter<String>() {

					public boolean filter(String item) {
						return _matchesWildcard(submoduleName, item);
					}

				};

			if (exportAll ||
				ArrayUtil.exists(jsSubmodulesExport, wildcardPredicateFilter)) {

				mapsConfigurationJSONObject.put(
					submoduleName, moduleRootPath.concat(submoduleName));
			}
		}

		return mapsConfigurationJSONObject.toString();
	}

	protected String normalize(String jsonString) {
		if (jsonString.startsWith("{") && jsonString.endsWith("}")) {
			jsonString = jsonString.substring(1, jsonString.length() - 1);
		}

		return jsonString;
	}

	protected void normalizeDependencies(
		String dependencyName, String dependencyPath, JSONArray jsonArray,
		int index, List<BundleWire> bundleWires) {

		for (BundleWire bundleWire : bundleWires) {
			BundleCapability bundleCapability = bundleWire.getCapability();

			Map<String, Object> attributes = bundleCapability.getAttributes();

			String attributesDependencyName = (String)attributes.get(
				Details.OSGI_WEBRESOURCE);

			if (!attributesDependencyName.equals(dependencyName)) {
				continue;
			}

			Version version = (Version)attributes.get(
				Constants.VERSION_ATTRIBUTE);

			dependencyName = StringBundler.concat(
				dependencyName, "@", version.toString(), dependencyPath);

			jsonArray.put(index, dependencyName);

			return;
		}
	}

	protected void urlToConfiguration(URL url, BundleWiring bundleWiring) {
		if (url == null) {
			return;
		}

		try (Reader reader = new InputStreamReader(url.openStream())) {
			JSONTokener jsonTokener = new JSONTokener(reader);

			JSONObject jsonObject = new JSONObject(jsonTokener);

			_unversionedConfiguration = normalize(
				generateConfiguration(jsonObject, bundleWiring, false));
			_versionedConfiguration = normalize(
				generateConfiguration(jsonObject, bundleWiring, true));

			Dictionary<String, String> headers = _bundle.getHeaders(
				StringPool.BLANK);

			String jsSubmodulesExport = GetterUtil.getString(
				headers.get("Liferay-JS-Submodules-Export"));

			if (Validator.isNotNull(jsSubmodulesExport)) {
				_unversionedMapsConfiguration = normalize(
					generateMapsConfiguration(
						_unversionedConfiguration,
						StringUtil.split(jsSubmodulesExport)));

				if (_log.isWarnEnabled()) {
					_log.warn(
						"Liferay-JS-Submodules-Export is deprecated and " +
							"replaced with Liferay-JS-Submodules-Bridge");
				}
			}
		}
		catch (IOException ioe) {
			throw new RuntimeException(ioe);
		}
	}

	private boolean _matchesWildcard(String text, String pattern) {
		pattern = StringUtil.replace(
			pattern, new String[] {"?", "*"}, new String[] {".?", ".*"});

		return text.matches(pattern);
	}

	private static final Log _log = LogFactoryUtil.getLog(JSLoaderModule.class);

	private final boolean _applyVersioning;
	private final Bundle _bundle;
	private final String _contextPath;
	private final String _name;
	private String _unversionedConfiguration = "";
	private String _unversionedMapsConfiguration = "";
	private final String _version;
	private String _versionedConfiguration = "";

}