/*
 * Copyright 2021-2022 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.events.api;

import org.opentest4j.reporting.schema.Namespace;
import org.opentest4j.reporting.schema.QualifiedName;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;

import java.util.function.Consumer;

/**
 * Base class for elements.
 *
 * @param <T> element type
 */
public class Element<T extends Element<T>> implements Appendable<T> {

	private final Context context;
	private final Namespace namespace;
	final org.w3c.dom.Element domElement;

	/**
	 * Create an instance with the supplied {@linkplain Context context} and {@linkplain QualifiedName qualified name}.
	 *
	 * @param context       the context of this instance
	 * @param qualifiedName the qualified name of this instance
	 */
	public Element(Context context, QualifiedName qualifiedName) {
		this.context = context;
		this.namespace = qualifiedName.getNamespace();
		this.domElement = context.createElement(qualifiedName);
	}

	org.w3c.dom.Element getDomElement() {
		return domElement;
	}

	/**
	 * Add text content to this element.
	 *
	 * @param value the text content
	 * @return this element
	 */
	public Element<T> withContent(String value) {
		domElement.setTextContent(value);
		return this;
	}

	/**
	 * Add a CDATA section to this element.
	 *
	 * @param value the content of the CDATA section
	 * @return this element
	 */
	@SuppressWarnings("UnusedReturnValue")
	public Element<T> withCDataSection(String value) {
		domElement.appendChild(document().createCDATASection(value));
		return this;
	}

	/**
	 * Add an attribute to this element.
	 *
	 * @param qualifiedName the qualified name of the attribute
	 * @param value         the attribute value
	 * @return this element
	 */
	public Element<T> withAttribute(QualifiedName qualifiedName, String value) {
		Attr attr = createAttr(qualifiedName);
		attr.setValue(value);
		domElement.setAttributeNode(attr);
		return this;
	}

	private Attr createAttr(QualifiedName qualifiedName) {
		return namespace.equals(qualifiedName.getNamespace()) //
				? document().createAttribute(qualifiedName.getSimpleName()) //
				: document().createAttributeNS(qualifiedName.getNamespace().getUri(), context.prefixed(qualifiedName));
	}

	private Document document() {
		return context.getDocument();
	}

	@Override
	public <C extends ChildElement<T, ? super C>> Appendable<T> append(Factory<C> creator,
			Consumer<? super C> configurer) {
		C child = creator.createAndConfigure(context, configurer);
		domElement.appendChild(child.domElement);
		return this;
	}

	@Override
	public String toString() {
		return "Element{domElement=" + domElement + '}';
	}

}
