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

/*
 *
 * @author Rama, Meka(v.meka@samsung.com)
           Sangwoo, Park(sw5771.park@samsung.com)
           Jamie Oh (jung-min.oh@samsung.com)
 * @date   2011-07-28
 *
 */

#include <sys/resource.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#include <EGL/egl.h>
#include <GLES/gl.h>
#include <hardware_legacy/uevent.h>
#include "SecHWCUtils.h"

static IMG_gralloc_module_public_t *gpsGrallocModule;

static int hwc_device_open(const struct hw_module_t* module, const char* name,
                           struct hw_device_t** device);

static struct hw_module_methods_t hwc_module_methods = {
    open: hwc_device_open
};

hwc_module_t HAL_MODULE_INFO_SYM = {
    common: {
        tag: HARDWARE_MODULE_TAG,
        module_api_version: HWC_MODULE_API_VERSION_0_1,
        hal_api_version: HARDWARE_HAL_API_VERSION,
        id: HWC_HARDWARE_MODULE_ID,
        name: "Samsung S5PC11X hwcomposer module",
        author: "SAMSUNG",
        methods: &hwc_module_methods,
    }
};

static void dump_layer(hwc_layer_t const* l) {
    ALOGD("\ttype=%d, flags=%08x, handle=%p, tr=%02x, blend=%04x, {%d,%d,%d,%d}, {%d,%d,%d,%d}",
            l->compositionType, l->flags, l->handle, l->transform, l->blending,
            l->sourceCrop.left,
            l->sourceCrop.top,
            l->sourceCrop.right,
            l->sourceCrop.bottom,
            l->displayFrame.left,
            l->displayFrame.top,
            l->displayFrame.right,
            l->displayFrame.bottom);
}

static int set_src_dst_info(hwc_layer_t *cur,
                            struct hwc_win_info_t *win,
                            struct sec_img *src_img,
                            struct sec_img *dst_img,
                            struct sec_rect *src_rect,
                            struct sec_rect *dst_rect,
                            int win_idx)
{
    IMG_native_handle_t *prev_handle = (IMG_native_handle_t *)(cur->handle);

    // set src image
    src_img->w       = prev_handle->iWidth;
    src_img->h       = prev_handle->iHeight;
    src_img->format  = prev_handle->iFormat;
    src_img->base    = NULL;
    src_img->offset  = 0;
    src_img->mem_id  =0;

    src_img->mem_type = HWC_PHYS_MEM_TYPE;
    src_img->w = (src_img->w + 15) & (~15);
    src_img->h = (src_img->h + 1) & (~1) ;

    //set src rect
    src_rect->x = SEC_MAX(cur->sourceCrop.left, 0);
    src_rect->y = SEC_MAX(cur->sourceCrop.top, 0);
    src_rect->w = SEC_MAX(cur->sourceCrop.right - cur->sourceCrop.left, 0);
    src_rect->w = SEC_MIN(src_rect->w, src_img->w - src_rect->x);
    src_rect->h = SEC_MAX(cur->sourceCrop.bottom - cur->sourceCrop.top, 0);
    src_rect->h = SEC_MIN(src_rect->h, src_img->h - src_rect->y);

    //set dst image
    dst_img->w = win->lcd_info.xres;
    dst_img->h = win->lcd_info.yres;

    switch (win->lcd_info.bits_per_pixel) {
    case 32:
        dst_img->format = HAL_PIXEL_FORMAT_RGBX_8888;
        break;
    default:
        dst_img->format = HAL_PIXEL_FORMAT_RGB_565;
        break;
    }

    dst_img->base     = win->addr[win->buf_index];
    dst_img->offset   = 0;
    dst_img->mem_id   = 0;
    dst_img->mem_type = HWC_PHYS_MEM_TYPE;

    //set dst rect
    //fimc dst image will be stored from left top corner
    dst_rect->x = 0;
    dst_rect->y = 0;
    dst_rect->w = win->rect_info.w;
    dst_rect->h = win->rect_info.h;

    ALOGV("%s::sr_x %d sr_y %d sr_w %d sr_h %d dr_x %d dr_y %d dr_w %d dr_h %d ",
            __func__, src_rect->x, src_rect->y, src_rect->w, src_rect->h,
            dst_rect->x, dst_rect->y, dst_rect->w, dst_rect->h);

    return 0;
}

static int get_hwc_compos_decision(hwc_layer_t* cur)
{
    if(cur->flags & HWC_SKIP_LAYER || !cur->handle) {
        ALOGV("%s::is_skip_layer %d cur->handle %x",
                __func__, cur->flags & HWC_SKIP_LAYER, (uint32_t)cur->handle);
        return HWC_FRAMEBUFFER;
    }

    IMG_native_handle_t *prev_handle = (IMG_native_handle_t *)(cur->handle);
    int compositionType = HWC_FRAMEBUFFER;

    /* check here....if we have any resolution constraints */
    if (((cur->sourceCrop.right - cur->sourceCrop.left) < 16) ||
        ((cur->sourceCrop.bottom - cur->sourceCrop.top) < 8))
        return compositionType;

    if ((cur->transform == HAL_TRANSFORM_ROT_90) ||
        (cur->transform == HAL_TRANSFORM_ROT_270)) {
        if(((cur->displayFrame.right - cur->displayFrame.left) < 4)||
           ((cur->displayFrame.bottom - cur->displayFrame.top) < 8))
            return compositionType;
        } else if (((cur->displayFrame.right - cur->displayFrame.left) < 8) ||
                   ((cur->displayFrame.bottom - cur->displayFrame.top) < 4))
         return compositionType;

    if((prev_handle->usage & GRALLOC_USAGE_PHYS_CONTIG) &&
       (cur->blending == HWC_BLENDING_NONE))
        compositionType = HWC_OVERLAY;
    else
        compositionType = HWC_FRAMEBUFFER;

    ALOGV("%s::compositionType %d bpp %d format %x usage %x",
            __func__,compositionType, prev_handle->uiBpp, prev_handle->iFormat,
            prev_handle->usage & GRALLOC_USAGE_PHYS_CONTIG);

    return  compositionType;
}

static int assign_overlay_window(struct hwc_context_t *ctx,
                                 hwc_layer_t *cur,
                                 int win_idx,
                                 int layer_idx)
{
    struct hwc_win_info_t *win;
    sec_rect rect;
    int ret = 0;

    if(NUM_OF_WIN <= win_idx)
        return -1;

    win = &ctx->win[win_idx];

    rect.x = SEC_MAX(cur->displayFrame.left, 0);
    rect.y = SEC_MAX(cur->displayFrame.top, 0);
    rect.w = SEC_MIN(cur->displayFrame.right - rect.x, win->lcd_info.xres - rect.x);
    rect.h = SEC_MIN(cur->displayFrame.bottom - rect.y, win->lcd_info.yres - rect.y);
    win->set_win_flag = 0;

    if((rect.x != win->rect_info.x) || (rect.y != win->rect_info.y) ||
       (rect.w != win->rect_info.w) || (rect.h != win->rect_info.h)){
            win->rect_info.x = rect.x;
            win->rect_info.y = rect.y;
            win->rect_info.w = rect.w;
            win->rect_info.h = rect.h;
            win->set_win_flag = 1;
            win->layer_prev_buf = 0;
    }

    win->layer_index = layer_idx;
    win->status = HWC_WIN_RESERVED;

    ALOGV("%s:: win_x %d win_y %d win_w %d win_h %d lay_idx %d win_idx %d",
            __func__, win->rect_info.x, win->rect_info.y, win->rect_info.w,
            win->rect_info.h, win->layer_index, win_idx );

    return 0;
}

static void reset_win_rect_info(hwc_win_info_t *win)
{
    win->rect_info.x = 0;
    win->rect_info.y = 0;
    win->rect_info.w = 0;
    win->rect_info.h = 0;
    return;
}

static int hwc_prepare(hwc_composer_device_t *dev, hwc_layer_list_t* list)
{

    struct hwc_context_t* ctx = (struct hwc_context_t*)dev;
    int overlay_win_cnt = 0;
    int compositionType = 0;
    int ret;

    //if geometry is not changed, there is no need to do any work here
    if( !list || (!(list->flags & HWC_GEOMETRY_CHANGED)))
        return 0;

    //all the windows are free here....
    for (int i = 0; i < NUM_OF_WIN; i++) {
        ctx->win[i].status = HWC_WIN_FREE;
        ctx->win[i].buf_index = 0;
    }
    ctx->num_of_hwc_layer = 0;
    ctx->num_of_fb_layer = 0;
    ALOGV("%s:: hwc_prepare list->numHwLayers %d", __func__, list->numHwLayers);

    for (int i = 0; i < list->numHwLayers ; i++) {
        hwc_layer_t* cur = &list->hwLayers[i];

        if (overlay_win_cnt < NUM_OF_WIN) {
            compositionType = get_hwc_compos_decision(cur);

            if (compositionType == HWC_FRAMEBUFFER) {
                cur->compositionType = HWC_FRAMEBUFFER;
                ctx->num_of_fb_layer++;
            } else {
                ret = assign_overlay_window(ctx, cur, overlay_win_cnt, i);
                if (ret != 0) {
                    cur->compositionType = HWC_FRAMEBUFFER;
                    ctx->num_of_fb_layer++;
                    continue;
                }

                cur->compositionType = HWC_OVERLAY;
                cur->hints = HWC_HINT_CLEAR_FB;
                overlay_win_cnt++;
                ctx->num_of_hwc_layer++;
            }
        } else {
            cur->compositionType = HWC_FRAMEBUFFER;
            ctx->num_of_fb_layer++;
        }
    }

    if(list->numHwLayers != (ctx->num_of_fb_layer + ctx->num_of_hwc_layer))
        ALOGV("%s:: numHwLayers %d num_of_fb_layer %d num_of_hwc_layer %d ",
                __func__, list->numHwLayers, ctx->num_of_fb_layer,
                ctx->num_of_hwc_layer);

    if (overlay_win_cnt < NUM_OF_WIN) {
        //turn off the free windows
        for (int i = overlay_win_cnt; i < NUM_OF_WIN; i++) {
            window_hide(&ctx->win[i]);
            reset_win_rect_info(&ctx->win[i]);
        }
    }
    return 0;
}

static int hwc_set(hwc_composer_device_t *dev,
                   hwc_display_t dpy,
                   hwc_surface_t sur,
                   hwc_layer_list_t* list)
{
    struct hwc_context_t *ctx = (struct hwc_context_t *)dev;
    unsigned int phyAddr[MAX_NUM_PLANES];
    int skipped_window_mask = 0;
    hwc_layer_t* cur;
    struct hwc_win_info_t *win;
    int ret;
    struct sec_img src_img;
    struct sec_img dst_img;
    struct sec_rect src_rect;
    struct sec_rect dst_rect;

    if (dpy == NULL && sur == NULL && list == NULL) {
        // release our resources, the screen is turning off
        // in our case, there is nothing to do.
        ctx->num_of_fb_layer_prev = 0;
        return 0;
    }

    bool need_swap_buffers = ctx->num_of_fb_layer > 0;

    /*
     * H/W composer documentation states:
     * There is an implicit layer containing opaque black
     * pixels behind all the layers in the list.
     * It is the responsibility of the hwcomposer module to make
     * sure black pixels are output (or blended from).
     *
     * Since we're using a blitter, we need to erase the frame-buffer when
     * switching to all-overlay mode.
     *
     */
    if (ctx->num_of_hwc_layer &&
        ctx->num_of_fb_layer==0 && ctx->num_of_fb_layer_prev) {
        /* we're clearing the screen using GLES here, this is very
         * hack-ish, ideal we would use the fimc (if it can do it) */
        glDisable(GL_SCISSOR_TEST);
        glClearColor(0, 0, 0, 0);
        glClear(GL_COLOR_BUFFER_BIT);
        glEnable(GL_SCISSOR_TEST);
        need_swap_buffers = true;
    }

    ctx->num_of_fb_layer_prev = ctx->num_of_fb_layer;

    if (need_swap_buffers || !list) {
        EGLBoolean sucess = eglSwapBuffers((EGLDisplay)dpy, (EGLSurface)sur);
        if (!sucess) {
            return HWC_EGL_ERROR;
        }
    }

    if (!list) {
        /* turn off the all windows */
        for (int i = 0; i < NUM_OF_WIN; i++) {
            window_hide(&ctx->win[i]);
            reset_win_rect_info(&ctx->win[i]);
            ctx->win[i].status = HWC_WIN_FREE;
        }
        ctx->num_of_hwc_layer = 0;
        return 0;
    }

    if(ctx->num_of_hwc_layer > NUM_OF_WIN)
        ctx->num_of_hwc_layer = NUM_OF_WIN;

    /* compose hardware layers here */
    for (uint32_t i = 0; i < ctx->num_of_hwc_layer; i++) {
        win = &ctx->win[i];
        if (win->status == HWC_WIN_RESERVED) {
            cur = &list->hwLayers[win->layer_index];

            if (cur->compositionType == HWC_OVERLAY) {

                ret = gpsGrallocModule->GetPhyAddrs(gpsGrallocModule,
                        cur->handle, phyAddr);
                if (ret) {
                    ALOGE("%s::GetPhyAddrs fail : ret=%d\n", __func__, ret);
                    skipped_window_mask |= (1 << i);
                    continue;
                }

                /* initialize the src & dist context for fimc */
                set_src_dst_info (cur, win, &src_img, &dst_img, &src_rect,
                        &dst_rect, i);

                ret = runFimc(ctx, &src_img, &src_rect, &dst_img, &dst_rect,
                        phyAddr, cur->transform);
                if (ret < 0){
                   ALOGE("%s::runFimc fail : ret=%d\n", __func__, ret);
                   skipped_window_mask |= (1 << i);
                   continue;
                }

                if (win->set_win_flag == 1) {
                    /* turnoff the window and set the window position with new conf... */
                    if (window_set_pos(win) < 0) {
                        ALOGE("%s::window_set_pos is failed : %s", __func__,
                                strerror(errno));
                        skipped_window_mask |= (1 << i);
                        continue;
                    }
                    win->set_win_flag = 0;
                }

                /* is the frame didn't change, it needs to be composited
                 * because something else below it could have changed, however
                 * it doesn't need to be swapped.
                 */
                if (win->layer_prev_buf != (uint32_t)cur->handle) {
                    win->layer_prev_buf = (uint32_t)cur->handle;
                    window_pan_display(win);
                    win->buf_index = (win->buf_index + 1) % NUM_OF_WIN_BUF;
                }

                if(win->power_state == 0)
                    window_show(win);

            } else {
                ALOGE("%s:: error : layer %d compositionType should have been \
                        HWC_OVERLAY", __func__, win->layer_index);
                skipped_window_mask |= (1 << i);
                continue;
            }
         } else {
             ALOGE("%s:: error : window status should have been HWC_WIN_RESERVED \
                     by now... ", __func__);
             skipped_window_mask |= (1 << i);
             continue;
         }
    }

    if (skipped_window_mask) {
        //turn off the free windows
        for (int i = 0; i < NUM_OF_WIN; i++) {
            if (skipped_window_mask & (1 << i))
                window_hide(&ctx->win[i]);
        }
    }

    return 0;
}

static void hwc_registerProcs(struct hwc_composer_device* dev,
        hwc_procs_t const* procs)
{
    struct hwc_context_t* ctx = (struct hwc_context_t*)dev;
    ctx->procs = const_cast<hwc_procs_t *>(procs);
}

static int hwc_query(struct hwc_composer_device* dev,
        int what, int* value)
{
    struct hwc_context_t* ctx = (struct hwc_context_t*)dev;

    switch (what) {
    case HWC_BACKGROUND_LAYER_SUPPORTED:
        // we don't support the background layer yet
        value[0] = 0;
        break;
    case HWC_VSYNC_PERIOD:
        // vsync period in nanosecond
        value[0] = 1000000000.0 / gpsGrallocModule->psFrameBufferDevice->base.fps;
        break;
    default:
        // unsupported query
        return -EINVAL;
    }
    return 0;
}

static int hwc_eventControl(struct hwc_composer_device* dev,
        int event, int enabled)
{
    struct hwc_context_t* ctx = (struct hwc_context_t*)dev;

    switch (event) {
    case HWC_EVENT_VSYNC:
        int val = !!enabled;
        int err = ioctl(ctx->global_lcd_win.fd, S3CFB_SET_VSYNC_INT, &val);
        if (err < 0)
            return -errno;

        return 0;
    }

    return -EINVAL;
}

void handle_vsync_uevent(hwc_context_t *ctx, const char *buff, int len)
{
    uint64_t timestamp = 0;
    const char *s = buff;

    if(!ctx->procs || !ctx->procs->vsync)
       return;

    s += strlen(s) + 1;

    while(*s) {
        if (!strncmp(s, "VSYNC=", strlen("VSYNC=")))
            timestamp = strtoull(s + strlen("VSYNC="), NULL, 0);

        s += strlen(s) + 1;
        if (s - buff >= len)
            break;
    }

    ctx->procs->vsync(ctx->procs, 0, timestamp);
}

static void *hwc_vsync_thread(void *data)
{
    hwc_context_t *ctx = (hwc_context_t *)(data);
    char uevent_desc[4096];
    memset(uevent_desc, 0, sizeof(uevent_desc));

    setpriority(PRIO_PROCESS, 0, HAL_PRIORITY_URGENT_DISPLAY);

    uevent_init();
    while(true) {
        int len = uevent_next_event(uevent_desc, sizeof(uevent_desc) - 2);

        bool vsync = !strcmp(uevent_desc, "change@/devices/platform/s3cfb");
        if(vsync)
            handle_vsync_uevent(ctx, uevent_desc, len);
    }

    return NULL;
}

static int hwc_device_close(struct hw_device_t *dev)
{
    struct hwc_context_t* ctx = (struct hwc_context_t*)dev;
    int ret = 0;
    int i;

    if (ctx) {
        if (destroyFimc(&ctx->fimc) < 0) {
            ALOGE("%s::destroyFimc fail", __func__);
            ret = -1;
        }

        if (window_close(&ctx->global_lcd_win) < 0) {
            ALOGE("%s::window_close() fail", __func__);
            ret = -1;
        }

        for (i = 0; i < NUM_OF_WIN; i++) {
            if (window_close(&ctx->win[i]) < 0) {
                ALOGE("%s::window_close() fail", __func__);
                ret = -1;
            }
        }

        // TODO: stop vsync_thread

        free(ctx);
    }
    return ret;
}

static const struct hwc_methods hwc_methods = {
    eventControl: hwc_eventControl
};

static int hwc_device_open(const struct hw_module_t* module, const char* name,
        struct hw_device_t** device)
{
    int status = 0;
    int err;
    struct hwc_win_info_t *win;

    if(hw_get_module(GRALLOC_HARDWARE_MODULE_ID,
                (const hw_module_t**)&gpsGrallocModule))
        return -EINVAL;

    if(strcmp(gpsGrallocModule->base.common.author, "Imagination Technologies"))
        return -EINVAL;

    if (strcmp(name, HWC_HARDWARE_COMPOSER))
        return -EINVAL;

    struct hwc_context_t *dev;
    dev = (hwc_context_t*)malloc(sizeof(*dev));

    /* initialize our state here */
    memset(dev, 0, sizeof(*dev));

    /* initialize the procs */
    dev->device.common.tag = HARDWARE_DEVICE_TAG;
    dev->device.common.version = HWC_DEVICE_API_VERSION_0_3;
    dev->device.common.module = const_cast<hw_module_t*>(module);
    dev->device.common.close = hwc_device_close;

    dev->device.prepare = hwc_prepare;
    dev->device.set = hwc_set;
    dev->device.registerProcs = hwc_registerProcs;
    dev->device.query = hwc_query;
    dev->device.methods = &hwc_methods;

    *device = &dev->device.common;

    /* initializing */
    memset(&(dev->fimc), 0, sizeof(s5p_fimc_t));
    dev->fimc.dev_fd = -1;

    /* open WIN0 & WIN1 here */
    for (int i = 0; i < NUM_OF_WIN; i++) {
        if (window_open(&(dev->win[i]), i) < 0) {
             ALOGE("%s:: Failed to open window %d device ", __func__, i);
             status = -EINVAL;
             goto err;
        }
    }

    /* open window 2, used to query global LCD info */
    if (window_open(&dev->global_lcd_win, 2) < 0) {
        ALOGE("%s:: Failed to open window 2 device ", __func__);
        status = -EINVAL;
        goto err;
    }

    /* get default window config */
    if (window_get_global_lcd_info(dev) < 0) {
        ALOGE("%s::window_get_global_lcd_info is failed : %s",
                __func__, strerror(errno));
        status = -EINVAL;
        goto err;
    }

    dev->lcd_info.yres_virtual = dev->lcd_info.yres * NUM_OF_WIN_BUF;

    /* initialize the window context */
    for (int i = 0; i < NUM_OF_WIN; i++) {
        win = &dev->win[i];
        memcpy(&win->lcd_info, &dev->lcd_info, sizeof(struct fb_var_screeninfo));
        memcpy(&win->var_info, &dev->lcd_info, sizeof(struct fb_var_screeninfo));

        win->rect_info.x = 0;
        win->rect_info.y = 0;
        win->rect_info.w = win->var_info.xres;
        win->rect_info.h = win->var_info.yres;

        if (window_set_pos(win) < 0) {
            ALOGE("%s::window_set_pos is failed : %s",
                    __func__, strerror(errno));
            status = -EINVAL;
            goto err;
        }

        if (window_get_info(win) < 0) {
            ALOGE("%s::window_get_info is failed : %s",
                    __func__, strerror(errno));
            status = -EINVAL;
            goto err;
        }

        win->size = win->fix_info.line_length * win->var_info.yres;

        if (!win->fix_info.smem_start){
            ALOGE("%s:: win-%d failed to get the reserved memory", __func__, i);
            status = -EINVAL;
            goto err;
        }

        for (int j = 0; j < NUM_OF_WIN_BUF; j++) {
            win->addr[j] = win->fix_info.smem_start + (win->size * j);
            ALOGI("%s::win-%d add[%d] %x ", __func__, i, j, win->addr[j]);
        }
    }

    /* open pp */
    if (createFimc(&dev->fimc) < 0) {
        ALOGE("%s::creatFimc() fail", __func__);
        status = -EINVAL;
        goto err;
    }

    err = pthread_create(&dev->vsync_thread, NULL, hwc_vsync_thread, dev);
    if (err) {
        ALOGE("%s::pthread_create() failed : %s", __func__, strerror(err));
        status = -err;
        goto err;
    }

    ALOGD("%s:: success\n", __func__);

    return 0;

err:
    if (destroyFimc(&dev->fimc) < 0)
        ALOGE("%s::destroyFimc() fail", __func__);

    if (window_close(&dev->global_lcd_win) < 0)
        ALOGE("%s::window_close() fail", __func__);

    for (int i = 0; i < NUM_OF_WIN; i++) {
        if (window_close(&dev->win[i]) < 0)
            ALOGE("%s::window_close() fail", __func__);
    }

    return status;
}