/*
 * Copyright 2021-2024 the original author or authors.
 *
 * 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
 *
 *      https://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.
 */

package org.opentest4j.reporting.tooling.core.htmlreport;

import org.apiguardian.api.API;
import org.joox.Match;
import org.opentest4j.reporting.events.java.FileEncoding;
import org.opentest4j.reporting.events.java.JavaVersion;
import org.opentest4j.reporting.tooling.spi.htmlreport.Contributor;
import org.opentest4j.reporting.tooling.spi.htmlreport.KeyValuePairs;
import org.opentest4j.reporting.tooling.spi.htmlreport.PreFormattedOutput;
import org.opentest4j.reporting.tooling.spi.htmlreport.Section;
import org.opentest4j.reporting.tooling.spi.htmlreport.Subsections;
import org.w3c.dom.Element;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

import static org.apiguardian.api.API.Status.INTERNAL;
import static org.joox.JOOX.$;
import static org.opentest4j.reporting.tooling.core.htmlreport.CoreContributor.addToTable;

/**
 * Contributes sections to the HTML report elements in the java schema.
 *
 * @since 0.2.0
 */
@API(status = INTERNAL, since = "0.2.0")
public class JavaContributor implements Contributor {

	/**
	 * Create a new instance.
	 */
	public JavaContributor() {
	}

	@Override
	public List<Section> contributeSectionsForExecution(Context context) {
		var sections = new ArrayList<Section>();
		var infrastructure = $(context.element()).child("infrastructure");
		createJvmSection(infrastructure).ifPresent(sections::add);
		return sections;
	}

	@Override
	public List<Section> contributeSectionsForTestNode(Context context) {
		var sections = new ArrayList<Section>();
		createResultSection(context.element()).ifPresent(sections::add);
		return sections;
	}

	private static Optional<Section> createJvmSection(Match infrastructure) {
		var table = new LinkedHashMap<String, String>();
		addToTable(infrastructure, JavaVersion.ELEMENT, "Java version", table::put);
		addToTable(infrastructure, FileEncoding.ELEMENT, "File encoding", table::put);
		String maxHeapSize = infrastructure.child("heapSize").attr("max");
		if (maxHeapSize != null) {
			table.put("Max heap size",
				String.format("%s (%s bytes)", toHumanReadableFormat(Long.parseLong(maxHeapSize)), maxHeapSize));
		}

		if (table.isEmpty()) {
			return Optional.empty();
		}

		var keyValuePairs = KeyValuePairs.builder().content(table).build();
		return Optional.of(Section.builder().title("Java Virtual Machine").order(10).addBlock(keyValuePairs).build());
	}

	private static Optional<Section> createResultSection(Element element) {
		var content = $(element).child("result").children("throwable").map(throwable -> {
			var type = throwable.match().getAttribute("type");
			var stackTrace = throwable.match().getTextContent();
			return Section.builder().title(type).addBlock(
				PreFormattedOutput.builder().content(stackTrace).build()).build();
		});

		if (content.isEmpty()) {
			return Optional.empty();
		}
		var subsections = Subsections.builder().content(content).build();
		return Optional.of(Section.builder().title("Result").order(25).addBlock(subsections).build());
	}

	// Adapted from https://www.baeldung.com/java-human-readable-byte-size#using-the-longnumberofleadingzeros-method
	private static String toHumanReadableFormat(long size) {
		if (size < 1024)
			return size + " B";
		int unitIdx = (63 - Long.numberOfLeadingZeros(size)) / 10;
		return formatSize(size, 1L << (unitIdx * 10), " KMGTPE".charAt(unitIdx) + "iB");
	}

	private static String formatSize(long size, long divider, String unitName) {
		return new DecimalFormat("#.##", new DecimalFormatSymbols(Locale.US)).format((double) size / divider) + " "
				+ unitName;
	}
}
