/*-------------------------------------------------------------------------
 * drawElements Quality Program Tester Core
 * ----------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Test Log C++ Wrapper.
 *//*--------------------------------------------------------------------*/

#include "deCommandLine.h"
#include "tcuTestLog.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuSurface.hpp"
#include "deMath.h"

#include <limits>

namespace tcu
{

class LogWriteFailedError : public ResourceError
{
public:
    LogWriteFailedError(void) : ResourceError("Writing to test log failed")
    {
    }
};

enum
{
    MAX_IMAGE_SIZE_2D = 4096,
    MAX_IMAGE_SIZE_3D = 128
};

// LogImage

LogImage::LogImage(const std::string &name, const std::string &description, const Surface &surface,
                   qpImageCompressionMode compression)
    : m_name(name)
    , m_description(description)
    , m_access(surface.getAccess())
    , m_scale(1.0f, 1.0f, 1.0f, 1.0f)
    , m_bias(0.0f, 0.0f, 0.0f, 0.0f)
    , m_compression(compression)
{
}

LogImage::LogImage(const std::string &name, const std::string &description, const ConstPixelBufferAccess &access,
                   qpImageCompressionMode compression)
    : m_name(name)
    , m_description(description)
    , m_access(access)
    , m_scale(1.0f, 1.0f, 1.0f, 1.0f)
    , m_bias(0.0f, 0.0f, 0.0f, 0.0f)
    , m_compression(compression)
{
    // Simplify combined formats that only use a single channel
    if (tcu::isCombinedDepthStencilType(m_access.getFormat().type))
    {
        if (m_access.getFormat().order == tcu::TextureFormat::D)
            m_access = tcu::getEffectiveDepthStencilAccess(m_access, tcu::Sampler::MODE_DEPTH);
        else if (m_access.getFormat().order == tcu::TextureFormat::S)
            m_access = tcu::getEffectiveDepthStencilAccess(m_access, tcu::Sampler::MODE_STENCIL);
    }

    // Implicit scale and bias
    if (m_access.getFormat().order != tcu::TextureFormat::DS)
        computePixelScaleBias(m_access, m_scale, m_bias);
    else
    {
        // Pack D and S bias and scale to R and G
        const ConstPixelBufferAccess depthAccess =
            tcu::getEffectiveDepthStencilAccess(m_access, tcu::Sampler::MODE_DEPTH);
        const ConstPixelBufferAccess stencilAccess =
            tcu::getEffectiveDepthStencilAccess(m_access, tcu::Sampler::MODE_STENCIL);
        tcu::Vec4 depthScale;
        tcu::Vec4 depthBias;
        tcu::Vec4 stencilScale;
        tcu::Vec4 stencilBias;

        computePixelScaleBias(depthAccess, depthScale, depthBias);
        computePixelScaleBias(stencilAccess, stencilScale, stencilBias);

        m_scale = tcu::Vec4(depthScale.x(), stencilScale.x(), 0.0f, 0.0f);
        m_bias  = tcu::Vec4(depthBias.x(), stencilBias.x(), 0.0f, 0.0f);
    }
}

LogImage::LogImage(const std::string &name, const std::string &description, const ConstPixelBufferAccess &access,
                   const Vec4 &scale, const Vec4 &bias, qpImageCompressionMode compression)
    : m_name(name)
    , m_description(description)
    , m_access(access)
    , m_scale(scale)
    , m_bias(bias)
    , m_compression(compression)
{
    // Cannot set scale and bias of combined formats
    DE_ASSERT(access.getFormat().order != tcu::TextureFormat::DS);

    // Simplify access
    if (tcu::isCombinedDepthStencilType(access.getFormat().type))
    {
        if (access.getFormat().order == tcu::TextureFormat::D)
            m_access = tcu::getEffectiveDepthStencilAccess(access, tcu::Sampler::MODE_DEPTH);
        if (access.getFormat().order == tcu::TextureFormat::S)
            m_access = tcu::getEffectiveDepthStencilAccess(access, tcu::Sampler::MODE_STENCIL);
        else
        {
            // Cannot log a DS format
            DE_ASSERT(false);
            return;
        }
    }
}

void LogImage::write(TestLog &log) const
{
    if (m_access.getFormat().order != tcu::TextureFormat::DS)
        log.writeImage(m_name.c_str(), m_description.c_str(), m_access, m_scale, m_bias, m_compression);
    else
    {
        const ConstPixelBufferAccess depthAccess =
            tcu::getEffectiveDepthStencilAccess(m_access, tcu::Sampler::MODE_DEPTH);
        const ConstPixelBufferAccess stencilAccess =
            tcu::getEffectiveDepthStencilAccess(m_access, tcu::Sampler::MODE_STENCIL);

        log.startImageSet(m_name.c_str(), m_description.c_str());
        log.writeImage("Depth", "Depth channel", depthAccess, m_scale.swizzle(0, 0, 0, 0), m_bias.swizzle(0, 0, 0, 0),
                       m_compression);
        log.writeImage("Stencil", "Stencil channel", stencilAccess, m_scale.swizzle(1, 1, 1, 1),
                       m_bias.swizzle(1, 1, 1, 1), m_compression);
        log.endImageSet();
    }
}

// MessageBuilder

MessageBuilder::MessageBuilder(const MessageBuilder &other) : m_log(other.m_log)
{
    m_str.str(other.m_str.str());
}

MessageBuilder &MessageBuilder::operator=(const MessageBuilder &other)
{
    m_log = other.m_log;
    m_str.str(other.m_str.str());
    return *this;
}

TestLog &MessageBuilder::operator<<(const TestLog::EndMessageToken &)
{
    m_log->writeMessage(m_str.str().c_str());
    return *m_log;
}

// SampleBuilder

TestLog &SampleBuilder::operator<<(const TestLog::EndSampleToken &)
{
    m_log->startSample();

    for (std::vector<Value>::const_iterator val = m_values.begin(); val != m_values.end(); ++val)
    {
        if (val->type == Value::TYPE_FLOAT64)
            m_log->writeSampleValue(val->value.float64);
        else if (val->type == Value::TYPE_INT64)
            m_log->writeSampleValue(val->value.int64);
        else
            DE_ASSERT(false);
    }

    m_log->endSample();

    return *m_log;
}

// TestLog

TestLog::TestLog(const char *fileName, uint32_t flags)
    : m_log(qpTestLog_createFileLog(fileName, flags))
    , m_logSupressed(false)
{
    if (!m_log)
        throw ResourceError(std::string("Failed to open test log file '") + fileName + "'");
}

void TestLog::writeSessionInfo(std::string additionalInfo)
{
    qpTestLog_beginSession(m_log, additionalInfo.c_str());
}

TestLog::~TestLog(void)
{
    qpTestLog_destroy(m_log);
}

void TestLog::writeMessage(const char *msgStr)
{
    if (m_logSupressed)
        return;
    if (m_skipAdditionalDataInLog)
        return;
    if (qpTestLog_writeText(m_log, DE_NULL, DE_NULL, QP_KEY_TAG_NONE, msgStr) == false)
        throw LogWriteFailedError();
}

void TestLog::startImageSet(const char *name, const char *description)
{
    if (m_logSupressed)
        return;
    if (m_skipAdditionalDataInLog)
        return;
    if (qpTestLog_startImageSet(m_log, name, description) == false)
        throw LogWriteFailedError();
}

void TestLog::endImageSet(void)
{
    if (m_logSupressed)
        return;
    if (m_skipAdditionalDataInLog)
        return;
    if (qpTestLog_endImageSet(m_log) == false)
        throw LogWriteFailedError();
}

template <int Size>
static Vector<int, Size> computeScaledSize(const Vector<int, Size> &imageSize, int maxSize)
{
    bool allInRange = true;
    for (int i = 0; i < Size; i++)
        allInRange = allInRange && (imageSize[i] <= maxSize);

    if (allInRange)
        return imageSize;
    else
    {
        float d = 1.0f;
        for (int i = 0; i < Size; i++)
            d = de::max(d, (float)imageSize[i] / (float)maxSize);

        Vector<int, Size> res;
        for (int i = 0; i < Size; i++)
            res[i] = de::max(1, deRoundFloatToInt32((float)imageSize[i] / d));

        return res;
    }
}

void TestLog::writeImage(const char *name, const char *description, const ConstPixelBufferAccess &access,
                         const Vec4 &pixelScale, const Vec4 &pixelBias, qpImageCompressionMode compressionMode)
{
    if (m_logSupressed)
        return;
    if (m_skipAdditionalDataInLog)
        return;
    const TextureFormat &format = access.getFormat();
    int width                   = access.getWidth();
    int height                  = access.getHeight();
    int depth                   = access.getDepth();

    // Writing a combined image does not make sense
    DE_ASSERT(!tcu::isCombinedDepthStencilType(access.getFormat().type));

    // Do not bother with preprocessing if images are not stored
    if ((qpTestLog_getLogFlags(m_log) & QP_TEST_LOG_EXCLUDE_IMAGES) != 0)
        return;

    if (depth == 1 && (format.type == TextureFormat::UNORM_INT8 || format.type == TextureFormat::UNSIGNED_INT8) &&
        width <= MAX_IMAGE_SIZE_2D && height <= MAX_IMAGE_SIZE_2D &&
        (format.order == TextureFormat::RGB || format.order == TextureFormat::RGBA) &&
        access.getPixelPitch() == access.getFormat().getPixelSize() && pixelBias[0] == 0.0f && pixelBias[1] == 0.0f &&
        pixelBias[2] == 0.0f && pixelBias[3] == 0.0f && pixelScale[0] == 1.0f && pixelScale[1] == 1.0f &&
        pixelScale[2] == 1.0f && pixelScale[3] == 1.0f)
    {
        // Fast-path.
        bool isRGBA = format.order == TextureFormat::RGBA;

        writeImage(name, description, compressionMode, isRGBA ? QP_IMAGE_FORMAT_RGBA8888 : QP_IMAGE_FORMAT_RGB888,
                   width, height, access.getRowPitch(), access.getDataPtr());
    }
    else if (depth == 1)
    {
        Sampler sampler(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::LINEAR,
                        Sampler::NEAREST);
        IVec2 logImageSize = computeScaledSize(IVec2(width, height), MAX_IMAGE_SIZE_2D);
        tcu::TextureLevel logImage(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8), logImageSize.x(),
                                   logImageSize.y(), 1);
        PixelBufferAccess logImageAccess = logImage.getAccess();
        std::ostringstream longDesc;

        longDesc << description << " (p' = p * " << pixelScale << " + " << pixelBias << ")";

        for (int y = 0; y < logImage.getHeight(); y++)
        {
            for (int x = 0; x < logImage.getWidth(); x++)
            {
                float yf = ((float)y + 0.5f) / (float)logImage.getHeight();
                float xf = ((float)x + 0.5f) / (float)logImage.getWidth();
                Vec4 s   = access.sample2D(sampler, sampler.minFilter, xf, yf, 0) * pixelScale + pixelBias;

                logImageAccess.setPixel(s, x, y);
            }
        }

        writeImage(name, longDesc.str().c_str(), compressionMode, QP_IMAGE_FORMAT_RGBA8888, logImageAccess.getWidth(),
                   logImageAccess.getHeight(), logImageAccess.getRowPitch(), logImageAccess.getDataPtr());
    }
    else
    {
        // Isometric splat volume rendering.
        const float blendFactor = 0.85f;
        IVec3 scaledSize        = computeScaledSize(IVec3(width, height, depth), MAX_IMAGE_SIZE_3D);
        int w                   = scaledSize.x();
        int h                   = scaledSize.y();
        int d                   = scaledSize.z();
        int logImageW           = w + d - 1;
        int logImageH           = w + d + h;
        std::vector<float> blendImage(logImageW * logImageH * 4, 0.0f);
        PixelBufferAccess blendImageAccess(TextureFormat(TextureFormat::RGBA, TextureFormat::FLOAT), logImageW,
                                           logImageH, 1, &blendImage[0]);
        tcu::TextureLevel logImage(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8), logImageW, logImageH,
                                   1);
        PixelBufferAccess logImageAccess = logImage.getAccess();
        Sampler sampler(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::NEAREST,
                        Sampler::NEAREST);
        std::ostringstream longDesc;

        // \note Back-to-front.
        for (int z = d - 1; z >= 0; z--)
        {
            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < w; x++)
                {
                    int px = w - (x + 1) + z;
                    int py = (w + d + h) - (x + y + z + 1);

                    float xf = ((float)x + 0.5f) / (float)w;
                    float yf = ((float)y + 0.5f) / (float)h;
                    float zf = ((float)z + 0.5f) / (float)d;

                    Vec4 p = blendImageAccess.getPixel(px, py);
                    Vec4 s = access.sample3D(sampler, sampler.minFilter, xf, yf, zf);
                    Vec4 b = s + p * blendFactor;

                    blendImageAccess.setPixel(b, px, py);
                }
            }
        }

        // Scale blend image nicely.
        longDesc << description << " (p' = p * " << pixelScale << " + " << pixelBias << ")";

        // Write to final image.
        tcu::clear(logImageAccess, tcu::IVec4(0x33, 0x66, 0x99, 0xff));

        for (int z = 0; z < d; z++)
        {
            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < w; x++)
                {
                    if (z != 0 && !(x == 0 || y == h - 1 || y == h - 2))
                        continue;

                    int px = w - (x + 1) + z;
                    int py = (w + d + h) - (x + y + z + 1);
                    Vec4 s = blendImageAccess.getPixel(px, py) * pixelScale + pixelBias;

                    logImageAccess.setPixel(s, px, py);
                }
            }
        }

        writeImage(name, longDesc.str().c_str(), compressionMode, QP_IMAGE_FORMAT_RGBA8888, logImageAccess.getWidth(),
                   logImageAccess.getHeight(), logImageAccess.getRowPitch(), logImageAccess.getDataPtr());
    }
}

void TestLog::writeImage(const char *name, const char *description, qpImageCompressionMode compressionMode,
                         qpImageFormat format, int width, int height, int stride, const void *data)
{
    if (m_logSupressed)
        return;
    if (m_skipAdditionalDataInLog)
        return;
    if (qpTestLog_writeImage(m_log, name, description, compressionMode, format, width, height, stride, data) == false)
        throw LogWriteFailedError();
}

void TestLog::startSection(const char *name, const char *description)
{
    if (m_logSupressed)
        return;
    if (m_skipAdditionalDataInLog)
        return;
    if (qpTestLog_startSection(m_log, name, description) == false)
        throw LogWriteFailedError();
}

void TestLog::endSection(void)
{
    if (m_logSupressed)
        return;
    if (m_skipAdditionalDataInLog)
        return;
    if (qpTestLog_endSection(m_log) == false)
        throw LogWriteFailedError();
}

void TestLog::startShaderProgram(bool linkOk, const char *linkInfoLog)
{
    if (m_logSupressed)
        return;
    if (m_skipAdditionalDataInLog)
        return;
    if (qpTestLog_startShaderProgram(m_log, linkOk ? true : false, linkInfoLog) == false)
        throw LogWriteFailedError();
}

void TestLog::endShaderProgram(void)
{
    if (m_logSupressed)
        return;
    if (m_skipAdditionalDataInLog)
        return;
    if (qpTestLog_endShaderProgram(m_log) == false)
        throw LogWriteFailedError();
}

void TestLog::writeShader(qpShaderType type, const char *source, bool compileOk, const char *infoLog)
{
    if (m_logSupressed)
        return;
    if (m_skipAdditionalDataInLog)
        return;
    if (qpTestLog_writeShader(m_log, type, source, compileOk ? true : false, infoLog) == false)
        throw LogWriteFailedError();
}

void TestLog::writeSpirVAssemblySource(const char *source)
{
    if (m_logSupressed)
        return;
    if (m_skipAdditionalDataInLog)
        return;
    if (qpTestLog_writeSpirVAssemblySource(m_log, source) == false)
        throw LogWriteFailedError();
}

void TestLog::writeKernelSource(const char *source)
{
    if (m_logSupressed)
        return;
    if (m_skipAdditionalDataInLog)
        return;
    if (qpTestLog_writeKernelSource(m_log, source) == false)
        throw LogWriteFailedError();
}

void TestLog::writeCompileInfo(const char *name, const char *description, bool compileOk, const char *infoLog)
{
    if (m_logSupressed)
        return;
    if (m_skipAdditionalDataInLog)
        return;
    if (qpTestLog_writeCompileInfo(m_log, name, description, compileOk ? true : false, infoLog) == false)
        throw LogWriteFailedError();
}

void TestLog::writeFloat(const char *name, const char *description, const char *unit, qpKeyValueTag tag, float value)
{
    if (m_logSupressed)
        return;
    if (m_skipAdditionalDataInLog)
        return;
    if (qpTestLog_writeFloat(m_log, name, description, unit, tag, value) == false)
        throw LogWriteFailedError();
}

void TestLog::writeInteger(const char *name, const char *description, const char *unit, qpKeyValueTag tag,
                           int64_t value)
{
    if (m_logSupressed)
        return;
    if (m_skipAdditionalDataInLog)
        return;
    if (qpTestLog_writeInteger(m_log, name, description, unit, tag, value) == false)
        throw LogWriteFailedError();
}

void TestLog::startEglConfigSet(const char *name, const char *description)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_startEglConfigSet(m_log, name, description) == false)
        throw LogWriteFailedError();
}

void TestLog::writeEglConfig(const qpEglConfigInfo *config)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_writeEglConfig(m_log, config) == false)
        throw LogWriteFailedError();
}

void TestLog::endEglConfigSet(void)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_endEglConfigSet(m_log) == false)
        throw LogWriteFailedError();
}

void TestLog::startCase(const char *testCasePath, qpTestCaseType testCaseType)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_startCase(m_log, testCasePath, testCaseType) == false)
        throw LogWriteFailedError();
    // Check if the test is one of those we want to print fully in the log
    m_skipAdditionalDataInLog = false;
    if (qpTestLog_isCompact(m_log))
    {
        const std::string testCasePathStr = testCasePath;
        if (testCasePathStr.rfind("dEQP-VK.info.") != 0 && testCasePathStr.rfind("dEQP-VK.api.info.") != 0 &&
            testCasePathStr.rfind("dEQP-VK.api.version_check.") != 0)
        {
            // We can skip writing text, numbers, imagesets, etc.
            m_skipAdditionalDataInLog = true;
        }
    }
}

void TestLog::endCase(qpTestResult result, const char *description)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_endCase(m_log, result, description) == false)
        throw LogWriteFailedError();
}

void TestLog::terminateCase(qpTestResult result)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_terminateCase(m_log, result) == false)
        throw LogWriteFailedError();
}

void TestLog::startTestsCasesTime(void)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_startTestsCasesTime(m_log) == false)
        throw LogWriteFailedError();
}

void TestLog::endTestsCasesTime(void)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_endTestsCasesTime(m_log) == false)
        throw LogWriteFailedError();
}

void TestLog::startSampleList(const std::string &name, const std::string &description)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_startSampleList(m_log, name.c_str(), description.c_str()) == false)
        throw LogWriteFailedError();
}

void TestLog::startSampleInfo(void)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_startSampleInfo(m_log) == false)
        throw LogWriteFailedError();
}

void TestLog::writeValueInfo(const std::string &name, const std::string &description, const std::string &unit,
                             qpSampleValueTag tag)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_writeValueInfo(m_log, name.c_str(), description.c_str(), unit.empty() ? DE_NULL : unit.c_str(),
                                 tag) == false)
        throw LogWriteFailedError();
}

void TestLog::endSampleInfo(void)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_endSampleInfo(m_log) == false)
        throw LogWriteFailedError();
}

void TestLog::startSample(void)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_startSample(m_log) == false)
        throw LogWriteFailedError();
}

void TestLog::writeSampleValue(double value)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_writeValueFloat(m_log, value) == false)
        throw LogWriteFailedError();
}

void TestLog::writeSampleValue(int64_t value)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_writeValueInteger(m_log, value) == false)
        throw LogWriteFailedError();
}

void TestLog::endSample(void)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_endSample(m_log) == false)
        throw LogWriteFailedError();
}

void TestLog::endSampleList(void)
{
    if (m_logSupressed)
        return;
    if (qpTestLog_endSampleList(m_log) == false)
        throw LogWriteFailedError();
}

void TestLog::writeRaw(const char *rawContents)
{
    if (m_logSupressed)
        return;
    qpTestLog_writeRaw(m_log, rawContents);
}

bool TestLog::isShaderLoggingEnabled(void)
{
    return (qpTestLog_getLogFlags(m_log) & QP_TEST_LOG_EXCLUDE_SHADER_SOURCES) == 0;
}

void TestLog::supressLogging(bool value)
{
    m_logSupressed = value;
}

bool TestLog::isSupressLogging(void)
{
    return m_logSupressed;
}

const TestLog::BeginMessageToken TestLog::Message              = TestLog::BeginMessageToken();
const TestLog::EndMessageToken TestLog::EndMessage             = TestLog::EndMessageToken();
const TestLog::EndImageSetToken TestLog::EndImageSet           = TestLog::EndImageSetToken();
const TestLog::EndSectionToken TestLog::EndSection             = TestLog::EndSectionToken();
const TestLog::EndShaderProgramToken TestLog::EndShaderProgram = TestLog::EndShaderProgramToken();
const TestLog::SampleInfoToken TestLog::SampleInfo             = TestLog::SampleInfoToken();
const TestLog::EndSampleInfoToken TestLog::EndSampleInfo       = TestLog::EndSampleInfoToken();
const TestLog::BeginSampleToken TestLog::Sample                = TestLog::BeginSampleToken();
const TestLog::EndSampleToken TestLog::EndSample               = TestLog::EndSampleToken();
const TestLog::EndSampleListToken TestLog::EndSampleList       = TestLog::EndSampleListToken();

} // namespace tcu
