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

#include "MarkdownWriter.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/EnumeratedValue.h>
#include <IPXACTmodels/Design/Design.h>

#include <IPXACTmodels/DesignConfiguration/DesignConfiguration.h>

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

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

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::MarkdownWriter()
//-----------------------------------------------------------------------------
MarkdownWriter::MarkdownWriter(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: MarkdownWriter::~MarkdownWriter()
//-----------------------------------------------------------------------------
MarkdownWriter::~MarkdownWriter()
{
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeHeader()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeHeader(QTextStream& stream)
{
    QSettings settings;
    
    // Write markdown header
    stream << "###### This document was generated by Kactus2 on " 
        << QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm:ss")
        << " by user " << settings.value("General/Username").toString()
        << "  " << Qt::endl << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeComponentHeader()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeComponentHeader(QTextStream& stream)
{
    // For empty line between table of contents and first component
    if (componentNumber_ == 1)
    {
        stream << Qt::endl;
    }
    
    stream << "# " << componentNumber_ << ". Component " << vlnvString_ 
        << "<a id=\"" << vlnvString_ << "\">  " << Qt::endl << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeComponentInfo()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeComponentInfo(QTextStream& stream)
{
    stream << "![Component " << component_->getVlnv().toString(".") << "]("
        << getImagesPath().split("/").back() << "/"
        << component_->getVlnv().toString(".") << ".png)" << Qt::endl << Qt::endl;

    if (auto const& description = component_->getDescription(); !description.isEmpty())
    {
        writeDescription(stream, description);
    }
    
    // print relative path to the xml file
    QFileInfo compXmlInfo(libraryHandler_->getPath(component_->getVlnv()));
    QString relativeXmlPath = General::getRelativePath(getTargetPath(), compXmlInfo.absoluteFilePath());

    stream << "**IP-Xact file:** " << "[" << compXmlInfo.fileName() << "](" << relativeXmlPath << ")  "
        << Qt::endl << Qt::endl;
}

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

    stream << "**Product hierarchy:** " <<
        KactusAttribute::hierarchyToString(component_->getHierarchy()) << "  " << Qt::endl
        << "**Component implementation:** " <<
        KactusAttribute::implementationToString(component_->getImplementation()) << "  " << Qt::endl
        << "**Component firmness:** " <<
        KactusAttribute::firmnessToString(component_->getFirmness()) << "  " << Qt::endl << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeTableOfContentsHeader()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeTableOfContentsHeader(QTextStream& stream)
{
    stream << "**Table of contents**  " << Qt::endl << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeTableOfContents()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeTableOfContents(QTextStream& stream)
{
    // Write component header
    stream << componentNumber_ << ". [" << "Component " << component_->getVlnv().toString(" - ") << "]" <<
        "(#" << vlnvString_ << ")  " << Qt::endl;

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

    // Write component subheaders. Component has at least kactus attributes.
    stream << "\t" << componentNumber_ << "." << subHeader << ". " << "[Kactus2 attributes" <<
        "](#" << vlnvString_ << ".attributes)  " << Qt::endl;

    ++subHeader;

    if (component_->hasParameters())
    {
        stream << "\t" << componentNumber_ << "." << subHeader << ". " << "[General parameters]" <<
            "(#" << vlnvString_ << ".parameters)  " << Qt::endl;
        ++subHeader;
    }

    if (!component_->getMemoryMaps()->isEmpty())
    {
        stream << "\t" << componentNumber_ << "." << subHeader << ". " << "[Memory maps]" <<
            "(#" << vlnvString_ << ".memoryMaps)  " << Qt::endl;
        ++subHeader;
    }

    if (component_->hasPorts())
    {
        stream << "\t" << componentNumber_ << "." << subHeader << ". " << "[Ports]" <<
            "(#" << vlnvString_ << ".ports)  " << Qt::endl;
        ++subHeader;
    }
    
    if (component_->hasInterfaces())
    {
        stream << "\t" << componentNumber_ << "." << subHeader << ". " << "[Bus interfaces]" <<
            "(#" << vlnvString_ << ".interfaces)  " << Qt::endl;
        ++subHeader;
    }
    
    if (component_->hasFileSets())
    {
        stream << "\t" << componentNumber_ << "." << subHeader << ". " << "[File sets]" <<
            "(#" << vlnvString_ << ".fileSets)  " << Qt::endl;
        ++subHeader;
    }

    if (component_->hasViews())
    {
        stream << "\t" << componentNumber_ << "." << subHeader << ". " << "[Views]" <<
            "(#" << vlnvString_ << ".views)  " << Qt::endl;
        ++subHeader;
    }
}

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

    for (auto const& parameter : *component_->getParameters())
    {
        QStringList paramCells(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, paramCells);
    }
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeMemoryMaps()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeMemoryMaps(QTextStream& stream, int subHeaderNumber)
{
    if (component_->getMemoryMaps()->isEmpty())
    {
        return;
    }
    
    writeSubHeader(stream, subHeaderNumber, "Memory maps", "memoryMaps");

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

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

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

        // Memory map description and address unit bits
        if (!memoryMap->description().isEmpty())
        {
            writeDescription(stream, memoryMap->description());
        }
        
        stream << "**Address unit bits (AUB):** " << memoryMap->getAddressUnitBits()
            << "  " << Qt::endl << Qt::endl;

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

        ++memoryMapNumber;
    }
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeAddressBlocks()
//-----------------------------------------------------------------------------
void MarkdownWriter::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: MarkdownWriter::writeRegisterFiles()
//-----------------------------------------------------------------------------
void MarkdownWriter::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();

        int subRegisterDataNumber = 1;

        auto const registersInFile = getRegisters(registerData);

        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: MarkdownWriter::writeRegisters()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeRegisters(QTextStream& stream, QList<QSharedPointer<Register > > registers,
    int subHeaderNumber, int memoryMapNumber, int addressBlockNumber, int& registerDataNumber)
{
    if (registers.isEmpty())
    {
        return;
    }

    // Write table with all registers of the address block.
    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: MarkdownWriter::writeFields()
//-----------------------------------------------------------------------------
void MarkdownWriter::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: MarkdownWriter::writePorts()
//-----------------------------------------------------------------------------
void MarkdownWriter::writePorts(QTextStream& stream, int subHeaderNumber)
{
    writeSubHeader(stream, subHeaderNumber, "Ports", "ports");

    const QList<QSharedPointer<Port> > ports = *component_->getPorts();

    writePortTable(stream, ports);
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeInterfaces()
//-----------------------------------------------------------------------------
void MarkdownWriter::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: MarkdownWriter::writeFileSets()
//-----------------------------------------------------------------------------
void MarkdownWriter::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);

        // description
        if (!fileSet->description().isEmpty())
        {
            writeDescription(stream, fileSet->description());
        }

        // identifiers
        writeFileSetGroupdIdentifiers(stream, fileSet);

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

        writeFiles(stream, fileSet, subHeaderNumber, fileSetNumber);

        ++fileSetNumber;
    }
}

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

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeSubHeader()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeSubHeader(QTextStream& stream, int subHeaderNumber,
    QString const& headerText, QString const& headerId) const
{
    stream << "## " << componentNumber_ << "." << subHeaderNumber << " " << headerText << " <a id=\"" <<
        vlnvString_ << "." << headerId << "\">  " << Qt::endl << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeSubHeader()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeSubHeader(QTextStream& stream, QList<int> const& subHeaderNumbers,
    QString const& title, int level) const
{
    // Writes the header level
    QString headerTag = QStringLiteral("#").repeated(level) + " ";

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

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

    stream << headerTag << headerTitle << "  " << Qt::endl << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeInfoParagraph()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeInfoParagraph(QTextStream& stream, QStringList const& names, QStringList const& values)
{
    for (auto i = 0; i < names.length(); ++i)
    {
        stream << "**" << names.at(i) << ":** " << values.at(i) << "  " << Qt::endl;
    }

    stream << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeErrorMessage()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeErrorMessage(QTextStream& stream, QString const& message)
{
    stream << "<span style=\"color:red\">" << message << "</span>  " << Qt::endl;
}

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

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

    stream << "**" << documentType << ":** " << vlnvReference->toString() << "  " << Qt::endl;

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

    stream << "**IP-Xact file:** [" << vlnvXMLInfo.fileName() << "](" << relativeXmlPath << ")  " << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeDiagram()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeDiagram(QTextStream& stream, QString const& title,
    QString const& link,QString const& altText)
{
    stream << title << "  " << Qt::endl;
    stream << "![" << altText << "](" << link << ")  " << Qt::endl << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeDesignInstances()
//-----------------------------------------------------------------------------
void MarkdownWriter::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 << instanceTitle << ":  " << Qt::endl << Qt::endl;

    writeTableHeader(stream, DocumentationWriter::DESIGN_INSTANCE_HEADERS);

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

        writeTableRow(stream, rowCells);
    }
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeEndOfDocument()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeEndOfDocument(QTextStream& /*stream*/)
{
}

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

    writeTableHeader(stream, allRegistersTableHeader);

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

        writeTableRow(stream, registersTableRowCells);
    }
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeFieldTable()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeFieldTable(QTextStream& stream, QSharedPointer<Register> reg) const
{
    QString headerTitle = QStringLiteral("Register '")
        + reg->name()
        + QStringLiteral("' contains the following fields:");

    writeSubHeader(stream, QList <int>(), headerTitle, 4);
    writeTableHeader(stream, DocumentationWriter::FIELD_HEADERS);

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

        writeTableRow(stream, fieldTableCells);
    }
}

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

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

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

    writeTableHeader(stream, { "Name", "Value" });
    
    for (auto const& enumeration : *enumerations)
    {
        writeTableRow(stream, { enumeration->name(), enumeration->getValue() });
    }
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeTableRow()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeTableRow(QTextStream& stream, QStringList const& cells) const
{
    for (auto const& cell : cells)
    {
        stream << "|" << cell;
    }

    stream << "|  " << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeTableSeparator()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeTableSeparator(QTextStream& stream, int columns) const
{
    QString tableSeparator(":---- ");   // :--- aligns text in cells to the left
    QStringList tableSeparators = tableSeparator.repeated(columns).split(" ", Qt::SkipEmptyParts);
    writeTableRow(stream, tableSeparators);
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeTableHeader()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeTableHeader(QTextStream& stream, QStringList const& headers) const
{
    writeTableRow(stream, headers);
    writeTableSeparator(stream, headers.length());
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writePortTable()
//-----------------------------------------------------------------------------
void MarkdownWriter::writePortTable(QTextStream& stream, QList<QSharedPointer<Port>> ports) const
{
    writeTableHeader(stream, DocumentationWriter::PORT_HEADERS);

    for (auto const& port : ports)
    {
        QStringList portTableCells(QStringList()
            << port->name() + " <a id=\"" + vlnvString_ + ".port." + port->name() + "\">"
            << 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);
    }
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeDescription()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeDescription(QTextStream& stream, QString const& description)
{
    stream << "**Description:** " << description << "  " << Qt::endl << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeFileSetGroupdIdentifiers()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeFileSetGroupdIdentifiers(QTextStream& stream, QSharedPointer<FileSet> fileSet) const
{
    stream << "**Identifiers:** ";

    QStringList groups = *fileSet->getGroups();
    
    stream << groups.join(", ") << "  " << Qt::endl << Qt::endl;
}

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

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

    stream << "**Default file builders:**  " << Qt::endl << Qt::endl;
    
    writeTableHeader(stream, DocumentationWriter::DEFAULT_FILE_BUILDER_HEADERS);

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

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeFiles()
//-----------------------------------------------------------------------------
void MarkdownWriter::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);

    writeTableHeader(stream, DocumentationWriter::FILE_HEADERS);

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

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeSingleFile()
//-----------------------------------------------------------------------------
void MarkdownWriter::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()
        << "[" + fileInfo.fileName() + "]" + "(" + pathFromDocToFile + ")"
        << file->getLogicalName()
        << (buildCommand ? buildCommand->getCommand() : QStringLiteral(""))
        << (buildCommand ? buildCommand->getFlags() : QStringLiteral(""))
        << fileTypes.join(",<br>")
        << file->getDescription()
    );

    writeTableRow(stream, fileTableCells);
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::writeImplementationDetails()
//-----------------------------------------------------------------------------
void MarkdownWriter::writeImplementationDetails(QTextStream& stream,
    QSharedPointer<ComponentInstantiation> instantiation)
{
    if (QString language = instantiation->getLanguage(); !language.isEmpty())
    {
        stream << "**Language:** " << language;

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

        stream << "  " << Qt::endl << Qt::endl;
    }
    
    if (QString library = instantiation->getLibraryName(); !library.isEmpty())
    {
        stream << "**Library:** " << library << "  " << Qt::endl << Qt::endl;
    }

    if (QString package = instantiation->getPackageName(); !package.isEmpty())
    {
        stream << "**Package:** " << package << "  " << Qt::endl << Qt::endl;
    }

    if (QString module = instantiation->getModuleName(); !module.isEmpty())
    {
        stream << "**Module name:** " << module << "  " << Qt::endl << Qt::endl;
    }

    if (QString architecture = instantiation->getArchitectureName(); !architecture.isEmpty())
    {
        stream << "**Architecture:** " << architecture << "  " << Qt::endl << Qt::endl;
    }

    if (QString configuration = instantiation->getConfigurationName(); !configuration.isEmpty())
    {
        stream << "**Configuration:** " << configuration << "  " << Qt::endl << Qt::endl;
    }
}

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

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

    stream << "**File sets containend in this instantiation:**  " << Qt::endl;

    for (auto const& fileSetRef : *fileSetRefs)
    {
        stream << "- [" << fileSetRef->getReference() << "](#" << vlnvString_ << ".fileSet." << 
            fileSetRef->getReference() << ")  " << Qt::endl;
    }

    stream << Qt::endl;
}

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

    stream << "**Default file build commands:**  " << Qt::endl << Qt::endl;

    writeTableHeader(stream, DocumentationWriter::DEFAULT_FILE_BUILDER_HEADERS);

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

        writeTableRow(stream, builderCells);
    }
}

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

    stream << "**" << tableHeading << "**  " << Qt::endl << Qt::endl;

    writeTableHeader(stream, DocumentationWriter::PARAMETER_HEADERS);

    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);
    }

    stream << Qt::endl;
}

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

    writeTableHeader(stream, DocumentationWriter::MODULE_PARAMETER_HEADERS);

    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);
    }

    stream << Qt::endl;
}

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

    stream << "Configurable element values:  " << Qt::endl << Qt::endl;

    QStringList paramHeaders({ QStringLiteral("Name"), QStringLiteral("Value") });
    writeTableHeader(stream, paramHeaders);

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

    stream << Qt::endl;
}

//-----------------------------------------------------------------------------
// Function: MarkdownWriter::getComponentInstanceConfigurableElements()
//-----------------------------------------------------------------------------
QString MarkdownWriter::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("<span style=\"color: red\">Unknown</span>"));
        }
        else
        {
            cell.append(configurableElementName);
        }

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

    return cell;
}
