//------------------------------------------------------------------------------
// SyntaxPrinter.cpp
// Support for printing syntax nodes and tokens
//
// File is under the MIT license; see LICENSE for details
//------------------------------------------------------------------------------
#include "slang/syntax/SyntaxPrinter.h"

#include "slang/parsing/Parser.h"
#include "slang/syntax/SyntaxTree.h"
#include "slang/text/SourceManager.h"

namespace slang {

SyntaxPrinter::SyntaxPrinter(const SourceManager& sourceManager) : sourceManager(&sourceManager) {
}

SyntaxPrinter& SyntaxPrinter::print(Trivia trivia) {
    switch (trivia.kind) {
        case TriviaKind::Directive:
            if (includeDirectives)
                print(*trivia.syntax());
            else if (includePreprocessed) {
                for (const auto& t : trivia.syntax()->getFirstToken().trivia())
                    print(t);
            }
            break;
        case TriviaKind::SkippedSyntax:
            if (includeSkipped)
                print(*trivia.syntax());
            break;
        case TriviaKind::SkippedTokens:
            if (includeSkipped) {
                for (Token t : trivia.getSkippedTokens())
                    print(t);
            }
            break;
        case TriviaKind::LineComment:
        case TriviaKind::BlockComment:
            if (!includeComments)
                break;
            [[fallthrough]];
        default:
            append(trivia.getRawText());
            break;
    }
    return *this;
}

SyntaxPrinter& SyntaxPrinter::print(Token token) {
    bool excluded = false;
    if (!includePreprocessed && sourceManager)
        excluded = sourceManager->isPreprocessedLoc(token.location());

    if (includeTrivia) {
        if (includePreprocessed || !sourceManager) {
            for (const auto& t : token.trivia())
                print(t);
        }
        else {
            // Exclude any trivia that is from a preprocessed location as well. In order
            // to know that we need to skip over any trivia that is implicitly located
            // relative to something ahead of it (a directive or the token itself).
            SmallVectorSized<const Trivia*, 8> pending;
            for (const auto& trivia : token.trivia()) {
                pending.append(&trivia);
                auto loc = trivia.getExplicitLocation();
                if (loc) {
                    if (!sourceManager->isPreprocessedLoc(*loc)) {
                        for (auto t : pending)
                            print(*t);
                    }
                    pending.clear();
                }
            }

            if (!excluded) {
                for (auto t : pending)
                    print(*t);
            }
        }
    }

    if (!excluded && (includeMissing || !token.isMissing()))
        append(token.rawText());

    return *this;
}

SyntaxPrinter& SyntaxPrinter::print(const SyntaxNode& node) {
    size_t childCount = node.getChildCount();
    for (size_t i = 0; i < childCount; i++) {
        if (auto childNode = node.childNode(i); childNode)
            print(*childNode);
        else if (auto token = node.childToken(i); token)
            print(token);
    }
    return *this;
}

SyntaxPrinter& SyntaxPrinter::print(const SyntaxTree& tree) {
    print(tree.root());
    if (tree.root().kind != SyntaxKind::CompilationUnit && tree.getMetadata().eofToken)
        print(tree.getMetadata().eofToken);
    return *this;
}

std::string SyntaxPrinter::printFile(const SyntaxTree& tree) {
    return SyntaxPrinter(tree.sourceManager())
        .setIncludeDirectives(true)
        .setIncludeSkipped(true)
        .setIncludeTrivia(true)
        .setIncludePreprocessed(false)
        .print(tree)
        .str();
}

void SyntaxPrinter::append(string_view text) {
    if (!squashNewlines) {
        buffer.append(text);
        return;
    }

    bool carriage = false;
    bool newline = false;

    if (!text.empty() && (text[0] == '\r' || text[0] == '\n')) {
        size_t i = 0;
        if (text[i] == '\r') {
            carriage = true;
            i++;
        }

        if (i < text.length() && text[i] == '\n') {
            newline = true;
            i++;
        }

        for (; i < text.length(); i++) {
            if (text[i] == '\r' || text[i] == '\n')
                i++;
        }

        text = text.substr(i);
    }

    if (buffer.empty() || buffer.back() != '\n') {
        if (carriage)
            buffer.push_back('\r');
        if (newline)
            buffer.push_back('\n');
    }

    buffer.append(text);
}

} // namespace slang
