// Copyright 2021 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "src/dawn/native/CompilationMessages.h"

#include <utility>

#include "src/dawn/common/Assert.h"
#include "src/dawn/common/StringViewUtils.h"
#include "src/dawn/native/dawn_platform.h"
#include "src/utils/compiler.h"
#include "tint/tint.h"

namespace dawn::native {

namespace {

wgpu::CompilationMessageType TintSeverityToMessageType(tint::diag::Severity severity) {
    switch (severity) {
        case tint::diag::Severity::Note:
            return wgpu::CompilationMessageType::Info;
        case tint::diag::Severity::Warning:
            return wgpu::CompilationMessageType::Warning;
        default:
            return wgpu::CompilationMessageType::Error;
    }
}

}  // anonymous namespace

uint64_t CountUTF16CodeUnitsFromUTF8String(const std::string_view& utf8String) {
    if (tint::utf8::IsASCII(utf8String)) {
        return utf8String.size();
    }

    uint64_t numberOfUTF16CodeUnits = 0;
    std::string_view remaining = utf8String;
    while (!remaining.empty()) {
        auto [codePoint, utf8CharacterByteLength] = tint::utf8::Decode(remaining);
        // WGSL validation will already have prevented invalid UTF-8.
        DAWN_ASSERT(utf8CharacterByteLength != 0);
        remaining = remaining.substr(utf8CharacterByteLength);

        // Count the number of code units in UTF-16. See https://en.wikipedia.org/wiki/UTF-16 for
        // more details. Note that while the code points ranging from U+D800 to U+DFFF are not
        // encodable in UTF-16 (they are surrogate code points), we still mark them as needing a
        // single UTF-16 code unit. The computations performed in this function are only useful when
        // the application uses UTF-16 internally (like JS code), so the source would have been
        // valid UTF-16 already.
        if (codePoint.value <= 0xFFFF) {
            // Code points from U+0000 to U+D7FF and U+E000 to U+FFFF are encoded as single 16-bit
            // code units. As noted above we also count surrogates as one UTF-16 code unit.
            numberOfUTF16CodeUnits += 1;
        } else {
            // Code points from U+010000 to U+10FFFF are encoded as two 16-bit code units.
            numberOfUTF16CodeUnits += 2;
        }
    }

    return numberOfUTF16CodeUnits;
}

void ParsedCompilationMessages::AddUnanchoredMessage(std::string_view message,
                                                     wgpu::CompilationMessageType type) {
    CompilationMessageContent m = {};
    m.message = message;
    m.type = type;
    AddMessage(std::move(m));
}

void ParsedCompilationMessages::AddMessageForTesting(std::string_view message,
                                                     wgpu::CompilationMessageType type,
                                                     uint64_t lineNum,
                                                     uint64_t linePos,
                                                     uint64_t offset,
                                                     uint64_t length) {
    AddMessage({{
        .message = std::string(message),
        .type = type,
        .lineNum = lineNum,
        .linePosInBytes = linePos,
        .offsetInBytes = offset,
        .lengthInBytes = length,
        // Incorrect for non-ACSII strings
        .linePosInUTF16 = linePos,
        .offsetInUTF16 = offset,
        .lengthInUTF16 = length,
    }});
}

void ParsedCompilationMessages::AddMessage(const tint::diag::Diagnostic& diagnostic) {
    // Tint line and column values are 1-based.
    uint64_t lineNum = diagnostic.source.range.begin.line;
    uint64_t linePosInBytes = diagnostic.source.range.begin.column;
    // The offset is 0-based.
    uint64_t offsetInBytes = 0;
    uint64_t lengthInBytes = 0;
    uint64_t linePosInUTF16 = 0;
    uint64_t offsetInUTF16 = 0;
    uint64_t lengthInUTF16 = 0;

    if (lineNum && linePosInBytes && diagnostic.source.file) {
        const tint::Source::FileContent& content = diagnostic.source.file->content;

        // Tint stores line as std::string_view in a complete source std::string that's in the
        // source file. So to get the offset in bytes of a line we just need to substract its start
        // pointer with the start of the file's content. Note that line numbering in Tint source
        // range starts at 1 while the array of lines start at 0 (hence the -1).
        const char* fileStart = content.data.data();
        const char* lineStart = content.lines[lineNum - 1].data();
        offsetInBytes = static_cast<uint64_t>(lineStart - fileStart) + linePosInBytes - 1;

        // The linePosInBytes is 1-based.
        uint64_t linePosOffsetInUTF16 =
            CountUTF16CodeUnitsFromUTF8String(std::string_view(lineStart, linePosInBytes - 1));
        linePosInUTF16 = linePosOffsetInUTF16 + 1;

        // The offset is 0-based.
        uint64_t lineStartToFileStartOffsetInUTF16 = CountUTF16CodeUnitsFromUTF8String(
            std::string_view(fileStart, static_cast<uint64_t>(lineStart - fileStart)));
        offsetInUTF16 = lineStartToFileStartOffsetInUTF16 + linePosInUTF16 - 1;

        // If the range has a valid start but the end is not specified, clamp it to the start.
        uint64_t endLineNum = diagnostic.source.range.end.line;
        uint64_t endLineCol = diagnostic.source.range.end.column;
        if (endLineNum == 0 || endLineCol == 0) {
            endLineNum = lineNum;
            endLineCol = linePosInBytes;
        }

        const char* endLineStart = content.lines[endLineNum - 1].data();
        uint64_t endOffsetInBytes =
            static_cast<uint64_t>(endLineStart - fileStart) + endLineCol - 1;
        // The length of the message is the difference between the starting offset and the
        // ending offset. Negative ranges aren't allowed.
        DAWN_ASSERT(endOffsetInBytes >= offsetInBytes);
        lengthInBytes = endOffsetInBytes - offsetInBytes;
        lengthInUTF16 = CountUTF16CodeUnitsFromUTF8String(
            std::string_view(DAWN_UNSAFE_TODO(fileStart + offsetInBytes), lengthInBytes));
    }

    std::string plainMessage = diagnostic.message.Plain();

    AddMessage({{
        .message = plainMessage,
        .type = TintSeverityToMessageType(diagnostic.severity),
        .lineNum = lineNum,
        .linePosInBytes = linePosInBytes,
        .offsetInBytes = offsetInBytes,
        .lengthInBytes = lengthInBytes,
        .linePosInUTF16 = linePosInUTF16,
        .offsetInUTF16 = offsetInUTF16,
        .lengthInUTF16 = lengthInUTF16,
    }});
}

void ParsedCompilationMessages::AddMessage(CompilationMessageContent&& message) {
    messages.push_back(message);
}

void ParsedCompilationMessages::AddMessages(const tint::diag::List& diagnostics) {
    for (const auto& diag : diagnostics) {
        AddMessage(diag);
    }

    AddFormattedTintMessages(diagnostics);
}

void ParsedCompilationMessages::AddFormattedTintMessages(const tint::diag::List& diagnostics) {
    tint::diag::List messageList;
    size_t warningCount = 0;
    size_t errorCount = 0;
    for (auto& diag : diagnostics) {
        switch (diag.severity) {
            case tint::diag::Severity::Error: {
                errorCount++;
                messageList.Add(diag);
                break;
            }
            case tint::diag::Severity::Warning: {
                warningCount++;
                messageList.Add(diag);
                break;
            }
            case tint::diag::Severity::Note: {
                messageList.Add(diag);
                break;
            }
            default:
                break;
        }
    }
    if (errorCount == 0 && warningCount == 0) {
        return;
    }
    tint::diag::Formatter::Style style;
    style.print_newline_at_end = false;
    std::ostringstream t;
    if (errorCount > 0) {
        t << errorCount << " error(s) ";
        if (warningCount > 0) {
            t << "and ";
        }
    }
    if (warningCount > 0) {
        t << warningCount << " warning(s) ";
    }
    t << "generated while compiling the shader:\n"
      << tint::diag::Formatter{style}.Format(messageList).Plain();
    formattedTintMessages.push_back(t.str());
}

OwnedCompilationMessages::OwnedCompilationMessages(
    ParsedCompilationMessages&& parsedCompilationMessagges)
    : mMessageContents(parsedCompilationMessagges), mCompilationInfo() {
    // Reserve the size of messages to avoid reallocations, so that we can use
    // the raw pointer when adding each message.
    mMessagesList.reserve(mMessageContents.messages.size());
    mUtf16Messages.reserve(mMessageContents.messages.size());

    // Build the CompilationMessage and DawnCompilationMessageUtf16 object for each message.
    for (auto& m : mMessageContents.messages) {
        if (m.type == wgpu::CompilationMessageType::Error ||
            m.type == wgpu::CompilationMessageType::Warning) {
            mHasWarningsOrErrors = true;
        }

        DawnCompilationMessageUtf16& utf16 = mUtf16Messages.emplace_back();
        utf16.linePos = m.linePosInUTF16;
        utf16.offset = m.offsetInUTF16;
        utf16.length = m.lengthInUTF16;

        CompilationMessage& message = mMessagesList.emplace_back();
        message.message = ToOutputStringView(m.message);
        message.type = m.type;
        message.lineNum = m.lineNum;
        message.linePos = m.linePosInBytes;
        message.offset = m.offsetInBytes;
        message.length = m.lengthInBytes;
        // Points to the created DawnCompilationMessageUtf16, pointers would keep valid since
        // mUtf16Messages has been reserved to the size of messages.
        message.nextInChain = &utf16;
    }

    mCompilationInfo.messageCount = mMessagesList.size();
    mCompilationInfo.messages = mMessagesList.data();
}

const CompilationInfo* OwnedCompilationMessages::GetCompilationInfo() const {
    return &mCompilationInfo;
}

const std::vector<std::string>& OwnedCompilationMessages::GetFormattedTintMessages() const {
    return mMessageContents.formattedTintMessages;
}

bool OwnedCompilationMessages::HasWarningsOrErrors() const {
    return mHasWarningsOrErrors;
}

}  // namespace dawn::native
