Source: processSchema.js

const ProcessUtils = require('./processUtils');
const ProcessDataType = require('./processDataType');
const Utils = require('./utils');

/**
 * Wrapper class for the process schemas (i.e. from parameters or return value).
 * 
 * @class
 */
class ProcessSchema {
	
	/**
	 * Constructs a new process schema based on the openEO API representation.
	 * 
	 * Can be array or JSON Schema object. The array consists of multiple JSON Schemas then.
	 * 
	 * @param {?object|array} [schema=null]
	 * @param {*} [defaultValue=undefined]
	 */
	constructor(schema = null, defaultValue = undefined) {
		if (!Utils.isObject(schema) && !Array.isArray(schema)) {
			this.unspecified = true;
			this.schemas = [];
		}
		else {
			this.unspecified = false;
			this.schemas = ProcessUtils.normalizeJsonSchema(schema, true).map(s => new ProcessDataType(s, this, defaultValue));

			// Find and assign the default value from sub-schemas if no defaultValue was given
			if (typeof defaultValue === 'undefined') {
				let defaults = this.schemas
					.map(s => s.default())
					.filter(d => typeof d !== 'undefined');
				this.default = defaults[0];
			}
			else {
				this.default = defaultValue;
			}
		}

		this.refs = [];
	}

	/**
	 * Converts the schemas to a JSON-serializable representation.
	 * 
	 * @returns {object}
	 */
	toJSON() {
		return this.schemas.map(s => s.toJSON());
	}

	/**
	 * Returns whether the schema is editable.
	 * 
	 * This means it returns `true`, unless certain data types are detected that
	 * can't be transmitted via JSON in the openEO API (e.g. data cubes or labeled arrays).
	 * 
	 * @returns {boolean}
	 */
	isEditable() {
		return (this.unspecified || this.schemas.filter(s => s.isEditable() && !s.isNull()).length > 0);
	}

	/**
	 * Checks whether the schema is exactly and only of the given data type.
	 * 
	 * Can be a native type or a openEO "subtype".
	 * 
	 * @param {string} type 
	 * @returns {boolean}
	 */
	is(type) {
		var types = this.dataTypes();
		return (types.length === 1 && types[0] === type);
	}

	/**
	 * Returns the native data type of the schema.
	 * 
	 * One of: array, object, null, string, boolean, number
	 * 
	 * @returns {string}
	 */
	nativeDataType() {
		return this.dataType(true);
	}

	/**
	 * Returns the data type of the associated schemas.
	 * 
	 * Setting `native` to `true` will only consider native JSON data types and "any".
	 * Otherwise, subtypes will also be considered.
	 * 
	 * If the schema has a two data types and one of them is `null`, 
	 * `null` is ignored and just the other data type is returned.
	 * 
	 * `nullable()` can be used to check whether a schema allows `null`.
	 * 
	 * Returns `mixed` if multiple data types are allowed.
	 * 
	 * @param {boolean} [native=false]
	 * @returns {string}
	 * @see ProcessSchema#nullable
	 */
	dataType(native = false) {
		var types = this.dataTypes(true, native);
		var nullIndex = types.indexOf('null');
		if (types.length === 1) {
			return types[0];
		}
		else if (types.length === 2 && nullIndex !== -1) {
			return types[nullIndex === 0 ? 1 : 0];
		}
		else {
			return 'mixed';
		}
	}

	/**
	 * Returns a set of all supported distinct data types (or 'any').
	 * 
	 * By default, `null` is not included in the list of data types.
	 * Setting `includeNull` to `true` to include `null` in the list.
	 * 
	 * Setting `native` to `true` will only consider native JSON data types and "any".
	 * Otherwise, subtypes will also be considered.
	 * 
	 * @param {boolean} [includeNull=false]
	 * @param {boolean} [native=false]
	 * @returns {array<string>}
	 */
	dataTypes(includeNull = false, native = false) {
		var types = this.schemas
			.map(s => s.dataType(native))
			.filter((v, i, a) => a.indexOf(v) === i); // Return each type only once
		if (types.length === 0 || types.includes('any')) {
			return ['any'];
		}
		return includeNull ? types : types.filter(s => s !== 'null');
	}

	/**
	 * Checks whether one of the schemas allows the value to be `null`.
	 * 
	 * @returns {boolean}
	 */
	nullable() {
		return (this.unspecified || this.schemas.filter(s => s.nullable()).length > 0);
	}

}

module.exports = ProcessSchema;