Source: capabilities.js

const Utils = require('@openeo/js-commons/src/utils');

/**
 * Capabilities of a back-end.
 */
class Capabilities {

	/**
	 * Creates a new Capabilities object from an API-compatible JSON response.
	 * 
	 * @param {object.<string, *>} data - A capabilities response compatible to the API specification for `GET /`.
	 * @throws {Error}
	 */
	constructor(data) {
		if(!Utils.isObject(data)) {
			throw new Error("No capabilities retrieved.");
		}
		if(!data.api_version) {
			throw new Error("Invalid capabilities: No API version retrieved");
		}
		if(!Array.isArray(data.endpoints)) {
			throw new Error("Invalid capabilities: No endpoints retrieved");
		}

		/**
		 * @private
		 * @type {object.<string, *>}
		 */
		this.data = data;

		/**
		 * @private
		 * @type {Array.<string>}
		 */
		this.features = this.data.endpoints
			// Flatten features to be compatible with the feature map.
			.map(e => e.methods.map(method => (method + ' ' + e.path).toLowerCase()))
			.reduce((flat, next) => flat.concat(next), []); // .flat(1) once browser support for ECMAscript 10/2019 gets better

		/**
		 * @private
		 * @ignore
		 * @type {object.<string, string>}
		 */
		this.featureMap = {
			// Discovery
			capabilities: true,
			listFileTypes: 'get /file_formats',
			listServiceTypes: 'get /service_types',
			listUdfRuntimes: 'get /udf_runtimes',
			// Collections
			listCollections: 'get /collections',
			describeCollection: 'get /collections/{collection_id}',
			listCollectionItems: 'get /collections/{collection_id}/items',
			// Processes
			listProcesses: 'get /processes',
			describeProcess: 'get /processes',
			// Auth / Account
			listAuthProviders: true,
			authenticateOIDC: 'get /credentials/oidc',
			authenticateBasic: 'get /credentials/basic',
			describeAccount: 'get /me',
			// Files
			listFiles: 'get /files',
			getFile: 'get /files', // getFile is a virtual function and doesn't request an endpoint, but get /files should be available nevertheless.
			uploadFile: 'put /files/{path}',
			downloadFile: 'get /files/{path}',
			deleteFile: 'delete /files/{path}',
			// User-Defined Processes
			validateProcess: 'post /validation',
			listUserProcesses: 'get /process_graphs',
			describeUserProcess: 'get /process_graphs/{process_graph_id}',
			getUserProcess: 'get /process_graphs/{process_graph_id}',
			setUserProcess: 'put /process_graphs/{process_graph_id}',
			replaceUserProcess: 'put /process_graphs/{process_graph_id}',
			deleteUserProcess: 'delete /process_graphs/{process_graph_id}',
			// Processing
			computeResult: 'post /result',
			listJobs: 'get /jobs',
			createJob: 'post /jobs',
			listServices: 'get /services',
			createService: 'post /services',
			getJob: 'get /jobs/{job_id}',
			describeJob: 'get /jobs/{job_id}',
			updateJob: 'patch /jobs/{job_id}',
			deleteJob: 'delete /jobs/{job_id}',
			estimateJob: 'get /jobs/{job_id}/estimate',
			debugJob: 'get /jobs/{job_id}/logs',
			startJob: 'post /jobs/{job_id}/results',
			stopJob: 'delete /jobs/{job_id}/results',
			listResults: 'get /jobs/{job_id}/results',
			downloadResults: 'get /jobs/{job_id}/results',
			// Web services
			describeService: 'get /services/{service_id}',
			getService: 'get /services/{service_id}',
			updateService: 'patch /services/{service_id}',
			deleteService: 'delete /services/{service_id}',
			debugService: 'get /services/{service_id}/logs',
		};
	}

	/**
	 * Returns the capabilities response as a JSON serializable representation of the data that is API compliant.
	 * 
	 * @returns {object.<string, *>} - A reference to the capabilities response.
	 */
	toJSON() {
		return this.data;
	}

	/**
	 * Returns the openEO API version implemented by the back-end.
	 * 
	 * @returns {string} openEO API version number.
	 */
	apiVersion() {
		return this.data.api_version;
	}

	/**
	 * Returns the back-end version number.
	 * 
	 * @returns {string} openEO back-end version number.
	 */
	backendVersion() {
		return this.data.backend_version;
	}

	/**
	 * Returns the back-end title.
	 * 
	 * @returns {string} Title
	 */
	title() {
		return typeof this.data.title === 'string' ? this.data.title : "";
	}

	/**
	 * Returns the back-end description.
	 * 
	 * @returns {string} Description
	 */
	description() {
		return typeof this.data.description === 'string' ? this.data.description : "";
	}

	/**
	 * Is the back-end suitable for use in production?
	 * 
	 * @returns {boolean} true = stable/production, false = unstable
	 */
	isStable() {
		return this.data.production === true;
	}

	/**
	 * Returns the links.
	 * 
	 * @returns {Array.<Link>} Array of link objects (href, title, rel, type)
	 */
	links() {
		return Array.isArray(this.data.links) ? this.data.links : [];
	}

	/**
	 * Lists all supported features.
	 * 
	 * @returns {Array.<string>} An array of supported features.
	 */
	listFeatures() {
		let features = [];
		for(let feature in this.featureMap) {
			if (this.featureMap[feature] === true || this.features.includes(this.featureMap[feature])) {
				features.push(feature);
			}
		}
		return features.sort();
	}

	/**
	 * Check whether a feature is supported by the back-end.
	 * 
	 * @param {string} methodName - A feature name (corresponds to the JS client method names, see also the feature map for allowed values).
	 * @returns {boolean} `true` if the feature is supported, otherwise `false`.
	 */
	hasFeature(methodName) {
		return this.featureMap[methodName] === true || this.features.some(e => e === this.featureMap[methodName]);
	}

	/**
	 * Get the billing currency.
	 * 
	 * @returns {string | null} The billing currency or `null` if not available.
	 */
	currency() {
		return (Utils.isObject(this.data.billing) && typeof this.data.billing.currency === 'string' ? this.data.billing.currency : null);
	}

	/**
	 * List all billing plans.
	 * 
	 * @returns {Array.<BillingPlan>} Billing plans
	 */
	listPlans() {
		if (Utils.isObject(this.data.billing) && Array.isArray(this.data.billing.plans)) {
			let defaultPlan = typeof this.data.billing.default_plan === 'string' ? this.data.billing.default_plan.toLowerCase() : null;
			return this.data.billing.plans.map(plan => {
				let addition = {
					default: (defaultPlan === plan.name.toLowerCase())
				};
				return Object.assign({}, plan, addition);
			});
		}
		else {
			return [];
		}
	}
}

module.exports = Capabilities;