/*
  Simple DirectMedia Layer
  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>

  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not
     claim that you wrote the original software. If you use this software
     in a product, an acknowledgment in the product documentation would be
     appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be
     misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution.
*/
#include "../../SDL_internal.h"

#if SDL_VIDEO_DRIVER_DIRECTFB

/*
 * #include "SDL_DirectFB_keyboard.h"
 */
#include "SDL_DirectFB_modes.h"
#include "SDL_DirectFB_opengl.h"
#include "SDL_DirectFB_window.h"
#include "SDL_DirectFB_WM.h"


/* DirectFB video driver implementation.
*/

#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

#include <directfb.h>
#include <directfb_version.h>
#include <directfb_strings.h>

#include "SDL_video.h"
#include "SDL_mouse.h"
#include "../SDL_sysvideo.h"
#include "../SDL_pixels_c.h"
#include "../../events/SDL_events_c.h"
#include "SDL_DirectFB_video.h"
#include "SDL_DirectFB_events.h"
#include "SDL_DirectFB_render.h"
#include "SDL_DirectFB_mouse.h"
#include "SDL_DirectFB_shape.h"


#include "SDL_DirectFB_dyn.h"

/* Initialization/Query functions */
static int DirectFB_VideoInit(_THIS);
static void DirectFB_VideoQuit(_THIS);

static int DirectFB_Available(void);
static SDL_VideoDevice *DirectFB_CreateDevice(int devindex);

VideoBootStrap DirectFB_bootstrap = {
    "directfb", "DirectFB",
    DirectFB_Available, DirectFB_CreateDevice
};

static const DirectFBSurfaceDrawingFlagsNames(drawing_flags);
static const DirectFBSurfaceBlittingFlagsNames(blitting_flags);
static const DirectFBAccelerationMaskNames(acceleration_mask);

/* DirectFB driver bootstrap functions */

static int
DirectFB_Available(void)
{
    if (!SDL_DirectFB_LoadLibrary())
        return 0;
    SDL_DirectFB_UnLoadLibrary();
    return 1;
}

static void
DirectFB_DeleteDevice(SDL_VideoDevice * device)
{
    SDL_DirectFB_UnLoadLibrary();
    SDL_DFB_FREE(device->driverdata);
    SDL_DFB_FREE(device);
}

static SDL_VideoDevice *
DirectFB_CreateDevice(int devindex)
{
    SDL_VideoDevice *device;

    if (!SDL_DirectFB_LoadLibrary()) {
        return NULL;
    }

    /* Initialize all variables that we clean on shutdown */
    SDL_DFB_ALLOC_CLEAR(device, sizeof(SDL_VideoDevice));

    /* Set the function pointers */
    device->VideoInit = DirectFB_VideoInit;
    device->VideoQuit = DirectFB_VideoQuit;
    device->GetDisplayModes = DirectFB_GetDisplayModes;
    device->SetDisplayMode = DirectFB_SetDisplayMode;
    device->PumpEvents = DirectFB_PumpEventsWindow;
    device->CreateSDLWindow = DirectFB_CreateWindow;
    device->CreateSDLWindowFrom = DirectFB_CreateWindowFrom;
    device->SetWindowTitle = DirectFB_SetWindowTitle;
    device->SetWindowIcon = DirectFB_SetWindowIcon;
    device->SetWindowPosition = DirectFB_SetWindowPosition;
    device->SetWindowSize = DirectFB_SetWindowSize;
    device->SetWindowOpacity = DirectFB_SetWindowOpacity;
    device->ShowWindow = DirectFB_ShowWindow;
    device->HideWindow = DirectFB_HideWindow;
    device->RaiseWindow = DirectFB_RaiseWindow;
    device->MaximizeWindow = DirectFB_MaximizeWindow;
    device->MinimizeWindow = DirectFB_MinimizeWindow;
    device->RestoreWindow = DirectFB_RestoreWindow;
    device->SetWindowGrab = DirectFB_SetWindowGrab;
    device->DestroyWindow = DirectFB_DestroyWindow;
    device->GetWindowWMInfo = DirectFB_GetWindowWMInfo;

    /* !!! FIXME: implement SetWindowBordered */

#if SDL_DIRECTFB_OPENGL
    device->GL_LoadLibrary = DirectFB_GL_LoadLibrary;
    device->GL_GetProcAddress = DirectFB_GL_GetProcAddress;
    device->GL_MakeCurrent = DirectFB_GL_MakeCurrent;

    device->GL_CreateContext = DirectFB_GL_CreateContext;
    device->GL_SetSwapInterval = DirectFB_GL_SetSwapInterval;
    device->GL_GetSwapInterval = DirectFB_GL_GetSwapInterval;
    device->GL_SwapWindow = DirectFB_GL_SwapWindow;
    device->GL_DeleteContext = DirectFB_GL_DeleteContext;

#endif

    /* Shaped window support */
    device->shape_driver.CreateShaper = DirectFB_CreateShaper;
    device->shape_driver.SetWindowShape = DirectFB_SetWindowShape;
    device->shape_driver.ResizeWindowShape = DirectFB_ResizeWindowShape;

    device->free = DirectFB_DeleteDevice;

    return device;
  error:
    if (device)
        SDL_free(device);
    return (0);
}

static void
DirectFB_DeviceInformation(IDirectFB * dfb)
{
    DFBGraphicsDeviceDescription desc;
    int n;

    dfb->GetDeviceDescription(dfb, &desc);

    SDL_DFB_LOG( "DirectFB Device Information");
    SDL_DFB_LOG( "===========================");
    SDL_DFB_LOG( "Name:           %s", desc.name);
    SDL_DFB_LOG( "Vendor:         %s", desc.vendor);
    SDL_DFB_LOG( "Driver Name:    %s", desc.driver.name);
    SDL_DFB_LOG( "Driver Vendor:  %s", desc.driver.vendor);
    SDL_DFB_LOG( "Driver Version: %d.%d", desc.driver.major,
            desc.driver.minor);

    SDL_DFB_LOG( "Video memory:   %d", desc.video_memory);

    SDL_DFB_LOG( "Blitting flags:");
    for (n = 0; blitting_flags[n].flag; n++) {
        if (desc.blitting_flags & blitting_flags[n].flag)
            SDL_DFB_LOG( "    %s", blitting_flags[n].name);
    }

    SDL_DFB_LOG( "Drawing flags:");
    for (n = 0; drawing_flags[n].flag; n++) {
        if (desc.drawing_flags & drawing_flags[n].flag)
            SDL_DFB_LOG( "    %s", drawing_flags[n].name);
    }


    SDL_DFB_LOG( "Acceleration flags:");
    for (n = 0; acceleration_mask[n].mask; n++) {
        if (desc.acceleration_mask & acceleration_mask[n].mask)
            SDL_DFB_LOG( "    %s", acceleration_mask[n].name);
    }


}

static int readBoolEnv(const char *env_name, int def_val)
{
    char *stemp;

    stemp = SDL_getenv(env_name);
    if (stemp)
        return atoi(stemp);
    else
        return def_val;
}

static int
DirectFB_VideoInit(_THIS)
{
    IDirectFB *dfb = NULL;
    DFB_DeviceData *devdata = NULL;
    DFBResult ret;

    SDL_DFB_ALLOC_CLEAR(devdata, sizeof(*devdata));

    SDL_DFB_CHECKERR(DirectFBInit(NULL, NULL));

    /* avoid switching to the framebuffer when we
     * are running X11 */
    ret = readBoolEnv(DFBENV_USE_X11_CHECK , 1);
    if (ret) {
        if (SDL_getenv("DISPLAY"))
            DirectFBSetOption("system", "x11");
        else
            DirectFBSetOption("disable-module", "x11input");
    }

    devdata->use_linux_input = readBoolEnv(DFBENV_USE_LINUX_INPUT, 1);       /* default: on */

    if (!devdata->use_linux_input)
    {
        SDL_DFB_LOG("Disabling linux input\n");
        DirectFBSetOption("disable-module", "linux_input");
    }

    SDL_DFB_CHECKERR(DirectFBCreate(&dfb));

    DirectFB_DeviceInformation(dfb);

    devdata->use_yuv_underlays = readBoolEnv(DFBENV_USE_YUV_UNDERLAY, 0);     /* default: off */
    devdata->use_yuv_direct = readBoolEnv(DFBENV_USE_YUV_DIRECT, 0);      /* default is off! */

    /* Create global Eventbuffer for axis events */
    if (devdata->use_linux_input) {
        SDL_DFB_CHECKERR(dfb->CreateInputEventBuffer(dfb, DICAPS_ALL,
                                                     DFB_TRUE,
                                                     &devdata->events));
    } else {
        SDL_DFB_CHECKERR(dfb->CreateInputEventBuffer(dfb, DICAPS_AXES
                                                     /* DICAPS_ALL */ ,
                                                     DFB_TRUE,
                                                     &devdata->events));
    }

    /* simple window manager support */
    devdata->has_own_wm = readBoolEnv(DFBENV_USE_WM, 0);

    devdata->initialized = 1;

    devdata->dfb = dfb;
    devdata->firstwin = NULL;
    devdata->grabbed_window = NULL;

    _this->driverdata = devdata;

    DirectFB_InitModes(_this);

#if SDL_DIRECTFB_OPENGL
    DirectFB_GL_Initialize(_this);
#endif

    DirectFB_InitMouse(_this);
    DirectFB_InitKeyboard(_this);

    return 0;


  error:
    SDL_DFB_FREE(devdata);
    SDL_DFB_RELEASE(dfb);
    return -1;
}

static void
DirectFB_VideoQuit(_THIS)
{
    DFB_DeviceData *devdata = (DFB_DeviceData *) _this->driverdata;

    DirectFB_QuitModes(_this);
    DirectFB_QuitKeyboard(_this);
    DirectFB_QuitMouse(_this);

    devdata->events->Reset(devdata->events);
    SDL_DFB_RELEASE(devdata->events);
    SDL_DFB_RELEASE(devdata->dfb);

#if SDL_DIRECTFB_OPENGL
    DirectFB_GL_Shutdown(_this);
#endif

    devdata->initialized = 0;
}

/* DirectFB driver general support functions */

static const struct {
    DFBSurfacePixelFormat dfb;
    Uint32 sdl;
} pixelformat_tab[] =
{
    { DSPF_RGB32, SDL_PIXELFORMAT_RGB888 },             /* 24 bit RGB (4 byte, nothing@24, red 8@16, green 8@8, blue 8@0) */
    { DSPF_ARGB, SDL_PIXELFORMAT_ARGB8888 },            /* 32 bit ARGB (4 byte, alpha 8@24, red 8@16, green 8@8, blue 8@0) */
    { DSPF_RGB16, SDL_PIXELFORMAT_RGB565 },             /* 16 bit RGB (2 byte, red 5@11, green 6@5, blue 5@0) */
    { DSPF_RGB332, SDL_PIXELFORMAT_RGB332 },            /* 8 bit RGB (1 byte, red 3@5, green 3@2, blue 2@0) */
    { DSPF_ARGB4444, SDL_PIXELFORMAT_ARGB4444 },        /* 16 bit ARGB (2 byte, alpha 4@12, red 4@8, green 4@4, blue 4@0) */
    { DSPF_ARGB1555, SDL_PIXELFORMAT_ARGB1555 },        /* 16 bit ARGB (2 byte, alpha 1@15, red 5@10, green 5@5, blue 5@0) */
    { DSPF_RGB24, SDL_PIXELFORMAT_RGB24 },              /* 24 bit RGB (3 byte, red 8@16, green 8@8, blue 8@0) */
    { DSPF_RGB444, SDL_PIXELFORMAT_RGB444 },            /* 16 bit RGB (2 byte, nothing @12, red 4@8, green 4@4, blue 4@0) */
    { DSPF_YV12, SDL_PIXELFORMAT_YV12 },                /* 12 bit YUV (8 bit Y plane followed by 8 bit quarter size V/U planes) */
    { DSPF_I420,SDL_PIXELFORMAT_IYUV },                 /* 12 bit YUV (8 bit Y plane followed by 8 bit quarter size U/V planes) */
    { DSPF_YUY2, SDL_PIXELFORMAT_YUY2 },                /* 16 bit YUV (4 byte/ 2 pixel, macropixel contains CbYCrY [31:0]) */
    { DSPF_UYVY, SDL_PIXELFORMAT_UYVY },                /* 16 bit YUV (4 byte/ 2 pixel, macropixel contains YCbYCr [31:0]) */
    { DSPF_RGB555, SDL_PIXELFORMAT_RGB555 },            /* 16 bit RGB (2 byte, nothing @15, red 5@10, green 5@5, blue 5@0) */
#if (ENABLE_LUT8)
    { DSPF_LUT8, SDL_PIXELFORMAT_INDEX8 },              /* 8 bit LUT (8 bit color and alpha lookup from palette) */
#endif

#if (DFB_VERSION_ATLEAST(1,2,0))
    { DSPF_BGR555, SDL_PIXELFORMAT_BGR555 },            /* 16 bit BGR (2 byte, nothing @15, blue 5@10, green 5@5, red 5@0) */
#else
    { DSPF_UNKNOWN, SDL_PIXELFORMAT_BGR555 },
#endif

    /* Pfff ... nonmatching formats follow */

    { DSPF_ALUT44, SDL_PIXELFORMAT_UNKNOWN },           /* 8 bit ALUT (1 byte, alpha 4@4, color lookup 4@0) */
    { DSPF_A8, SDL_PIXELFORMAT_UNKNOWN },               /*  8 bit alpha (1 byte, alpha 8@0), e.g. anti-aliased glyphs */
    { DSPF_AiRGB, SDL_PIXELFORMAT_UNKNOWN },            /*  32 bit ARGB (4 byte, inv. alpha 8@24, red 8@16, green 8@8, blue 8@0) */
    { DSPF_A1, SDL_PIXELFORMAT_UNKNOWN },               /*  1 bit alpha (1 byte/ 8 pixel, most significant bit used first) */
    { DSPF_NV12, SDL_PIXELFORMAT_UNKNOWN },             /*  12 bit YUV (8 bit Y plane followed by one 16 bit quarter size CbCr [15:0] plane) */
    { DSPF_NV16, SDL_PIXELFORMAT_UNKNOWN },             /*  16 bit YUV (8 bit Y plane followed by one 16 bit half width CbCr [15:0] plane) */
    { DSPF_ARGB2554, SDL_PIXELFORMAT_UNKNOWN },         /*  16 bit ARGB (2 byte, alpha 2@14, red 5@9, green 5@4, blue 4@0) */
    { DSPF_NV21, SDL_PIXELFORMAT_UNKNOWN },             /*  12 bit YUV (8 bit Y plane followed by one 16 bit quarter size CrCb [15:0] plane) */
    { DSPF_AYUV, SDL_PIXELFORMAT_UNKNOWN },             /*  32 bit AYUV (4 byte, alpha 8@24, Y 8@16, Cb 8@8, Cr 8@0) */
    { DSPF_A4, SDL_PIXELFORMAT_UNKNOWN },               /*  4 bit alpha (1 byte/ 2 pixel, more significant nibble used first) */
    { DSPF_ARGB1666, SDL_PIXELFORMAT_UNKNOWN },         /*  1 bit alpha (3 byte/ alpha 1@18, red 6@16, green 6@6, blue 6@0) */
    { DSPF_ARGB6666, SDL_PIXELFORMAT_UNKNOWN },         /*  6 bit alpha (3 byte/ alpha 6@18, red 6@16, green 6@6, blue 6@0) */
    { DSPF_RGB18, SDL_PIXELFORMAT_UNKNOWN },            /*  6 bit RGB (3 byte/ red 6@16, green 6@6, blue 6@0) */
    { DSPF_LUT2, SDL_PIXELFORMAT_UNKNOWN },             /*  2 bit LUT (1 byte/ 4 pixel, 2 bit color and alpha lookup from palette) */

#if (DFB_VERSION_ATLEAST(1,3,0))
    { DSPF_RGBA4444, SDL_PIXELFORMAT_UNKNOWN },         /* 16 bit RGBA (2 byte, red 4@12, green 4@8, blue 4@4, alpha 4@0) */
#endif

#if (DFB_VERSION_ATLEAST(1,4,3))
    { DSPF_RGBA5551, SDL_PIXELFORMAT_UNKNOWN },         /*  16 bit RGBA (2 byte, red 5@11, green 5@6, blue 5@1, alpha 1@0) */
    { DSPF_YUV444P, SDL_PIXELFORMAT_UNKNOWN },          /*  24 bit full YUV planar (8 bit Y plane followed by an 8 bit Cb and an 8 bit Cr plane) */
    { DSPF_ARGB8565, SDL_PIXELFORMAT_UNKNOWN },         /*  24 bit ARGB (3 byte, alpha 8@16, red 5@11, green 6@5, blue 5@0) */
    { DSPF_AVYU, SDL_PIXELFORMAT_UNKNOWN },             /*  32 bit AVYU 4:4:4 (4 byte, alpha 8@24, Cr 8@16, Y 8@8, Cb 8@0) */
    { DSPF_VYU, SDL_PIXELFORMAT_UNKNOWN },              /*  24 bit VYU 4:4:4 (3 byte, Cr 8@16, Y 8@8, Cb 8@0)  */
#endif

    { DSPF_UNKNOWN, SDL_PIXELFORMAT_INDEX1LSB },
    { DSPF_UNKNOWN, SDL_PIXELFORMAT_INDEX1MSB },
    { DSPF_UNKNOWN, SDL_PIXELFORMAT_INDEX4LSB },
    { DSPF_UNKNOWN, SDL_PIXELFORMAT_INDEX4MSB },
    { DSPF_UNKNOWN, SDL_PIXELFORMAT_BGR24 },
    { DSPF_UNKNOWN, SDL_PIXELFORMAT_BGR888 },
    { DSPF_UNKNOWN, SDL_PIXELFORMAT_RGBA8888 },
    { DSPF_UNKNOWN, SDL_PIXELFORMAT_ABGR8888 },
    { DSPF_UNKNOWN, SDL_PIXELFORMAT_BGRA8888 },
    { DSPF_UNKNOWN, SDL_PIXELFORMAT_ARGB2101010 },
    { DSPF_UNKNOWN, SDL_PIXELFORMAT_ABGR4444 },
    { DSPF_UNKNOWN, SDL_PIXELFORMAT_ABGR1555 },
    { DSPF_UNKNOWN, SDL_PIXELFORMAT_BGR565 },
    { DSPF_UNKNOWN, SDL_PIXELFORMAT_YVYU },                        /**< Packed mode: Y0+V0+Y1+U0 (1 pla */
};

Uint32
DirectFB_DFBToSDLPixelFormat(DFBSurfacePixelFormat pixelformat)
{
    int i;

    for (i=0; pixelformat_tab[i].dfb != DSPF_UNKNOWN; i++)
        if (pixelformat_tab[i].dfb == pixelformat)
        {
            return pixelformat_tab[i].sdl;
        }
    return SDL_PIXELFORMAT_UNKNOWN;
}

DFBSurfacePixelFormat
DirectFB_SDLToDFBPixelFormat(Uint32 format)
{
    int i;

    for (i=0; pixelformat_tab[i].dfb != DSPF_UNKNOWN; i++)
        if (pixelformat_tab[i].sdl == format)
        {
            return pixelformat_tab[i].dfb;
        }
    return DSPF_UNKNOWN;
}

void DirectFB_SetSupportedPixelFormats(SDL_RendererInfo* ri)
{
    int i, j;

    for (i=0, j=0; pixelformat_tab[i].dfb != DSPF_UNKNOWN; i++)
        if (pixelformat_tab[i].sdl != SDL_PIXELFORMAT_UNKNOWN)
            ri->texture_formats[j++] = pixelformat_tab[i].sdl;
    ri->num_texture_formats = j;
}

#endif /* SDL_VIDEO_DRIVER_DIRECTFB */
