/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */
package org.apache.nifi.documentation.html;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.apache.nifi.annotation.behavior.DynamicRelationship;
import org.apache.nifi.annotation.behavior.ReadsAttribute;
import org.apache.nifi.annotation.behavior.ReadsAttributes;
import org.apache.nifi.annotation.behavior.WritesAttribute;
import org.apache.nifi.annotation.behavior.WritesAttributes;
import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.processor.Processor;
import org.apache.nifi.processor.Relationship;

/**
 * Writes documentation specific for a Processor. This includes everything for a
 * ConfigurableComponent as well as Relationship information.
 *
 */
public class HtmlProcessorDocumentationWriter extends HtmlDocumentationWriter {

    public HtmlProcessorDocumentationWriter(ExtensionManager extensionManager) {
        super(extensionManager);
    }

    @Override
    protected void writeAdditionalBodyInfo(final ConfigurableComponent configurableComponent,
            final XMLStreamWriter xmlStreamWriter) throws XMLStreamException {
        final Processor processor = (Processor) configurableComponent;
        writeRelationships(processor, xmlStreamWriter);
        writeDynamicRelationships(processor, xmlStreamWriter);
        writeAttributeInfo(processor, xmlStreamWriter);
    }

    /**
     * Writes all the attributes that a processor says it reads and writes
     *
     * @param processor the processor to describe
     * @param xmlStreamWriter the xml stream writer to use
     * @throws XMLStreamException thrown if there was a problem writing the XML
     */
    private void writeAttributeInfo(Processor processor, XMLStreamWriter xmlStreamWriter)
            throws XMLStreamException {

        handleReadsAttributes(xmlStreamWriter, processor);
        handleWritesAttributes(xmlStreamWriter, processor);
    }

    private String defaultIfBlank(final String test, final String defaultValue) {
        if (test == null || test.trim().isEmpty()) {
            return defaultValue;
        }
        return test;
    }

    /**
     * Writes out just the attributes that are being read in a table form.
     *
     * @param xmlStreamWriter the xml stream writer to use
     * @param processor the processor to describe
     * @throws XMLStreamException xse
     */
    private void handleReadsAttributes(XMLStreamWriter xmlStreamWriter, final Processor processor)
            throws XMLStreamException {
        List<ReadsAttribute> attributesRead = getReadsAttributes(processor);

        writeSimpleElement(xmlStreamWriter, H3, "Reads Attributes: ");
        if (!attributesRead.isEmpty()) {
            xmlStreamWriter.writeStartElement(TABLE);
            xmlStreamWriter.writeAttribute(ID, "reads-attributes");
            xmlStreamWriter.writeStartElement(TR);
            writeSimpleElement(xmlStreamWriter, TH, "Name");
            writeSimpleElement(xmlStreamWriter, TH, "Description");
            xmlStreamWriter.writeEndElement();
            for (ReadsAttribute attribute : attributesRead) {
                xmlStreamWriter.writeStartElement(TR);
                writeSimpleElement(xmlStreamWriter, TD,
                        defaultIfBlank(attribute.attribute(), "Not Specified"));
                // TODO allow for HTML characters here.
                writeSimpleElement(xmlStreamWriter, TD,
                        defaultIfBlank(attribute.description(), "Not Specified"));
                xmlStreamWriter.writeEndElement();

            }
            xmlStreamWriter.writeEndElement();

        } else {
            xmlStreamWriter.writeCharacters("None specified.");
        }
    }

    /**
     * Writes out just the attributes that are being written to in a table form.
     *
     * @param xmlStreamWriter the xml stream writer to use
     * @param processor the processor to describe
     * @throws XMLStreamException xse
     */
    private void handleWritesAttributes(XMLStreamWriter xmlStreamWriter, final Processor processor)
            throws XMLStreamException {
        List<WritesAttribute> attributesRead = getWritesAttributes(processor);

        writeSimpleElement(xmlStreamWriter, H3, "Writes Attributes: ");
        if (!attributesRead.isEmpty()) {
            xmlStreamWriter.writeStartElement(TABLE);
            xmlStreamWriter.writeAttribute(ID, "writes-attributes");
            xmlStreamWriter.writeStartElement(TR);
            writeSimpleElement(xmlStreamWriter, TH, "Name");
            writeSimpleElement(xmlStreamWriter, TH, "Description");
            xmlStreamWriter.writeEndElement();
            for (WritesAttribute attribute : attributesRead) {
                xmlStreamWriter.writeStartElement(TR);
                writeSimpleElement(xmlStreamWriter, TD,
                        defaultIfBlank(attribute.attribute(), "Not Specified"));
                // TODO allow for HTML characters here.
                writeSimpleElement(xmlStreamWriter, TD,
                        defaultIfBlank(attribute.description(), "Not Specified"));
                xmlStreamWriter.writeEndElement();
            }
            xmlStreamWriter.writeEndElement();

        } else {
            xmlStreamWriter.writeCharacters("None specified.");
        }
    }

    /**
     * Collects the attributes that a processor is reading from.
     *
     * @param processor the processor to describe
     * @return the list of attributes that processor is reading
     */
    private List<ReadsAttribute> getReadsAttributes(Processor processor) {
        List<ReadsAttribute> attributes = new ArrayList<>();

        ReadsAttributes readsAttributes = processor.getClass().getAnnotation(ReadsAttributes.class);
        if (readsAttributes != null) {
            attributes.addAll(Arrays.asList(readsAttributes.value()));
        }

        ReadsAttribute readsAttribute = processor.getClass().getAnnotation(ReadsAttribute.class);
        if (readsAttribute != null) {
            attributes.add(readsAttribute);
        }

        return attributes;
    }

    /**
     * Collects the attributes that a processor is writing to.
     *
     * @param processor the processor to describe
     * @return the list of attributes the processor is writing
     */
    private List<WritesAttribute> getWritesAttributes(Processor processor) {
        List<WritesAttribute> attributes = new ArrayList<>();

        WritesAttributes writesAttributes = processor.getClass().getAnnotation(WritesAttributes.class);
        if (writesAttributes != null) {
            attributes.addAll(Arrays.asList(writesAttributes.value()));
        }

        WritesAttribute writeAttribute = processor.getClass().getAnnotation(WritesAttribute.class);
        if (writeAttribute != null) {
            attributes.add(writeAttribute);
        }

        return attributes;
    }

    /**
     * Writes a table describing the relations a processor has.
     *
     * @param processor the processor to describe
     * @param xmlStreamWriter the stream writer to use
     * @throws XMLStreamException thrown if there was a problem writing the xml
     */
    private void writeRelationships(final Processor processor, final XMLStreamWriter xmlStreamWriter)
            throws XMLStreamException {

        writeSimpleElement(xmlStreamWriter, H3, "Relationships: ");

        if (!processor.getRelationships().isEmpty()) {
            xmlStreamWriter.writeStartElement(TABLE);
            xmlStreamWriter.writeAttribute(ID, "relationships");
            xmlStreamWriter.writeStartElement(TR);
            writeSimpleElement(xmlStreamWriter, TH, "Name");
            writeSimpleElement(xmlStreamWriter, TH, "Description");
            xmlStreamWriter.writeEndElement();

            for (Relationship relationship : processor.getRelationships()) {
                xmlStreamWriter.writeStartElement(TR);
                writeSimpleElement(xmlStreamWriter, TD, relationship.getName());
                writeSimpleElement(xmlStreamWriter, TD, relationship.getDescription());
                xmlStreamWriter.writeEndElement();
            }
            xmlStreamWriter.writeEndElement();
        } else {
            xmlStreamWriter.writeCharacters("This processor has no relationships.");
        }
    }

    private void writeDynamicRelationships(final Processor processor, final XMLStreamWriter xmlStreamWriter) throws XMLStreamException {

        List<DynamicRelationship> dynamicRelationships = getDynamicRelationships(processor);

        if (!dynamicRelationships.isEmpty()) {
            writeSimpleElement(xmlStreamWriter, H3, "Dynamic Relationships: ");
            xmlStreamWriter.writeStartElement(P);
            xmlStreamWriter.writeCharacters("A Dynamic Relationship may be created based on how the user configures the Processor.");
            xmlStreamWriter.writeStartElement(TABLE);
            xmlStreamWriter.writeAttribute(ID, "dynamic-relationships");
            xmlStreamWriter.writeStartElement(TR);
            writeSimpleElement(xmlStreamWriter, TH, "Name");
            writeSimpleElement(xmlStreamWriter, TH, "Description");
            xmlStreamWriter.writeEndElement();

            for (DynamicRelationship dynamicRelationship : dynamicRelationships) {
                xmlStreamWriter.writeStartElement(TR);
                writeSimpleElement(xmlStreamWriter, TD, dynamicRelationship.name());
                writeSimpleElement(xmlStreamWriter, TD, dynamicRelationship.description());
                xmlStreamWriter.writeEndElement();
            }
            xmlStreamWriter.writeEndElement();
            xmlStreamWriter.writeEndElement();
        }
    }

    private List<DynamicRelationship> getDynamicRelationships(Processor processor) {
        List<DynamicRelationship> results = new ArrayList<>();

        DynamicRelationship dynamicRelationships = processor.getClass().getAnnotation(DynamicRelationship.class);
        if (dynamicRelationships != null) {
            results.add(dynamicRelationships);
        }

        return results;
    }
}
