/*
 * Copyright 2017, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

function transform({obj, kind, name, children, timestamp, rect, bounds, highlight, rects_transform, chips, visible, flattened, stableId}) {
	function call(fn, arg) {
		return (typeof fn == 'function') ? fn(arg) : fn;
	}
	function handle_children(arg, transform) {
		return [].concat(...arg.map((item) => {
			var childrenFunc = item[0];
			var transformFunc = item[1];
			var childs = call(childrenFunc, obj);
			if (childs) {
				if (typeof childs.map != 'function'){
					throw 'Childs should be an array, but is: ' + (typeof childs) + '.'
				}
				return transform ? childs.map(transformFunc) : childs;
			} else {
				return [];
			}
		}));
	}
	function concat(arg, args, argsmap) {
		var validArg = arg !== undefined && arg !== null;

		if (Array.isArray(args)) {
			if (validArg) {
				return [arg].concat(...args.map(argsmap));
			} else {
				return [].concat(...args.map(argsmap));
			}
		} else if (validArg) {
			return [arg];
		} else {
			return undefined;
		}
	}

	var transformed_children = handle_children(children, true /* transform */);
	rects_transform = (rects_transform === undefined) ? (e) => e : rects_transform;

	var kindResolved = call(kind, obj);
	var nameResolved = call(name, obj);
	var rectResolved = call(rect, obj);
	var stableIdResolved = (stableId === undefined) ?
			kindResolved + '|-|' + nameResolved :
			call(stableId, obj);

	var result = {
		kind: kindResolved,
		name: nameResolved,
		children: transformed_children,
		obj: obj,
		timestamp: call(timestamp, obj),
		skip: handle_children(children, false /* transform */),
		bounds: call(bounds, obj) || transformed_children.map((e) => e.bounds).find((e) => true) || undefined,
		rect: rectResolved,
		rects: rects_transform(concat(rectResolved, transformed_children, (e) => e.rects)),
		highlight: call(highlight, obj),
		chips: call(chips, obj),
		stableId: stableIdResolved,
		visible: call(visible, obj),
		childrenVisible: transformed_children.some((c) => {
				return c.childrenVisible || c.visible
			}),
		flattened: call(flattened, obj),
	};

	if (rectResolved) {
		rectResolved.ref = result;
	}

	return Object.freeze(result);
}


function transform_json(obj, name, options) {
	let {skip, formatter} = options;

	var children = [];
	var formatted = undefined;

	if (skip && skip.includes(obj)) {
		// skip
	} else if ((formatted = formatter(obj))) {
		children.push(transform_json(null, formatted, options));
	} else if (Array.isArray(obj)) {
		obj.forEach((e, i) => {
			children.push(transform_json(e, ""+i, options));
		})
	} else if (typeof obj == 'string') {
		children.push(transform_json(null, obj, options));
	} else if (typeof obj == 'number' || typeof obj == 'boolean') {
		children.push(transform_json(null, ""+obj, options));
	} else if (obj && typeof obj == 'object') {
		Object.keys(obj).forEach((key) => {
			children.push(transform_json(obj[key], key, options));
		});
	}

	if (children.length == 1 && !children[0].combined) {
		return Object.freeze({
			kind: "",
			name: name + ": " + children[0].name,
			children: children[0].children,
			combined: true
		});
	}

	return Object.freeze({
		kind: "",
		name: name,
		children: children,
	});
}

function nanos_to_string(elapsedRealtimeNanos) {
	var units = [
		[1000000, '(ns)'],
		[1000, 'ms'],
		[60, 's'],
		[60, 'm'],
		[24, 'h'],
		[Infinity, 'd'],
	];

	var parts = []
	units.some(([div, str], i) => {
		var part = (elapsedRealtimeNanos % div).toFixed()
		if (!str.startsWith('(')) {
			parts.push(part + str);
		}
		elapsedRealtimeNanos = Math.floor(elapsedRealtimeNanos / div);
		return elapsedRealtimeNanos == 0;
	});

	return parts.reverse().join('');
}

 // Returns a UI element used highlight a visible entry.
 function get_visible_chip() {
	return {short: 'V', long: "visible", class: 'default'};
 }

export {transform, transform_json, nanos_to_string, get_visible_chip};
