//-----------------------------------------------------------------------------
// File: HtmlWriter.cpp
//-----------------------------------------------------------------------------
// Project: Kactus 2
// Author: Anton Hagqvist
// Date: 12.4.2023
//
// Description:
// Writes HTML documentation of a component.
//-----------------------------------------------------------------------------

#include "HtmlWriter.h"

#include <IPXACTmodels/Component/Component.h>
#include <IPXACTmodels/Component/MemoryMap.h>
#include <IPXACTmodels/Component/AddressBlock.h>
#include <IPXACTmodels/Component/Register.h>
#include <IPXACTmodels/Component/RegisterFile.h>
#include <IPXACTmodels/Component/Field.h>
#include <IPXACTmodels/Component/BusInterface.h>
#include <IPXACTmodels/Component/FileSet.h>
#include <IPXACTmodels/Component/ComponentInstantiation.h>
#include <IPXACTmodels/Component/EnumeratedValue.h>
#include <IPXACTmodels/DesignConfiguration/DesignConfiguration.h>

#include <KactusAPI/include/ExpressionFormatter.h>
#include <KactusAPI/include/LibraryInterface.h>
#include <KactusAPI/include/ListParameterFinder.h>

#include <QDateTime>
#include <QFileInfo>
#include <QSettings>

namespace HTML
{
    const QString SPACE("&nbsp;");

    const QString INDENT("&nbsp;&nbsp;&nbsp;");

    const QString TABLE("<table frame=\"box\" rules=\"all\" border=\"1\" cellPadding=\"3\" title=\"");

    const QString DOCTYPE("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\" "
        "\"http://www.w3.org/TR/html4/strict.dtd\">");

    const QString ENCODING("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">");
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::HtmlWriter()
//-----------------------------------------------------------------------------
HtmlWriter::HtmlWriter(QSharedPointer<Component> component, ExpressionFormatter* formatter,
    ExpressionFormatterFactory* expressionFormatterFactory,
    LibraryInterface* libraryHandler, int componentNumber) :
    DocumentationWriter(formatter, expressionFormatterFactory),
    expressionFormatter_(formatter),
    libraryHandler_(libraryHandler),
    component_(component),
    componentNumber_(componentNumber)
{
    vlnvString_ = component_->getVlnv().toString();
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::~HtmlWriter()
//-----------------------------------------------------------------------------
HtmlWriter::~HtmlWriter()
{
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeHeader()
//-----------------------------------------------------------------------------
void HtmlWriter::writeHeader(QTextStream& stream)
{
    QSettings settings;
    
    // write the top of the html document
    stream << HTML::DOCTYPE << Qt::endl;
    stream << "<html>" << Qt::endl;
    stream << indent(1) << "<head>" << Qt::endl;
    stream << indent(1) << HTML::ENCODING << Qt::endl;
    stream << indent(2) <<"<title>Kactus2 generated documentation for component " <<
        component_->getVlnv().getName() << " " << component_->getVlnv().getVersion() << "</title>" << Qt::endl;
    stream << indent(1) << "</head>" << Qt::endl;
    stream << indent(1) << "<body>" << Qt::endl;
    stream << indent(2) << "<h6>This document was generated by Kactus2 on " <<
        QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm:ss") << " by user " <<
        settings.value("General/Username").toString() << "</h6>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeComponentHeader()
//-----------------------------------------------------------------------------
void HtmlWriter::writeComponentHeader(QTextStream& stream)
{
    stream << indent(2) << "<h1><a id=\"" << vlnvString_ << "\">" << componentNumber_ << ". Component " <<
        component_->getVlnv().toString(" - ") << "</a></h1>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeComponentInfo()
//-----------------------------------------------------------------------------
void HtmlWriter::writeComponentInfo(QTextStream& stream)
{
    stream << indent(2) << "<p>" << Qt::endl;

    stream << indent(2) << "<img src=\"" << getImagesPath().split("/").back() << "/"
        << component_->getVlnv().toString(".") << ".png\" alt=\""
        << component_->getVlnv().toString(" - ") << " preview picture\"><br>" << Qt::endl;

    // if component has description, write it
    if (!component_->getDescription().isEmpty())
    {
        stream << indent(2) << "<strong>Description:</strong> " << component_->getDescription()
            << "<br>" << Qt::endl;
    }

    // print relative path to the xml file
    QFileInfo compXmlInfo(libraryHandler_->getPath(component_->getVlnv()));
    QString relativeXmlPath = General::getRelativePath(getTargetPath(), compXmlInfo.absoluteFilePath());
    stream << indent(2) << "<strong>IP-Xact file: </strong><a href=\"" <<
        relativeXmlPath << "\">" << compXmlInfo.fileName() <<
        "</a><br>" << Qt::endl;

    stream << indent(2) << "</p>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeKactusAttributes()
//-----------------------------------------------------------------------------
void HtmlWriter::writeKactusAttributes(QTextStream& stream, int subHeaderNumber)
{
    writeSubHeader(stream, subHeaderNumber, "Kactus2 attributes", "attributes");

    stream << indent(2) << "<p>" << Qt::endl;
    stream << indent(3) << "<strong>" << HTML::INDENT << "Product hierarchy: </strong>" <<
        KactusAttribute::hierarchyToString(component_->getHierarchy()) << "<br>" << Qt::endl;

    stream << indent(3) << "<strong>" << HTML::INDENT << "Component implementation: </strong>" <<
        KactusAttribute::implementationToString(component_->getImplementation()) << "<br>" << Qt::endl;

    stream << indent(3) << "<strong>" << HTML::INDENT << "Component firmness: </strong>" <<
        KactusAttribute::firmnessToString(component_->getFirmness()) << "<br>" << Qt::endl;

    stream << indent(2) << "</p>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeTableOfContentsHeader()
//-----------------------------------------------------------------------------
void HtmlWriter::writeTableOfContentsHeader(QTextStream& stream)
{
    stream << indent(2) << "<p>" << Qt::endl;
    stream << indent(2) << "<strong>Table of contents</strong><br>" << Qt::endl;
    stream << indent(2) << "</p>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeTableOfContents()
//-----------------------------------------------------------------------------
void HtmlWriter::writeTableOfContents(QTextStream& stream)
{
    QString vlnvHeader = "\t\t" + HTML::INDENT + "<a href=\"#" + component_->getVlnv().toString();

    stream << indent(2) << "<a href=\"#" << component_->getVlnv().toString() << "\">"
        << componentNumber_ << ". Component" << HTML::SPACE << component_->getVlnv().toString(" - ")
        << "</a><br>" << Qt::endl;

    // subHeader is running number that counts the number of sub headers for component
    int subHeader = 1;

    // component always has kactus parameters
    stream << vlnvHeader << ".kts_params\">" << componentNumber_ << "." << subHeader
        << ". Kactus2 attributes</a><br>" << Qt::endl;
    ++subHeader;

    if (component_->hasParameters())
    {
        stream << vlnvHeader << ".parameters\">" << componentNumber_ << "." << subHeader <<
            ". General parameters</a><br>" << Qt::endl;
        ++subHeader;
    }

    if (!component_->getMemoryMaps()->isEmpty())
    {
        stream << vlnvHeader << ".memoryMaps\">" << componentNumber_ << "." << subHeader
            << ". Memory maps</a><br>" << Qt::endl;
        ++subHeader;
    }

    if (component_->hasPorts())
    {
        stream << vlnvHeader << ".ports\">" << componentNumber_ << "." << subHeader
            << ". Ports</a><br>" << Qt::endl;
        ++subHeader;
    }

    if (component_->hasInterfaces())
    {
        stream << vlnvHeader << ".interfaces\">" << componentNumber_ << "." << subHeader
            << ". Bus interfaces</a><br>" <<
            Qt::endl;
        ++subHeader;
    }

    if (component_->hasFileSets())
    {
        stream << vlnvHeader << ".fileSets\">" << componentNumber_ << "." << subHeader
            << ". File sets</a><br>" << Qt::endl;
        ++subHeader;
    }

    if (component_->hasViews())
    {
        stream << vlnvHeader << ".views\">" << componentNumber_ << "." << subHeader
            << ". Views</a><br>" << Qt::endl;
    }
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeParameters()
//-----------------------------------------------------------------------------
void HtmlWriter::writeParameters(QTextStream& stream, int subHeaderNumber)
{
    writeSubHeader(stream, subHeaderNumber, "General parameters", "parameters");

    // Write table element
    stream << indent(3) << HTML::TABLE << "List of parameters defined for the component\">" << Qt::endl;

    // Write header row
    writeTableHeader(stream, DocumentationWriter::PARAMETER_HEADERS, 4);

    // Write parameters
    for (auto const& parameter : *component_->getParameters())
    {
        QStringList paramFields(QStringList()
            << parameter->name()
            << parameter->getType()
            << expressionFormatter_->formatReferringExpression(parameter->getValue())
            << parameter->getValueResolve()
            << expressionFormatter_->formatReferringExpression(parameter->getVectorLeft())
            << expressionFormatter_->formatReferringExpression(parameter->getVectorRight())
            << expressionFormatter_->formatReferringExpression(parameter->getArrayLeft())
            << expressionFormatter_->formatReferringExpression(parameter->getArrayRight())
            << parameter->description()
        );

        writeTableRow(stream, paramFields, 4);
    }

    stream << indent(3) << "</table>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeMemoryMaps()
//-----------------------------------------------------------------------------
void HtmlWriter::writeMemoryMaps(QTextStream& stream, int subHeaderNumber)
{
    if (component_->getMemoryMaps()->isEmpty())
    {
        return;
    }

    writeSubHeader(stream, subHeaderNumber, "Memory maps", "memoryMaps");

    const QList<QSharedPointer<MemoryMap> > componentMemoryMaps = *component_->getMemoryMaps().data();
    int memoryMapNumber = 1;

    for (auto const& memoryMap : componentMemoryMaps)
    {
        QList subHeaderNumbers({
            componentNumber_,
            subHeaderNumber,
            memoryMapNumber,
        });

        writeSubHeader(stream, subHeaderNumbers, QStringLiteral("Memory map ") + memoryMap->name(), 3);

        // Write memory map info.
        writeInfoParagraph(
            stream,
            { QStringLiteral("Description"), QStringLiteral("Address unit bits (AUB)") },
            { memoryMap->description(), memoryMap->getAddressUnitBits() }
        );

        QList <QSharedPointer <AddressBlock> > addressBlocks = getMemoryMapAddressBlocks(memoryMap);
        writeAddressBlocks(stream, addressBlocks, subHeaderNumber, memoryMapNumber);

        ++memoryMapNumber;
    }
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeAddressBlocks()
//-----------------------------------------------------------------------------
void HtmlWriter::writeAddressBlocks(QTextStream& stream, QList<QSharedPointer<AddressBlock>> addressBlocks,
    int subHeaderNumber, int memoryMapNumber)
{
    if (addressBlocks.isEmpty())
    {
        return;
    }

    int addressBlockNumber = 1;

    for (auto const& addressBlock : addressBlocks)
    {        
        QList subHeaderNumbers({
            componentNumber_,
            subHeaderNumber,
            memoryMapNumber,
            addressBlockNumber
        });
        
        writeSubHeader(stream, subHeaderNumbers, QStringLiteral("Address block ") + addressBlock->name(), 3);

        writeAddressBlockInfo(stream, addressBlock);

        // Running number to number address block registers and register files
        int registerDataNumber = 1;

        // Write address block registers
        if (auto const addressBlockRegisters = getRegisters(addressBlock->getRegisterData());
            !addressBlockRegisters.isEmpty())
        {
            QString registerTableText = QStringLiteral("Address block '") + addressBlock->name() +
                QStringLiteral("' contains the following registers:");

            writeSubHeader(stream, QList<int>(), registerTableText, 4);

            writeRegisters(stream, addressBlockRegisters, subHeaderNumber,
                memoryMapNumber, addressBlockNumber, registerDataNumber);
        }

        // Write address block register files
        if (auto const addressBlockRegisterFiles = getRegisterFiles(addressBlock->getRegisterData());
            !addressBlockRegisterFiles.isEmpty())
        {
            QString registerFilesSubText = QStringLiteral("Address block '") + addressBlock->name()
                + QStringLiteral("' contains the following register files:");
            writeSubHeader(stream, QList<int>(), registerFilesSubText, 4);

            writeRegisterFiles(stream, addressBlockRegisterFiles, subHeaderNumbers, registerDataNumber);
        }

        ++addressBlockNumber;
    }
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeRegisters()
//-----------------------------------------------------------------------------
void HtmlWriter::writeRegisters(QTextStream& stream, QList<QSharedPointer<Register> > registers,
    int subHeaderNumber, int memoryMapNumber, int addressBlockNumber, int& registerDataNumber)
{
    if (registers.isEmpty())
    {
        return;
    }

    // Write table with all address block registers.
    writeRegisterTable(stream, registers);
    
    // Write each register separately.
    for (auto const& currentRegister : registers)
    {
        QList subHeaderNumbers({
            componentNumber_,
            subHeaderNumber,
            memoryMapNumber,
            addressBlockNumber,
            registerDataNumber
        });

        writeSingleRegister(stream, currentRegister, subHeaderNumbers, registerDataNumber);
    }
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeRegisterFiles()
//-----------------------------------------------------------------------------
void HtmlWriter::writeRegisterFiles(QTextStream& stream,
    QList<QSharedPointer<RegisterFile> > registerFiles,
    QList<int> subHeaderNumbers, int& registerDataNumber)
{
    for (auto const& registerFile : registerFiles)
    {
        QList registerFileSubHeaderNumbers(subHeaderNumbers);
        registerFileSubHeaderNumbers.append(registerDataNumber);

        writeSubHeader(stream, registerFileSubHeaderNumbers,
            QStringLiteral("Register file ") + registerFile->name(), 3);

        writeRegisterFileInfo(stream, registerFile);

        auto const registerData = registerFile->getRegisterData();
        auto const registersInFile = getRegisters(registerData);

        int subRegisterDataNumber = 1;

        if (!registersInFile.isEmpty())
        {
            writeSubHeader(stream, {}, QStringLiteral("Register file ")
                + registerFile->name() + QStringLiteral(" contains the following registers:"), 4);
            writeRegisterTable(stream, registersInFile);
        }

        // Write register file registers.
        for (auto const& reg : registersInFile)
        {
            // Sub-registers need their own subheader number, as the hierarchy ends here.
            QList newSubHeaderNumbers(registerFileSubHeaderNumbers);
            newSubHeaderNumbers.append(subRegisterDataNumber);
            writeSingleRegister(stream, reg, newSubHeaderNumbers, registerDataNumber);
            subRegisterDataNumber++;
        }

        // Write the register files of the current register file recursively.
        for (auto const& regFile : getRegisterFiles(registerData))
        {
            // Note: subRegisterDataNumber is passed to the recursive function call.
            // The value of registerDataNumber stays constant for the same parent register file.
            writeRegisterFiles(stream, QList({ regFile }), registerFileSubHeaderNumbers, subRegisterDataNumber);
            subRegisterDataNumber++;
        }

        registerDataNumber++;
    }
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeFields()
//-----------------------------------------------------------------------------
void HtmlWriter::writeFields(QTextStream& stream, QSharedPointer<Register> currentRegister,
    QList<int> registerSubHeaderNumbers)
{
    if (currentRegister->getFields()->isEmpty())
    {
        return;
    }

    // Write all register fields in one table.
    writeFieldTable(stream, currentRegister);

    int fieldNumber = 1;

    for (auto const& field : *currentRegister->getFields())
    {
        QList fieldSubHeaderNumbers = registerSubHeaderNumbers;
        fieldSubHeaderNumbers << fieldNumber;

        writeSubHeader(stream, fieldSubHeaderNumbers, QStringLiteral("Field ") + field->name(), 3);
        writeSingleField(stream, field);

        ++fieldNumber;
    }
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writePorts()
//-----------------------------------------------------------------------------
void HtmlWriter::writePorts(QTextStream& stream, int subHeaderNumber)
{
    writeSubHeader(stream, subHeaderNumber, "Ports", "ports");

    const QList<QSharedPointer<Port> > ports = *component_->getPorts().data();
    QString tableTitle = "List of all ports the component has.";
    
    writePortTable(stream, tableTitle, ports);
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeInterfaces()
//-----------------------------------------------------------------------------
void HtmlWriter::writeInterfaces(QTextStream& stream, int& subHeaderNumber)
{
    writeSubHeader(stream, subHeaderNumber, "Bus interfaces", "interfaces");

    int interfaceNumber = 1;

    for (auto const& interface : *component_->getBusInterfaces())
    {
        QList subHeaderNumbers({
            componentNumber_,
            subHeaderNumber,
            interfaceNumber,
        });

        writeSubHeader(stream, subHeaderNumbers, QStringLiteral("Bus interface ") + interface->name(), 3);

        auto const ports = component_->getPortsMappedInInterface(interface->name());
        
        writeInterfaceInfo(stream, interface, !ports.isEmpty());

        if (!ports.isEmpty())
        {
            writePortTable(stream, "", ports);
        }

        ++interfaceNumber;
    }
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeFileSets()
//-----------------------------------------------------------------------------
void HtmlWriter::writeFileSets(QTextStream& stream, int& subHeaderNumber)
{
    writeSubHeader(stream, subHeaderNumber, "File sets", "fileSets");

    const QList<QSharedPointer<FileSet> > fileSets = *component_->getFileSets();

    int fileSetNumber = 1;

    for (auto const& fileSet : fileSets)
    {
        writeSubHeader(stream, QList({ componentNumber_, subHeaderNumber, fileSetNumber }),
            QStringLiteral("File set ") + fileSet->name(), 3);

        stream << indent(3) << "<p>" << Qt::endl;

        // description
        if (!fileSet->description().isEmpty())
        {
            stream << indent(3) << HTML::INDENT << "<strong>Description:</strong> " <<
                fileSet->description() << "<br>" << Qt::endl;
        }

        // identifiers
        writeFileSetGroupdIdentifiers(stream, fileSet);

        // Default file builders table
        writeDefaultFileBuilders(stream, fileSet);

        writeFiles(stream, fileSet, subHeaderNumber, fileSetNumber);

        ++fileSetNumber;
    }
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::setComponentNumber()
//-----------------------------------------------------------------------------
void HtmlWriter::setComponentNumber(int componentNumber)
{
    componentNumber_ = componentNumber;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeSubHeader()
//-----------------------------------------------------------------------------
void HtmlWriter::writeSubHeader(QTextStream& stream, int subHeaderNumber,
    QString const& headerText, QString const& headerId) const
{
    stream << "\t\t<h2><a id=\"" << component_->getVlnv().toString() << "." << headerId << "\">" <<
        componentNumber_ << "." << subHeaderNumber << " " << headerText << "</a></h2>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeSubHeader()
//-----------------------------------------------------------------------------
void HtmlWriter::writeSubHeader(QTextStream& stream, QList<int> const& subHeaderNumbers,
    QString const& title, int level) const
{
    // Writes the header level
    QString headerTag = QStringLiteral("<h") + QString::number(level) + QStringLiteral(">");
    QString headerClosingTag = QStringLiteral("</h") + QString::number(level) + QStringLiteral(">");

    QStringList subHeaderNumberParts;
    for (int number : subHeaderNumbers)
    {
        subHeaderNumberParts << QString::number(number);
    }

    QString headerTitle = title;

    if (!subHeaderNumberParts.isEmpty())
    {
        headerTitle = subHeaderNumberParts.join(".") + " " + title;
    }

    stream << indent(3) << headerTag << headerTitle << headerClosingTag << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeDescription()
//-----------------------------------------------------------------------------
void HtmlWriter::writeDescription(QTextStream& stream, QString const& description)
{
    stream << indent(3) << "<p>" << Qt::endl;
    if (!description.isEmpty())
    {
        stream << indent(3) << HTML::INDENT << "<strong>Description:</strong> " <<
            description << "<br>" << Qt::endl;
    }
    stream << indent(3) << "</p>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeInfoParagraph()
//-----------------------------------------------------------------------------
void HtmlWriter::writeInfoParagraph(QTextStream& stream, QStringList const& names, QStringList const& values)
{
    stream << indent(3) << "<p>" << Qt::endl;

    for (auto i = 0; i < names.length(); ++i)
    {
        stream << indent(3) << HTML::INDENT << "<strong>" << names.at(i) << ":</strong> "
            << values.at(i) << "<br>" << Qt::endl;
    }

    stream << indent(3) << "</p>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeErrorMessage()
//-----------------------------------------------------------------------------
void HtmlWriter::writeErrorMessage(QTextStream& stream, QString const& message)
{
    stream << indent(3) << "<p>" << Qt::endl;
    stream << indent(3) << "<strong><font color=red>" << message << "</font></strong><br>" << Qt::endl;
    stream << indent(3) << "</p>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeDocumentReference()
//-----------------------------------------------------------------------------
void HtmlWriter::writeDocumentReference(QTextStream& stream, QString const& documentType,
    QSharedPointer<ConfigurableVLNVReference> vlnvReference)
{
    if (!vlnvReference)
    {
        return;
    }

    if (!libraryHandler_->getModelReadOnly(*vlnvReference))
    {
        QString errorMsg(QObject::tr("VLNV: %1 was not found in library.").arg(vlnvReference->toString()));
        writeErrorMessage(stream, errorMsg);
        return;
    }

    stream << indent(3) << "<p>" << Qt::endl;
    stream << indent(4) << HTML::INDENT <<
        "<strong>" << documentType << ": </strong>" << vlnvReference->toString() << "<br>" << Qt::endl;

    QFileInfo vlnvXMLInfo(libraryHandler_->getPath(*vlnvReference));
    QString relativeXmlPath = General::getRelativePath(getTargetPath(), vlnvXMLInfo.absoluteFilePath());

    stream << indent(4) << HTML::INDENT << "<strong>" <<
        "IP-Xact file: </strong><a href=\"" << relativeXmlPath << "\">" << vlnvXMLInfo.fileName() <<
        "</a><br>" << Qt::endl;

    stream << indent(3) << "</p>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeDiagram()
//-----------------------------------------------------------------------------
void HtmlWriter::writeDiagram(QTextStream& stream, QString const& title,
    QString const& link, QString const& altText)
{
    stream << indent(3) << title << "<br>" << Qt::endl;
    stream << indent(3) << "<img src=\"" << link << "\" alt=\"" << altText << "\"><br>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeDesignInstances()
//-----------------------------------------------------------------------------
void HtmlWriter::writeDesignInstances(QTextStream& stream, QSharedPointer<Design> design,
    QSharedPointer<DesignConfiguration> configuration)
{
    if (design->getComponentInstances()->isEmpty())
    {
        return;
    }

    QString instanceTitle = QString("Component instances within design %1").arg(design->getVlnv().toString());
    stream << indent(3) << "<br>" << Qt::endl;
    stream << indent(3) << instanceTitle << ":<br>" << Qt::endl;
    
    stream << indent(3) << HTML::TABLE << "\">" << Qt::endl;
    
    writeTableHeader(stream, DocumentationWriter::DESIGN_INSTANCE_HEADERS, 3);

    for (auto const& instance : *design->getComponentInstances())
    {
        QStringList rowCells(QStringList()
            << instance->getInstanceName()
            << "<a href=\"" + instance->getComponentRef()->toString(":") + "\">"
                + instance->getComponentRef()->toString(" - ") + "</a>"
            << getComponentInstanceConfigurableElements(instance, design)
            << (configuration && configuration->getDesignRef() == design->getVlnv()
                ? configuration->getActiveView(instance->getInstanceName())
                : QStringLiteral(""))
        );

        writeTableRow(stream, rowCells, 4);
    }

    stream << indent(3) << "</table>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeEndOfDocument()
//-----------------------------------------------------------------------------
void HtmlWriter::writeEndOfDocument(QTextStream& stream)
{
    stream << indent(1) << "</body>" << Qt::endl;
    stream << "</html>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::indent()
//-----------------------------------------------------------------------------
QString HtmlWriter::indent(int n) const
{
    auto tab = QStringLiteral("\t");
    return tab.repeated(n);
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeRegisterTable()
//-----------------------------------------------------------------------------
void HtmlWriter::writeRegisterTable(QTextStream& stream, QList<QSharedPointer<Register>> registers)
{
    QStringList allRegistersTableHeader;
    allRegistersTableHeader << QStringLiteral("Register name");
    allRegistersTableHeader.append(DocumentationWriter::REGISTER_HEADERS);

    stream << indent(3) << HTML::TABLE << "" << "\">" << Qt::endl;
    writeTableHeader(stream, allRegistersTableHeader, 4);

    for (auto const& currentRegister : registers)
    {
        QStringList registerInfoTableRowCells(QStringList()
            << currentRegister->name()
            << expressionFormatter_->formatReferringExpression(currentRegister->getAddressOffset())
            << expressionFormatter_->formatReferringExpression(currentRegister->getSize())
            << expressionFormatter_->formatReferringExpression(currentRegister->getDimension())
            << currentRegister->getVolatile()
            << AccessTypes::access2Str(currentRegister->getAccess())
        );

        writeTableRow(stream, registerInfoTableRowCells, 4);
    }

    stream << indent(3) << "</table>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeFieldTable()
//-----------------------------------------------------------------------------
void HtmlWriter::writeFieldTable(QTextStream& stream, QSharedPointer<Register> reg)
{
    stream << indent(3) << "<h4>Register '" << reg->name() << "' contains the following fields:</h4>" <<
        Qt::endl;

    QString tableTitle = "List of fields contained within register " + reg->name() + ".";
    stream << indent(3) << HTML::TABLE << tableTitle << "\">" << Qt::endl;

    writeTableHeader(stream, DocumentationWriter::FIELD_HEADERS, 4);

    for (auto const& field : *reg->getFields())
    {
        QStringList fieldTableCells(QStringList()
            << "<a id=\"" + vlnvString_ + ".field." + field->name() + "\">" + field->name() + "</a>"
            << expressionFormatter_->formatReferringExpression(field->getBitOffset())
            << expressionFormatter_->formatReferringExpression(field->getBitWidth())
            << field->getVolatile().toString()
            << AccessTypes::access2Str(field->getAccess())
            << getFieldResetInfo(field)
            << field->description()
        );

        writeTableRow(stream, fieldTableCells, 4);
    }

    stream << indent(3) << "</table>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeFieldEnumerations()
//-----------------------------------------------------------------------------
void HtmlWriter::writeFieldEnumerations(QTextStream& stream, QSharedPointer<Field> field)
{
    auto const enumerations = field->getEnumeratedValues();

    if (enumerations->isEmpty())
    {
        return;
    }

    writeSubHeader(stream, {}, "Enumerations:", 4);

    stream << indent(3) << HTML::TABLE << "" << "\">" << Qt::endl;

    writeTableHeader(stream, { "Name", "Value" }, 4);

    for (auto const& enumeration : *enumerations)
    {
        writeTableRow(stream, { enumeration->name(), enumeration->getValue() }, 4);
    }
    stream << indent(3) << "</table>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeTableRow()
//-----------------------------------------------------------------------------
void HtmlWriter::writeTableRow(QTextStream& stream, QStringList const& cells, int indentation) const
{
    stream << indent(indentation) << "<tr>" << Qt::endl;
    
    for (auto const& field : cells)
    {
        stream << indent(indentation + 1) << "<td>" << field << "</td>" << Qt::endl;
    }

    stream << indent(indentation) << "</tr>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeTableHeader()
//-----------------------------------------------------------------------------
void HtmlWriter::writeTableHeader(QTextStream& stream, QStringList const& headerCells, int indentation) const
{
    stream << indent(indentation) << "<tr>" << Qt::endl;

    for (auto const& fields : headerCells)
    {
        stream << indent(indentation + 1) << "<th>" << fields << "</th>" << Qt::endl;
    }

    stream << indent(indentation) << "</tr>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writePortTable()
//-----------------------------------------------------------------------------
void HtmlWriter::writePortTable(QTextStream& stream, QString const& tableTitle,
    QList<QSharedPointer<Port>> ports) const
{
    stream << indent(3) << HTML::TABLE << tableTitle << "\">" << Qt::endl;

    writeTableHeader(stream, DocumentationWriter::PORT_HEADERS, 4);

    for (auto const& port : ports)
    {
        QStringList portTableCells(QStringList()
            << "<a id=\"" + vlnvString_ + ".port." + port->name() + "\">" + port->name() + "</a>"
            << DirectionTypes::direction2Str(port->getDirection())
            << expressionFormatter_->formatReferringExpression(port->getLeftBound())
            << expressionFormatter_->formatReferringExpression(port->getRightBound())
            << port->getTypeName()
            << port->getTypeDefinition(port->getTypeName())
            << expressionFormatter_->formatReferringExpression(port->getDefaultValue())
            << expressionFormatter_->formatReferringExpression(port->getArrayLeft())
            << expressionFormatter_->formatReferringExpression(port->getArrayRight())
            << port->description()
        );

        writeTableRow(stream, portTableCells, 4);
    }

    stream << indent(3) << "</table>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeFileSetGroupdIdentifiers()
//-----------------------------------------------------------------------------
void HtmlWriter::writeFileSetGroupdIdentifiers(QTextStream& stream, QSharedPointer<FileSet> fileSet) const
{
    stream << indent(3) << HTML::INDENT << "<strong>Identifiers:</strong> ";
    
    QStringList groups = *fileSet->getGroups();
    
    stream << groups.join(", ") << "<br>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeDefaultFileBuilders()
//-----------------------------------------------------------------------------
void HtmlWriter::writeDefaultFileBuilders(QTextStream& stream, QSharedPointer<FileSet> fileSet) const
{
    const auto defaultFileBuilders = fileSet->getDefaultFileBuilders();

    if (defaultFileBuilders->isEmpty())
    {
        stream << indent(3) << "</p>" << Qt::endl;
        return;
    }

    stream << indent(3) << HTML::INDENT << "<strong>Default file builders:</strong>" << Qt::endl;
    stream << indent(3) << "</p>" << Qt::endl;

    QString tableTitle = QStringLiteral("Default file build commands");
    stream << indent(3) << HTML::TABLE << tableTitle << "\">" << Qt::endl;

    writeTableHeader(stream, DocumentationWriter::DEFAULT_FILE_BUILDER_HEADERS, 4);

    for (auto const& defaultBuilder : *defaultFileBuilders)
    {
        QStringList builderCells(QStringList()
            << defaultBuilder->getFileType().type_
            << defaultBuilder->getCommand()
            << defaultBuilder->getFlags()
            << expressionFormatter_->formatReferringExpression(defaultBuilder->getReplaceDefaultFlags())
        );

        writeTableRow(stream, builderCells, 4);
    }

    stream << indent(3) << "</table>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeFiles()
//-----------------------------------------------------------------------------
void HtmlWriter::writeFiles(QTextStream& stream, QSharedPointer<FileSet> fileSet,
    int subHeaderNumber, int fileSetNumber)
{
    QList<QSharedPointer<File> > files = *fileSet->getFiles();

    if (files.isEmpty())
    {
        return;
    }

    int filesSubHeaderNumber = 1;

    QList filesSubHeaderNumbers({
        componentNumber_,
        subHeaderNumber,
        fileSetNumber,
        filesSubHeaderNumber
    });

    writeSubHeader(stream, filesSubHeaderNumbers, "Files", 4);

    QString tableTitle = QStringLiteral("List of files contained in this file set.");
    stream << indent(3) << HTML::TABLE << tableTitle << "\">" << Qt::endl;

    writeTableHeader(stream, DocumentationWriter::FILE_HEADERS, 4);

    for (auto const& file : files)
    {
        writeSingleFile(stream, file);
    }

    stream << indent(3) << "</table>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeSingleFile()
//-----------------------------------------------------------------------------
void HtmlWriter::writeSingleFile(QTextStream& stream, QSharedPointer<File> file) const
{
    QString relativeFilePath = file->name();
    QString absFilePath = General::getAbsolutePath(libraryHandler_->getPath(component_->getVlnv()),
        relativeFilePath);

    QFileInfo fileInfo(absFilePath);

    // get relative path from html file to the file
    QFileInfo htmlInfo(getTargetPath());
    QString pathFromDocToFile = General::getRelativePath(getTargetPath(), absFilePath);

    QSharedPointer<BuildCommand> buildCommand = file->getBuildCommand();

    QStringList fileTypes;
    for (auto const& fileType : *file->getFileTypes())
    {
        fileTypes.append(fileType.type_);
    }

    QStringList fileTableCells(QStringList()
        << "<a href=\"" + pathFromDocToFile + "\">" + file->name() + "</a>"
        << file->getLogicalName()
        << (buildCommand ? buildCommand->getCommand() : QStringLiteral(""))
        << (buildCommand ? buildCommand->getFlags() : QStringLiteral(""))
        << fileTypes.join(",<br>")
        << file->getDescription()
    );

    writeTableRow(stream, fileTableCells, 4);
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeImplementationDetails()
//-----------------------------------------------------------------------------
void HtmlWriter::writeImplementationDetails(QTextStream& stream,
    QSharedPointer<ComponentInstantiation> instantiation)
{
    QString language = instantiation->getLanguage();
    QString library = instantiation->getLibraryName();
    QString package = instantiation->getPackageName();
    QString module = instantiation->getModuleName();
    QString architecture = instantiation->getArchitectureName();
    QString configuration = instantiation->getConfigurationName();

    if (language.isEmpty() && library.isEmpty() && package.isEmpty() && module.isEmpty() &&
        architecture.isEmpty() && configuration.isEmpty())
    {
        return;
    }

    stream << indent(3) << "<p>" << Qt::endl;

    if (!language.isEmpty())
    {
        stream << indent(4) << HTML::INDENT << "<strong>Language: </strong>" <<
            language;

        if (instantiation->isLanguageStrict())
        {
            stream << " <strong>strict</strong>";
        }

        stream << "<br>" << Qt::endl;
    }
    if (!library.isEmpty())
    {
        stream << indent(4) << HTML::INDENT << "<strong>Library: </strong>" <<
            library << "<br>" << Qt::endl;
    }
    if (!package.isEmpty())
    {
        stream << indent(4) << HTML::INDENT << "<strong>Package: </strong>" <<
            package << "<br>" << Qt::endl;
    }
    if (!module.isEmpty())
    {
        stream << indent(4) << HTML::INDENT << "<strong>Module name: </strong>" <<
            module << "<br>" << Qt::endl;
    }
    if (!architecture.isEmpty())
    {
        stream << indent(4) << HTML::INDENT <<
            "<strong>Architecture: </strong>" << architecture << "<br>" << Qt::endl;
    }
    if (!configuration.isEmpty())
    {
        stream << indent(4) << HTML::INDENT <<
            "<strong>Configuration: </strong>" << configuration << "<br>" << Qt::endl;
    }

    stream << indent(3) << "</p>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeFileSetReferences()
//-----------------------------------------------------------------------------
void HtmlWriter::writeFileSetReferences(QTextStream& stream, QSharedPointer<ComponentInstantiation> instantiation)
{
    auto fileSetRefs = instantiation->getFileSetReferences();

    if (fileSetRefs->isEmpty())
    {
        return;
    }

    stream << indent(3) << "<p>" << Qt::endl;
    stream << indent(4) << "File sets contained in this component instantiation: </strong>" <<
        Qt::endl;
    stream << indent(3) << "</p>" << Qt::endl;

    stream << indent(3) << "<ul>" << Qt::endl;

    for (auto const& fileSetRef : *fileSetRefs)
    {
        stream << indent(4) << "<li><a href=\"#" << component_->getVlnv().toString() <<
            ".fileSet." << fileSetRef->getReference() << "\">" << fileSetRef->getReference() << 
            "</a></li>" << Qt::endl;
    }

    stream << indent(3) << "</ul>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeFileBuildCommands()
//-----------------------------------------------------------------------------
void HtmlWriter::writeFileBuildCommands(QTextStream& stream, QSharedPointer<ComponentInstantiation> instantiation,
    QSharedPointer<ExpressionFormatter> instantiationFormatter)
{
    if (instantiation->getDefaultFileBuilders()->isEmpty())
    {
        return;
    }

    stream << indent(3) << "<p>Default file build commands:</p>" << Qt::endl;

    stream << indent(3) << HTML::TABLE << "\">" << Qt::endl;
    writeTableHeader(stream, DocumentationWriter::DEFAULT_FILE_BUILDER_HEADERS, 3);

    for (auto const& defaultBuilder : *instantiation->getDefaultFileBuilders())
    {
        QStringList builderCells(QStringList()
            << defaultBuilder->getFileType().type_
            << defaultBuilder->getCommand()
            << defaultBuilder->getFlags()
            << instantiationFormatter->formatReferringExpression(defaultBuilder->getReplaceDefaultFlags())
        );

        writeTableRow(stream, builderCells, 4);
    }

    stream << indent(3) << "</table>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeConfigurableElementValues()
//-----------------------------------------------------------------------------
void HtmlWriter::writeConfigurableElementValues(QTextStream& stream,
    QSharedPointer<ConfigurableVLNVReference> vlnvReference,
    QSharedPointer<ExpressionFormatter> instantiationFormatter)
{
    if (!vlnvReference || !vlnvReference->getConfigurableElementValues() ||
        vlnvReference->getConfigurableElementValues()->isEmpty())
    {
        return;
    }

    stream << indent(3) << "<p>Configurable element values:</p>" << Qt::endl;

    QStringList paramHeaders({ QStringLiteral("Name"), QStringLiteral("Value") });
    
    stream << indent(3) << HTML::TABLE << "\">" << Qt::endl;
    writeTableHeader(stream, paramHeaders, 3);

    for (auto const& element : *vlnvReference->getConfigurableElementValues())
    {
        QStringList rowCells(QStringList()
            << instantiationFormatter->formatReferringExpression(element->getReferenceId())
            << instantiationFormatter->formatReferringExpression(element->getConfigurableValue())
        );
        writeTableRow(stream, rowCells, 4);
    }

    stream << indent(3) << "</table>" << Qt::endl;
}

void HtmlWriter::writeModuleParameterTable(QTextStream& stream, QString const& tableHeading,
    QSharedPointer<QList<QSharedPointer<Parameter> > > moduleParameters,
    QSharedPointer<ExpressionFormatter> formatter)
{
    if (!moduleParameters || moduleParameters->isEmpty())
    {
        return;
    }

    stream << indent(3) << "<p><strong>" << tableHeading << "</strong></p>" << Qt::endl;

    stream << indent(3) << HTML::TABLE << "\">" << Qt::endl;
    writeTableHeader(stream, DocumentationWriter::MODULE_PARAMETER_HEADERS, 4);

    for (auto const& parameter : *moduleParameters)
    {
        QSharedPointer<ModuleParameter> moduleParameter = parameter.dynamicCast<ModuleParameter>();

        QStringList paramCells(QStringList()
            << moduleParameter->name()
            << moduleParameter->getType()
            << formatter->formatReferringExpression(moduleParameter->getValue())
            << moduleParameter->getDataType()
            << moduleParameter->getUsageType()
            << moduleParameter->getValueResolve()
            << formatter->formatReferringExpression(moduleParameter->getVectorLeft())
            << formatter->formatReferringExpression(moduleParameter->getVectorRight())
            << formatter->formatReferringExpression(moduleParameter->getArrayLeft())
            << formatter->formatReferringExpression(moduleParameter->getArrayRight())
            << moduleParameter->description()
        );

        writeTableRow(stream, paramCells, 4);
    }

    stream << indent(3) << "</table>" << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::getComponentInstanceConfigurableElements()
//-----------------------------------------------------------------------------
QString HtmlWriter::getComponentInstanceConfigurableElements(QSharedPointer<ComponentInstance> instance,
    QSharedPointer<Design> design)
{
    QString cell;
    VLNV componentVLNV = *instance->getComponentRef();

    QSharedPointer<Document> libComp = libraryHandler_->getModel(componentVLNV);
    QSharedPointer<Component> component = libComp.staticCast<Component>();

    QSharedPointer<ExpressionFormatter> equationFormatter = createDesignInstanceFormatter(design, component);

    QSharedPointer<QList<QSharedPointer<ConfigurableElementValue> > >  confElements =
        instance->getConfigurableElementValues();

    for (auto const& element : *confElements)
    {
        QString configurableElementID = element->getReferenceId();
        QString configurableElementName = equationFormatter->formatReferringExpression(configurableElementID);

        if (configurableElementID == configurableElementName)
        {
            cell.append(QStringLiteral("<font color=red>Unknown</font>"));
        }
        else
        {
            cell.append(configurableElementName);
        }

        cell.append(" = " + equationFormatter->formatReferringExpression(element->getConfigurableValue()));

        if (element != confElements->last())
        {
            cell.append(QStringLiteral("<br>"));
        }
    }

    return cell;
}

//-----------------------------------------------------------------------------
// Function: HtmlWriter::writeParameterTable()
//-----------------------------------------------------------------------------
void HtmlWriter::writeParameterTable(QTextStream& stream, QString const& tableHeading,
    ParameterList parameters, QSharedPointer<ExpressionFormatter> formatter)
{
    if (!parameters || parameters->isEmpty())
    {
        return;
    }

    stream << indent(3) << "<p><strong>" << tableHeading << "</strong></p>" << Qt::endl;

    stream << indent(3) << HTML::TABLE <<"\">" << Qt::endl;

    writeTableHeader(stream, DocumentationWriter::PARAMETER_HEADERS, 4);

    for (auto const& parameter : *parameters)
    {
        QStringList paramCells(QStringList()
            << parameter->name()
            << parameter->getType()
            << formatter->formatReferringExpression(parameter->getValue())
            << parameter->getValueResolve()
            << formatter->formatReferringExpression(parameter->getVectorLeft())
            << formatter->formatReferringExpression(parameter->getVectorRight())
            << formatter->formatReferringExpression(parameter->getArrayLeft())
            << formatter->formatReferringExpression(parameter->getArrayRight())
            << parameter->description()
        );

        writeTableRow(stream, paramCells, 4);
    }

    stream << indent(3) << "</table>" << Qt::endl;
}
