/*
 * Copyright (c) 2021-2023 Huawei Device Co., Ltd.
 * 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.
 */

#include "hi_gbm.h"
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <fcntl.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <drm_fourcc.h>
#include <securec.h>
#include "display_log.h"
#include "hi_gbm_internal.h"

namespace OHOS {
namespace HDI {
namespace DISPLAY {
#ifdef ROCKCHIP_CMA
#define ROCKCHIP_BO_CONTIG (1 << 0)
#endif

using PlaneLayoutInfo = struct {
    uint32_t numPlanes;
    uint32_t radio[MAX_PLANES];
};

using FormatInfo = struct {
    uint32_t format;
    uint32_t bitsPerPixel; // bits per pixel for first plane
    const PlaneLayoutInfo *planes;
};

static const PlaneLayoutInfo YUV_420SP_LAYOUT = {
    .numPlanes = 2,
    .radio = { 4, 2 },
};

static const PlaneLayoutInfo YUV_420P_LAYOUT = {
    .numPlanes = 3,
    .radio = { 4, 1, 1 },
};

static const PlaneLayoutInfo YUV_422SP_LAYOUT = {
    .numPlanes = 2,
    .radio = { 4, 4 },
};

static const PlaneLayoutInfo YUV_422P_LAYOUT = {
    .numPlanes = 3,
    .radio = { 4, 2, 2 },
};

static const FormatInfo *GetFormatInfo(uint32_t format)
{
    static const FormatInfo FMT_INFOS[] = {
        {DRM_FORMAT_RGBX8888,  32, nullptr},  {DRM_FORMAT_RGBA8888, 32,  nullptr},
        {DRM_FORMAT_BGRX8888,  32, nullptr},  {DRM_FORMAT_BGRA8888, 32,  nullptr},
        {DRM_FORMAT_RGB888,    24, nullptr},  {DRM_FORMAT_RGB565,   16,  nullptr},
        {DRM_FORMAT_BGRX4444,  16, nullptr},  {DRM_FORMAT_BGRA4444, 16,  nullptr},
        {DRM_FORMAT_RGBA4444,  16, nullptr},  {DRM_FORMAT_RGBX4444, 16,  nullptr},
        {DRM_FORMAT_BGRX5551,  16, nullptr},  {DRM_FORMAT_BGRA5551, 16,  nullptr},
        {DRM_FORMAT_NV12, 8, &YUV_420SP_LAYOUT}, {DRM_FORMAT_NV21, 8, &YUV_420SP_LAYOUT},
        {DRM_FORMAT_NV16, 8, &YUV_422SP_LAYOUT},  {DRM_FORMAT_NV61, 8, &YUV_422SP_LAYOUT},
        {DRM_FORMAT_YUV420, 8, &YUV_420P_LAYOUT}, {DRM_FORMAT_YVU420, 8, &YUV_420P_LAYOUT},
        {DRM_FORMAT_YUV422, 8, &YUV_422P_LAYOUT}, {DRM_FORMAT_YVU422, 8, &YUV_422P_LAYOUT},
    };

    for (uint32_t i = 0; i < sizeof(FMT_INFOS) / sizeof(FormatInfo); i++) {
        if (FMT_INFOS[i].format == format) {
            return &FMT_INFOS[i];
        }
    }
    DISPLAY_LOGE("the format can not support");
    return nullptr;
}

void InitGbmBo(struct gbm_bo *bo, const struct drm_mode_create_dumb *dumb)
{
    DISPLAY_CHK_RETURN_NOT_VALUE((dumb == nullptr), DISPLAY_LOGE("dumb is null"));
    DISPLAY_CHK_RETURN_NOT_VALUE((bo == nullptr), DISPLAY_LOGE("bo is null"));
    bo->stride = dumb->pitch;
    bo->size = dumb->size;
    bo->handle = dumb->handle;
}

static uint32_t AdjustStrideFromFormat(uint32_t format, uint32_t height)
{
    uint32_t tmpHeight = height;
    const FormatInfo *fmtInfo = GetFormatInfo(format);
    if ((fmtInfo != nullptr) && (fmtInfo->planes != nullptr)) {
        uint32_t sum = fmtInfo->planes->radio[0];
        for (uint32_t i = 1; (i < fmtInfo->planes->numPlanes) && (i < MAX_PLANES); i++) {
            sum += fmtInfo->planes->radio[i];
        }
        if (sum > 0) {
            tmpHeight = DIV_ROUND_UP((height * sum), fmtInfo->planes->radio[0]);
        }
        DISPLAY_LOGD("height adjust to : %{public}d", tmpHeight);
    }
    return tmpHeight;
}

struct gbm_bo *HdiGbmBoCreate(struct gbm_device *gbm, uint32_t width, uint32_t height, uint32_t format,
    uint32_t usage)
{
    DISPLAY_UNUSED(usage);
    int ret;
    struct gbm_bo *bo;
    struct drm_mode_create_dumb dumb = { 0 };
    const FormatInfo *fmtInfo = GetFormatInfo(format);
    DISPLAY_CHK_RETURN((fmtInfo == nullptr), nullptr,
        DISPLAY_LOGE("formt: 0x%{public}x can not get layout info", format));
    bo = (struct gbm_bo *)calloc(1, sizeof(struct gbm_bo));
    DISPLAY_CHK_RETURN((bo == nullptr), nullptr, DISPLAY_LOGE("gbm bo create fialed no memery"));
    (void)memset_s(bo, sizeof(struct gbm_bo), 0, sizeof(struct gbm_bo));
    bo->width = width;
    bo->height = height;
    bo->gbm = gbm;
    bo->format = format;
    // init create_dumb
    dumb.height = ALIGN_UP(AdjustStrideFromFormat(format, height), HEIGHT_ALIGN);
    dumb.width = ALIGN_UP(width, WIDTH_ALIGN);
    dumb.flags = 0;
    dumb.bpp = fmtInfo->bitsPerPixel;
    ret = drmIoctl(gbm->fd, DRM_IOCTL_MODE_CREATE_DUMB, &dumb);
    DISPLAY_LOGD("fmt 0x%{public}x create dumb width: %{public}d  height: %{public}d bpp: %{public}u pitch"
        "%{public}d size %{public}llu",
        format, dumb.width, dumb.height, dumb.bpp, dumb.pitch, dumb.size);
    DISPLAY_CHK_RETURN((ret != 0), nullptr, DISPLAY_LOGE("DRM_IOCTL_MODE_CREATE_DUMB failed errno %{public}d", errno));
    InitGbmBo(bo, &dumb);
    DISPLAY_LOGD(
        "fmt 0x%{public}x create dumb width: %{public}d  height: %{public}d  stride %{public}d size %{public}u", format,
        bo->width, bo->height, bo->stride, bo->size);
    return bo;
}

struct gbm_device *HdiGbmCreateDevice(int fd)
{
    struct gbm_device *gbm = (struct gbm_device *)calloc(1, sizeof(struct gbm_device));
    DISPLAY_CHK_RETURN((gbm == nullptr), nullptr, DISPLAY_LOGE("memory calloc failed"));
    gbm->fd = fd;
    return gbm;
}

void HdiGbmDeviceDestroy(struct gbm_device *gbm)
{
    free(gbm);
}

uint32_t HdiGbmBoGetStride(struct gbm_bo *bo)
{
    DISPLAY_CHK_RETURN((bo == nullptr), 0, DISPLAY_LOGE("the bo is null"));
    return bo->stride;
}

uint32_t HdiGbmBoGetWidth(struct gbm_bo *bo)
{
    DISPLAY_CHK_RETURN((bo == nullptr), 0, DISPLAY_LOGE("the bo is null"));
    return bo->width;
}

uint32_t HdiGbmBoGetHeight(struct gbm_bo *bo)
{
    DISPLAY_CHK_RETURN((bo == nullptr), 0, DISPLAY_LOGE("the bo is null"));
    return bo->height;
}

uint32_t HdiGbmBoGetSize(struct gbm_bo *bo)
{
    DISPLAY_CHK_RETURN((bo == nullptr), 0, DISPLAY_LOGE("the bo is null"));
    return bo->size;
}

void HdiGbmBoDestroy(struct gbm_bo *bo)
{
    int ret;
    DISPLAY_CHK_RETURN_NOT_VALUE((bo == nullptr), DISPLAY_LOGE("the bo is null"));
    struct drm_mode_destroy_dumb dumb = { 0 };
    dumb.handle = bo->handle;
    ret = drmIoctl(bo->gbm->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dumb);
    DISPLAY_CHK_RETURN_NOT_VALUE((ret), DISPLAY_LOGE("dumb buffer destroy failed errno %{public}d", errno));
    free(bo);
}

int HdiGbmBoGetFd(struct gbm_bo *bo)
{
    int fd;
    int ret = drmPrimeHandleToFD(bo->gbm->fd, bo->handle, DRM_CLOEXEC | DRM_RDWR, &fd);
    DISPLAY_CHK_RETURN((ret), -1,
        DISPLAY_LOGE("drmPrimeHandleToFD  failed ret: %{public}d  errno: %{public}d", ret, errno));
    return fd;
}
} // namespace DISPLAY
} // namespace HDI
} // namespace OHOS