/* SPDX-License-Identifier: LGPL-2.1-only OR BSD-3-Clause */
/******************************************************************************
 *
 * This file is provided under a dual license.  When you use or
 * distribute this software, you may choose to be licensed under
 * version 2.1 of the GNU Lesser General Public License 
 * ("LGPLv2.1 License") or BSD License.
 *
 * LGPLv2.1 License
 *
 * Copyright(C) 2019 MediaTek Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2.1 of the GNU Lesser General Public 
 * License as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html for more
 * details.
 *
 * BSD LICENSE
 *
 * Copyright(C) 2019 MediaTek Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * 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.
 *  * 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 <config.h>

#include <pthread.h>
#include <assert.h>

#include <sys/ioctl.h>
#include <sys/mman.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

#include <asm/types.h>

#include <directfb.h>

#include <direct/messages.h>

#include <core/palette.h>
#include <core/surface.h>

#include <gfx/convert.h>
#include <gfx_if.h>

#include "mt53.h"

typedef enum
{
    OSD_BM_NONE,
    OSD_BM_PIXEL,
    OSD_BM_REGION,
    OSD_BM_PLANE
} OSD_BLEND_MODE_T;

static unsigned int _mapColorFormat(DFBSurfacePixelFormat format) 
{
    unsigned int mt_format = CM_Reserved0;
    switch(format)
    {
        case DSPF_UNKNOWN:
        case DSPF_ARGB1555:         
            break;
        case DSPF_RGB16:  
            mt_format = CM_RGB565_DIRECT16;
            break;          
        case DSPF_RGB24:
        case DSPF_RGB32:
            break;
        case DSPF_ARGB:
            mt_format = CM_ARGB8888_DIRECT32;
            break;
        case DSPF_A8:
        case DSPF_YUY2:
            mt_format = CM_YCbYCr422_DIRECT16;          
            break;          
        case DSPF_RGB332:
            break;
        case DSPF_UYVY:
            mt_format = CM_CbYCrY422_DIRECT16;          
            break;
        case DSPF_I420:
        case DSPF_YV12:
            break;     
        case DSPF_LUT8:
            mt_format = CM_RGB_CLUT8;           
            break;
        case DSPF_ALUT44:
        case DSPF_AiRGB:
        case DSPF_A1:
        case DSPF_NV12:
        case DSPF_NV16:
        case DSPF_ARGB2554:
            break;
        case DSPF_ARGB4444:
            mt_format = CM_ARGB4444_DIRECT16;
            break;
        case DSPF_NV21:
            break;
        case DSPF_AYUV:
            mt_format = CM_AYCbCr8888_DIRECT32;
            break;          
        case DSPF_A4:
        case DSPF_ARGB1666:
        case DSPF_ARGB6666:
        case DSPF_RGB18:
        case DSPF_LUT2:
        case DSPF_RGB444:
        case DSPF_RGB555:
            break;
      }
    return mt_format;
}

/**********************************************************************************************************************/

static int
vdpLayerDataSize()
{
     return sizeof(MT53LayerData);
}

static int
vdpRegionDataSize()
{
     return 0;
}


int vdpSetCropRect(mt53fb_vdp_crop_rect * pt_vdp_crop_rect)
{
    if (ioctl( dfb_mt53->fd, FBIO_VDP_SET_CROP_RECT, pt_vdp_crop_rect ) < 0) 
    {
        return DFB_IO;
    }
     
    return DFB_OK;
}

int vdpGetCropRect(mt53fb_vdp_crop_rect * pt_vdp_crop_rect)
{
    if (ioctl( dfb_mt53->fd, FBIO_VDP_GET_CROP_RECT, pt_vdp_crop_rect ) < 0) 
    {
        return DFB_IO;
    }
     
    return DFB_OK;
}

int vdpStretchBlit(mt53fb_vdp_stretch_blit * pt_vdp_stretch_blit)
{
    if (ioctl( dfb_mt53->fd, FBIO_VDP_STRETCH_BLIT, pt_vdp_stretch_blit ) < 0) 
    {
        return DFB_IO;
    }
     
    return DFB_OK;
}

static DFBResult
vdpInitLayer( CoreLayer                  *layer,
                  void                       *driver_data,
                  void                       *layer_data,
                  DFBDisplayLayerDescription *description,
                  DFBDisplayLayerConfig      *config,
                  DFBColorAdjustment         *adjustment )
{

     MT53DriverData *sdrv = (MT53DriverData *)driver_data;
     MT53LayerData  *data = (MT53LayerData *)layer_data;

     data->layer = (MT53LayerID)sdrv->num_plane;
     sdrv->num_plane++;

     /* set capabilities and type */
     description->caps = (DFBDisplayLayerCapabilities)(DLCAPS_SURFACE | DLCAPS_SCREEN_POSITION | DLCAPS_SCREEN_SIZE | DLCAPS_LEVELS | DLCAPS_OPACITY | DLCAPS_ALPHACHANNEL);
     description->type = DLTF_GRAPHICS;

     /* set name */
     snprintf( description->name,
               DFB_DISPLAY_LAYER_DESC_NAME_LENGTH, "MediaTek 53xx VDP Layer" );

     /* fill out the default configuration */
     config->flags       = (DFBDisplayLayerConfigFlags)(DLCONF_WIDTH|DLCONF_HEIGHT|
                            DLCONF_PIXELFORMAT | DLCONF_BUFFERMODE | DLCONF_OPTIONS);
     config->width       = 1920;
     config->height      = 1080;
     config->pixelformat = DSPF_NV16;
     config->buffermode  = DLBM_FRONTONLY;
     config->options     = DLOP_NONE;

     // printf( "DirectFB/MT53: vdpInitLayer!\n" );
     
     return DFB_OK;
}

static DFBResult
vdpTestRegion( CoreLayer                  *layer,
                   void                       *driver_data,
                   void                       *layer_data,
                   CoreLayerRegionConfig      *config,
                   CoreLayerRegionConfigFlags *failed )
{
     CoreLayerRegionConfigFlags fail = CLRCF_NONE;

     // printf( "DirectFB/MT53: vdpTestRegion!\n" );
     
     /* check format */
     switch (config->format) {
          case DSPF_NV16:
               break;

          default:
               fail |= CLRCF_FORMAT;
               break;
     }

     /* check width */
     if (config->width > 1920 || config->width < 2)
     	{
          fail |= ((CoreLayerRegionConfigFlags)CLRCF_WIDTH);
     	}

     /* check height */
     if (config->height > 1080 || config->height < 2)
     	{
          fail |= ((CoreLayerRegionConfigFlags)CLRCF_HEIGHT);
     	}

     /* write back failing fields */
     if (failed)
          *failed = fail;

     /* return failure if any field failed */
     if (fail)
          return DFB_UNSUPPORTED;

     return DFB_OK;
}
                                

static DFBResult
vdpSetRegion( CoreLayer                  *layer,
                void                       *driver_data,
                void                       *layer_data,
                void                       *region_data,
                CoreLayerRegionConfig      *config,
                CoreLayerRegionConfigFlags  updated,
                CoreSurface                *surface,
                CorePalette                *palette,
                CoreSurfaceBufferLock      *left_lock, 
                CoreSurfaceBufferLock      *right_lock 
                )
{
     struct mt53fb_vdp_set set;
     CoreSurfaceBufferLock* lock = left_lock;
     MT53LayerData  *data = (MT53LayerData  *)layer_data;
     mt53fb_vdp_crop_rect  t_vdp_crop_rect;
     
     // direct_log_printf(NULL, "[%s,%d,%d]\n",__FUNCTION__,__LINE__,data->layer);

     
     if(data->layer != MT53_LAYER_VDP1)
     {
        printf( "[%s,%d,%d]\n",__FUNCTION__,__LINE__,data->layer);
        return DFB_INVAREA;
     }
     
     memset(&t_vdp_crop_rect,0x0,sizeof(mt53fb_vdp_crop_rect));
     t_vdp_crop_rect.ui4_layer = data->layer;

     if(vdpGetCropRect(&t_vdp_crop_rect) != DFB_OK) {
         printf( "[DFB] %s() ==> vdpGetCropRect() Fail.\n", __FUNCTION__ );
     }

     if(t_vdp_crop_rect.ui4_3d_mode & DLOP_3D_MODE)
     {
         data->_3dmode = 1;
     }
     else
     {
         data->_3dmode = 0;
     }
     if(updated & CLRCF_3D_CROP_OFFSET)
     {
         data->u4CropBottomOffset = config->crop_bottom_offset;
         data->u4CropTopOffset = config->crop_top_offset;         
         data->u4CropLeftOffset = config->crop_left_offset;         
         data->u4CropRightOffset = config->crop_right_offset;         
     }
     
     data->height = config->height;
     data->pitch = lock->pitch;
   
     set.ucEnable = 1;

     // printf( "DirectFB/MT53: vdpSetRegion!\n" );

     if (ioctl( dfb_mt53->fd, FBIO_VDP_SET, &set ) < 0) {
          D_PERROR( "DirectFB/MT53: FBIO_VDP_SET failed!\n" );
          return DFB_IO;
     }
     
     return DFB_OK;
}

static DFBResult
vdpRemoveRegion( CoreLayer             *layer,
                     void                  *driver_data,
                     void                  *layer_data,
                     void                  *region_data )
{
     struct mt53fb_vdp_set set;
     MT53LayerData  *data = (MT53LayerData  *)layer_data;

     if(data->layer != MT53_LAYER_VDP1)
     {
        printf( "[%s,%d,%d]\n",__FUNCTION__,__LINE__,data->layer);
        return DFB_INVAREA;
     }

     
     set.ucEnable = 0;

     // printf( "DirectFB/MT53: vdpRemoveRegion!\n" );

     if (ioctl( dfb_mt53->fd, FBIO_VDP_SET, &set ) < 0) {
          D_PERROR( "DirectFB/MT53: FBIO_VDP_SET failed!\n" );
          return DFB_IO;
     }

     return DFB_OK;
}

static DFBResult
vdpFlipRegion( CoreLayer                  *layer,
                void                       *driver_data,
                void                       *layer_data,
                void                       *region_data,
                CoreSurface                *surface,
                DFBSurfaceFlipFlags         flags,
                CoreSurfaceBufferLock      *left_lock,
                CoreSurfaceBufferLock      *right_lock)
{
    struct mt53fb_vdp_flip set;
     MT53LayerData          *data = (MT53LayerData  *)layer_data;
     CoreSurfaceBufferLock *lock  = left_lock;
     mt53fb_vdp_crop_rect  t_vdp_crop_rect;

     // direct_log_printf(NULL, "[%s,%d,%d]\n",__FUNCTION__,__LINE__,data->layer);
     
     if(data->layer != MT53_LAYER_VDP1)
     {
        return DFB_INVAREA;
     }

     memset(&t_vdp_crop_rect,0x0,sizeof(mt53fb_vdp_crop_rect));

     t_vdp_crop_rect.ui4_layer = data->layer;

     if(vdpGetCropRect(&t_vdp_crop_rect) != DFB_OK) {
         printf( "[DFB] %s() ==> vdpGetCropRect() Fail.\n", __FUNCTION__ );
     }

     if(t_vdp_crop_rect.ui4_3d_mode & DLOP_3D_MODE)
     {
         data->_3dmode = 1;
     }
     else
     {
         data->_3dmode = 0;
     }
     
     set.pu1SrcYAddr = (unsigned char*)lock->phys;
     set.pu1SrcCAddr =  (unsigned char*)(lock->phys + lock->pitch * surface->config.size.h);
     set.fgIs3DMode = data->_3dmode;
     set.ui4_pitch = lock->pitch;
     set.u4CropBottomOffset = data->u4CropBottomOffset;
     set.u4CropTopOffset = data->u4CropTopOffset;     
     set.u4CropLeftOffset = data->u4CropLeftOffset;          
     set.u4CropRightOffset = data->u4CropRightOffset;               

     if (ioctl( dfb_mt53->fd, FBIO_VDP_FLIP, &set ) < 0) {
          D_PERROR( "DirectFB/MT53: FBIO_SET failed!\n" );
          return DFB_IO;
     }

     if(!(flags & DSFLIP_BLIT))
     {
         dfb_surface_flip( surface, false );
     }

     return DFB_OK;
}


static DFBResult
vdpUpdateRegion ( CoreLayer                  *layer,
                    void                       *driver_data,
                    void                       *layer_data,
                    void                       *region_data,
                    CoreSurface                *surface,
                    const DFBRegion            *left_update,
                    CoreSurfaceBufferLock      *left_lock, 
                    const DFBRegion            *right_update,
                    CoreSurfaceBufferLock      *right_lock)
{
    struct mt53fb_vdp_flip set;
     MT53LayerData  *data = (MT53LayerData  *)layer_data;
     CoreSurfaceBufferLock *lock = left_lock;
     mt53fb_vdp_crop_rect  t_vdp_crop_rect;

     if(data->layer != MT53_LAYER_VDP1)
     {
        printf( "[%s,%d,%d]\n",__FUNCTION__,__LINE__,data->layer);
        return DFB_INVAREA;
     }
          
     memset(&t_vdp_crop_rect,0x0,sizeof(mt53fb_vdp_crop_rect));

     t_vdp_crop_rect.ui4_layer = data->layer;

     if(vdpGetCropRect(&t_vdp_crop_rect) != DFB_OK) {
         printf( "[DFB] %s() ==> vdpGetCropRect() Fail.\n", __FUNCTION__ );
     }

     if(t_vdp_crop_rect.ui4_3d_mode & DLOP_3D_MODE)
     {
         data->_3dmode = 1;
     }
     else
     {
         data->_3dmode = 0;
     }
     
     set.pu1SrcYAddr = (unsigned char*)lock->phys;
     set.pu1SrcCAddr =  (unsigned char*)(lock->phys + lock->pitch * data->height);
     set.fgIs3DMode = data->_3dmode;
     set.ui4_pitch = lock->pitch;
     set.u4CropBottomOffset = data->u4CropBottomOffset;
     set.u4CropTopOffset = data->u4CropTopOffset;     
     set.u4CropLeftOffset = data->u4CropLeftOffset;          
     set.u4CropRightOffset = data->u4CropRightOffset;       
     
     // printf( "DirectFB/MT53: vdpUpdateRegion!\n" );

     if (ioctl( dfb_mt53->fd, FBIO_VDP_FLIP, &set ) < 0) {
          D_PERROR( "DirectFB/MT53: FBIO_FLIP failed!\n" );
          return DFB_IO;
     }

     return DFB_OK;
}

static DFBResult 
vdpGetLevel( CoreLayer              *layer,
                                         void                   *driver_data,
                                         void                   *layer_data,
                                         int                    *level )
{     
     return DFB_OK;     
}


static DFBResult 
vdpSetLevel             ( CoreLayer              *layer,
                                         void                   *driver_data,
                                         void                   *layer_data,
                                         int                     level )
{
     return DFB_OK;
}


/**********************************************************************************************************************/

/*const*/ DisplayLayerFuncs mt53VdpLayerFuncs = {
     LayerDataSize:      vdpLayerDataSize,
     RegionDataSize:     vdpRegionDataSize,
     InitLayer:          vdpInitLayer,
     GetLevel:         vdpGetLevel,
     SetLevel:         vdpSetLevel,
     TestRegion:         vdpTestRegion,
     SetRegion:          vdpSetRegion,
     RemoveRegion:       vdpRemoveRegion,
     FlipRegion:         vdpFlipRegion,
     UpdateRegion:       vdpUpdateRegion     
};
