/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#include "cmDocumentationFormatter.h"

#include "cmDocumentationEntry.h"
#include "cmDocumentationSection.h"

#include <ostream>
#include <string.h>
#include <string>
#include <vector>

cmDocumentationFormatter::cmDocumentationFormatter()
: TextWidth(77)
, TextIndent("")
{}

cmDocumentationFormatter::~cmDocumentationFormatter() {}

void
cmDocumentationFormatter::PrintFormatted(std::ostream& os, const char* text)
{
    if(!text)
    {
        return;
    }
    const char* ptr = text;
    while(*ptr)
    {
        // Any ptrs starting in a space are treated as preformatted text.
        std::string preformatted;
        while(*ptr == ' ')
        {
            for(char ch = *ptr; ch && ch != '\n'; ++ptr, ch = *ptr)
            {
                preformatted.append(1, ch);
            }
            if(*ptr)
            {
                ++ptr;
                preformatted.append(1, '\n');
            }
        }
        if(!preformatted.empty())
        {
            this->PrintPreformatted(os, preformatted.c_str());
        }

        // Other ptrs are treated as paragraphs.
        std::string paragraph;
        for(char ch = *ptr; ch && ch != '\n'; ++ptr, ch = *ptr)
        {
            paragraph.append(1, ch);
        }
        if(*ptr)
        {
            ++ptr;
            paragraph.append(1, '\n');
        }
        if(!paragraph.empty())
        {
            this->PrintParagraph(os, paragraph.c_str());
        }
    }
}

void
cmDocumentationFormatter::PrintPreformatted(std::ostream& os, const char* text)
{
    bool newline = true;
    for(const char* ptr = text; *ptr; ++ptr)
    {
        if(newline && *ptr != '\n')
        {
            os << this->TextIndent;
            newline = false;
        }
        os << *ptr;
        if(*ptr == '\n')
        {
            newline = true;
        }
    }
    os << "\n";
}

void
cmDocumentationFormatter::PrintParagraph(std::ostream& os, const char* text)
{
    os << this->TextIndent;
    this->PrintColumn(os, text);
    os << "\n";
}

void
cmDocumentationFormatter::SetIndent(const char* indent)
{
    this->TextIndent = indent;
}

void
cmDocumentationFormatter::PrintColumn(std::ostream& os, const char* text)
{
    // Print text arranged in an indented column of fixed width.
    const char* l           = text;
    long        column      = 0;
    bool        newSentence = false;
    bool        firstLine   = true;
    int width = this->TextWidth - static_cast<int>(strlen(this->TextIndent));

    // Loop until the end of the text.
    while(*l)
    {
        // Parse the next word.
        const char* r = l;
        while(*r && (*r != '\n') && (*r != ' '))
        {
            ++r;
        }

        // Does it fit on this line?
        if(r - l < (width - column - (newSentence ? 1 : 0)))
        {
            // Word fits on this line.
            if(r > l)
            {
                if(column)
                {
                    // Not first word on line.  Separate from the previous word
                    // by a space, or two if this is a new sentence.
                    if(newSentence)
                    {
                        os << "  ";
                        column += 2;
                    } else
                    {
                        os << " ";
                        column += 1;
                    }
                } else
                {
                    // First word on line.  Print indentation unless this is the
                    // first line.
                    os << (firstLine ? "" : this->TextIndent);
                }

                // Print the word.
                os.write(l, static_cast<long>(r - l));
                newSentence = (*(r - 1) == '.');
            }

            if(*r == '\n')
            {
                // Text provided a newline.  Start a new line.
                os << "\n";
                ++r;
                column    = 0;
                firstLine = false;
            } else
            {
                // No provided newline.  Continue this line.
                column += static_cast<long>(r - l);
            }
        } else
        {
            // Word does not fit on this line.  Start a new line.
            os << "\n";
            firstLine = false;
            if(r > l)
            {
                os << this->TextIndent;
                os.write(l, static_cast<long>(r - l));
                column      = static_cast<long>(r - l);
                newSentence = (*(r - 1) == '.');
            } else
            {
                column = 0;
            }
        }

        // Move to beginning of next word.  Skip over whitespace.
        l = r;
        while(*l == ' ')
        {
            ++l;
        }
    }
}

void
cmDocumentationFormatter::PrintSection(std::ostream&                 os,
                                       cmDocumentationSection const& section)
{
    os << section.GetName() << "\n";

    const std::vector<cmDocumentationEntry>& entries = section.GetEntries();
    for(cmDocumentationEntry const& entry : entries)
    {
        if(!entry.Name.empty())
        {
            os << "  " << entry.Name;
            this->TextIndent = "                                 ";
            int align        = static_cast<int>(strlen(this->TextIndent)) - 4;
            for(int i = static_cast<int>(entry.Name.size()); i < align; ++i)
            {
                os << " ";
            }
            if(entry.Name.size() > strlen(this->TextIndent) - 4)
            {
                os << "\n";
                os.write(this->TextIndent, strlen(this->TextIndent) - 2);
            }
            os << "= ";
            this->PrintColumn(os, entry.Brief.c_str());
            os << "\n";
        } else
        {
            os << "\n";
            this->TextIndent = "";
            this->PrintFormatted(os, entry.Brief.c_str());
        }
    }
    os << "\n";
}
