/*
 * Copyright (C) 2019 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.
 */

#pragma once

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
#pragma clang diagnostic ignored "-Wextra"

#include <chrono>

#include <android/native_window.h>
#include <binder/IPCThreadState.h>
#include <gtest/gtest.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <private/gui/ComposerService.h>
#include <ui/GraphicBuffer.h>
#include <ui/Rect.h>

#include "ColorUtils.h"

namespace android {

namespace {

using namespace std::chrono_literals;
using Transaction = SurfaceComposerClient::Transaction;

std::ostream& operator<<(std::ostream& os, const Color& color) {
    os << int(color.r) << ", " << int(color.g) << ", " << int(color.b) << ", " << int(color.a);
    return os;
}

class TransactionUtils {
public:
    // Fill a region with the specified color.
    static void fillANativeWindowBufferColor(const ANativeWindow_Buffer& buffer, const Rect& rect,
                                             const Color& color) {
        Rect r(0, 0, buffer.width, buffer.height);
        if (!r.intersect(rect, &r)) {
            return;
        }

        int32_t width = r.right - r.left;
        int32_t height = r.bottom - r.top;

        for (int32_t row = 0; row < height; row++) {
            uint8_t* dst = static_cast<uint8_t*>(buffer.bits) +
                    (buffer.stride * (r.top + row) + r.left) * 4;
            for (int32_t column = 0; column < width; column++) {
                dst[0] = color.r;
                dst[1] = color.g;
                dst[2] = color.b;
                dst[3] = color.a;
                dst += 4;
            }
        }
    }

    // Fill a region with the specified color.
    static void fillGraphicBufferColor(const sp<GraphicBuffer>& buffer, const Rect& rect,
                                       const Color& color) {
        Rect r(0, 0, buffer->width, buffer->height);
        if (!r.intersect(rect, &r)) {
            return;
        }

        int32_t width = r.right - r.left;
        int32_t height = r.bottom - r.top;

        uint8_t* pixels;
        buffer->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
                     reinterpret_cast<void**>(&pixels));

        for (int32_t row = 0; row < height; row++) {
            uint8_t* dst = pixels + (buffer->getStride() * (r.top + row) + r.left) * 4;
            for (int32_t column = 0; column < width; column++) {
                dst[0] = color.r;
                dst[1] = color.g;
                dst[2] = color.b;
                dst[3] = color.a;
                dst += 4;
            }
        }
        buffer->unlock();
    }

    // Check if a region has the specified color.
    static void expectBufferColor(const sp<GraphicBuffer>& outBuffer, uint8_t* pixels,
                                  const Rect& rect, const Color& color, uint8_t tolerance) {
        int32_t x = rect.left;
        int32_t y = rect.top;
        int32_t width = rect.right - rect.left;
        int32_t height = rect.bottom - rect.top;

        int32_t bufferWidth = int32_t(outBuffer->getWidth());
        int32_t bufferHeight = int32_t(outBuffer->getHeight());
        if (x + width > bufferWidth) {
            x = std::min(x, bufferWidth);
            width = bufferWidth - x;
        }
        if (y + height > bufferHeight) {
            y = std::min(y, bufferHeight);
            height = bufferHeight - y;
        }

        auto colorCompare = [tolerance](uint8_t a, uint8_t b) {
            uint8_t tmp = a >= b ? a - b : b - a;
            return tmp <= tolerance;
        };
        for (int32_t j = 0; j < height; j++) {
            const uint8_t* src = pixels + (outBuffer->getStride() * (y + j) + x) * 4;
            for (int32_t i = 0; i < width; i++) {
                const uint8_t expected[4] = {color.r, color.g, color.b, color.a};
                ASSERT_TRUE(std::equal(src, src + 4, expected, colorCompare))
                        << "pixel @ (" << x + i << ", " << y + j << "): "
                        << "expected (" << color << "), "
                        << "got (" << Color{src[0], src[1], src[2], src[3]} << ")";
                src += 4;
            }
        }
    }

    static void fillSurfaceRGBA8(const sp<SurfaceControl>& sc, const Color& color,
                                 bool unlock = true) {
        fillSurfaceRGBA8(sc, color.r, color.g, color.b, unlock);
    }

    // Fill an RGBA_8888 formatted surface with a single color.
    static void fillSurfaceRGBA8(const sp<SurfaceControl>& sc, uint8_t r, uint8_t g, uint8_t b,
                                 bool unlock = true) {
        ANativeWindow_Buffer outBuffer;
        sp<Surface> s = sc->getSurface();
        ASSERT_TRUE(s != nullptr);
        ASSERT_EQ(NO_ERROR, s->lock(&outBuffer, nullptr));
        uint8_t* img = reinterpret_cast<uint8_t*>(outBuffer.bits);
        for (int y = 0; y < outBuffer.height; y++) {
            for (int x = 0; x < outBuffer.width; x++) {
                uint8_t* pixel = img + (4 * (y * outBuffer.stride + x));
                pixel[0] = r;
                pixel[1] = g;
                pixel[2] = b;
                pixel[3] = 255;
            }
        }
        if (unlock) {
            ASSERT_EQ(NO_ERROR, s->unlockAndPost());
        }
    }

    static void setFrame(Transaction& t, const sp<SurfaceControl>& sc, Rect source, Rect dest,
                         int32_t transform = 0) {
        uint32_t sourceWidth = source.getWidth();
        uint32_t sourceHeight = source.getHeight();

        if (transform & ui::Transform::ROT_90) {
            std::swap(sourceWidth, sourceHeight);
        }

        float dsdx = dest.getWidth() / static_cast<float>(sourceWidth);
        float dsdy = dest.getHeight() / static_cast<float>(sourceHeight);

        t.setMatrix(sc, dsdx, 0, 0, dsdy);
        t.setPosition(sc, dest.left, dest.top);
    }
};

enum class RenderPath { SCREENSHOT, VIRTUAL_DISPLAY };

// Environment for starting up binder threads. This is required for testing
// virtual displays, as BufferQueue parameters may be queried over binder.
class BinderEnvironment : public ::testing::Environment {
public:
    void SetUp() override { ProcessState::self()->startThreadPool(); }
};

/** RAII Wrapper around get/seteuid */
class UIDFaker {
    uid_t oldId;

public:
    UIDFaker(uid_t uid) {
        oldId = geteuid();
        seteuid(uid);
    }
    ~UIDFaker() { seteuid(oldId); }
};
} // namespace
} // namespace android

// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"