/*  RetroArch - A frontend for libretro.
 *  Copyright (C) 2014-2017 - Ali Bouhlel
 *  Copyright (C) 2011-2017 - Daniel De Matteis
 *
 *  RetroArch is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  RetroArch 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 the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with RetroArch.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#include <pspkernel.h>
#include <pspdisplay.h>
#include <malloc.h>
#include <pspgu.h>
#include <pspgum.h>
#include <psprtc.h>

#include <retro_assert.h>
#include <retro_inline.h>

#ifdef HAVE_CONFIG_H
#include "../../config.h"
#endif

#ifdef HAVE_MENU
#include "../../menu/menu_driver.h"
#endif

#include "../font_driver.h"

#include "../../defines/psp_defines.h"

#ifndef SCEGU_SCR_WIDTH
#define SCEGU_SCR_WIDTH 480
#endif

#ifndef SCEGU_SCR_HEIGHT
#define SCEGU_SCR_HEIGHT 272
#endif

#ifndef SCEGU_VRAM_WIDTH
#define SCEGU_VRAM_WIDTH 512
#endif

/* Frame buffer */
#define SCEGU_VRAM_TOP        (0x44000000)
/* 16bit mode */
#define SCEGU_VRAM_BUFSIZE    (SCEGU_VRAM_WIDTH*SCEGU_SCR_HEIGHT*2)
#define SCEGU_VRAM_BP_0       ((void *)(SCEGU_VRAM_TOP))
#define SCEGU_VRAM_BP_1       ((void *)(SCEGU_VRAM_TOP+SCEGU_VRAM_BUFSIZE))
#define SCEGU_VRAM_BP_2       ((void *)(SCEGU_VRAM_TOP+(SCEGU_VRAM_BUFSIZE*2)))
/* 32bit mode */
#define SCEGU_VRAM_BUFSIZE32  (SCEGU_VRAM_WIDTH*SCEGU_SCR_HEIGHT*4)
#define SCEGU_VRAM_BP32_0     ((void *)(SCEGU_VRAM_TOP))
#define SCEGU_VRAM_BP32_1     ((void *)(SCEGU_VRAM_TOP+SCEGU_VRAM_BUFSIZE32))
#define SCEGU_VRAM_BP32_2     ((void *)(SCEGU_VRAM_TOP+(SCEGU_VRAM_BUFSIZE32*2)))

#define TO_UNCACHED_PTR(ptr)  ((void *)((uint32_t)(ptr)|0x40000000))
#define TO_CACHED_PTR(ptr)    ((void *)((uint32_t)(ptr)&~0x40000000))

#define FROM_GU_POINTER(ptr)  ((void *)((uint32_t)(ptr)|0x44000000))
#define TO_GU_POINTER(ptr)    ((void *)((uint32_t)(ptr)&~0x44000000))

typedef struct __attribute__((packed)) psp1_vertex
{
   float u,v;
   float x,y,z;

} psp1_vertex_t;

typedef struct __attribute__((packed)) psp1_sprite
{
   psp1_vertex_t v0;
   psp1_vertex_t v1;
} psp1_sprite_t;

typedef struct psp1_menu_frame
{
   void* dList;
   void* frame;
   psp1_sprite_t* frame_coords;

   bool active;

   PspGeContext context_storage;
} psp1_menu_frame_t;

typedef struct psp1_video
{
   void* main_dList;
   void* frame_dList;
   void* draw_buffer;
   void* texture;
   psp1_sprite_t* frame_coords;
   int tex_filter;

   bool vsync;
   bool rgb32;
   int bpp_log2;

   psp1_menu_frame_t menu;

   video_viewport_t vp;

   unsigned rotation;
   bool vblank_not_reached;
   bool keep_aspect;
   bool should_resize;
   bool hw_render;
} psp1_video_t;

// both row and column count need to be a power of 2
#define PSP_FRAME_ROWS_COUNT     4
#define PSP_FRAME_COLUMNS_COUNT  16
#define PSP_FRAME_SLICE_COUNT    (PSP_FRAME_ROWS_COUNT * PSP_FRAME_COLUMNS_COUNT)
#define PSP_FRAME_VERTEX_COUNT   (PSP_FRAME_SLICE_COUNT * 2)

static INLINE void psp_set_screen_coords (psp1_sprite_t* framecoords, 
      int x, int y, int width, int height, unsigned rotation)
{
   int i;
   float x0, y0, step_x, step_y;
   int current_column = 0;

   if (rotation == 0)
   {
      x0 = x;
      y0 = y;
      step_x = ((float) width)  / PSP_FRAME_COLUMNS_COUNT;
      step_y = ((float) height) / PSP_FRAME_ROWS_COUNT;

      for (i=0; i < PSP_FRAME_SLICE_COUNT; i++)
      {
         framecoords[i].v0.x = x0;
         framecoords[i].v0.y = y0;

         framecoords[i].v1.x = (x0 += step_x);
         framecoords[i].v1.y = y0 + step_y;

         if (++current_column == PSP_FRAME_COLUMNS_COUNT)
         {
            x0 = x;
            y0 += step_y;
            current_column = 0;
         }
      }
   }
   else if (rotation == 1) /* 90° */
   {
      x0 = x + width;
      y0 = y;
      step_x = -((float) width) / PSP_FRAME_ROWS_COUNT;
      step_y = ((float) height)  / PSP_FRAME_COLUMNS_COUNT;

      for (i=0; i < PSP_FRAME_SLICE_COUNT; i++)
      {
         framecoords[i].v0.x = x0;
         framecoords[i].v0.y = y0;

         framecoords[i].v1.x = x0 + step_x;
         framecoords[i].v1.y = (y0 += step_y);

         if (++current_column == PSP_FRAME_COLUMNS_COUNT)
         {
            y0 = y;
            x0 += step_x;
            current_column = 0;
         }
      }
   }
   else if (rotation == 2) /* 180° */
   {
      x0 = x + width;
      y0 = y + height;
      step_x = -((float) width)  / PSP_FRAME_COLUMNS_COUNT;
      step_y = -((float) height) / PSP_FRAME_ROWS_COUNT;

      for (i=0; i < PSP_FRAME_SLICE_COUNT; i++)
      {
         framecoords[i].v0.x = x0;
         framecoords[i].v0.y = y0;

         framecoords[i].v1.x = (x0 += step_x);
         framecoords[i].v1.y = y0 + step_y;

         if (++current_column == PSP_FRAME_COLUMNS_COUNT)
         {
            x0 = x + width;
            y0 += step_y;
            current_column = 0;
         }
      }
   }
   else /* 270° */
   {
      x0 = x;
      y0 = y + height;
      step_x = ((float) width)  / PSP_FRAME_ROWS_COUNT;
      step_y = -((float) height) / PSP_FRAME_COLUMNS_COUNT;

      for (i=0; i < PSP_FRAME_SLICE_COUNT; i++)
      {
         framecoords[i].v0.x = x0;
         framecoords[i].v0.y = y0;
         framecoords[i].v1.x = x0 + step_x;
         framecoords[i].v1.y = (y0 += step_y);

         if (++current_column == PSP_FRAME_COLUMNS_COUNT)
         {
            y0 = y + height;
            x0 += step_x;
            current_column = 0;
         }
      }
   }
}

static INLINE void psp_set_tex_coords (psp1_sprite_t* framecoords,
      int width, int height)
{
   int i;
   int current_column     = 0;
   float u0               = 0;
   float v0               = 0;
   float step_u           = ((float) width)  / PSP_FRAME_COLUMNS_COUNT;
   float step_v           = ((float) height) / PSP_FRAME_ROWS_COUNT;

   for (i=0; i < PSP_FRAME_SLICE_COUNT; i++)
   {
      framecoords[i].v0.u = u0;
      framecoords[i].v0.v = v0;
      u0                 += step_u;
      framecoords[i].v1.u = u0;
      framecoords[i].v1.v = v0 + step_v;

      if (++current_column == PSP_FRAME_COLUMNS_COUNT)
      {
         u0               = 0;
         v0              += step_v;
         current_column   = 0;
      }
   }
}

static void psp_update_viewport(psp1_video_t* psp,
      video_frame_info_t *video_info);

static void psp_on_vblank(u32 sub, psp1_video_t *psp)
{
   if (psp)
      psp->vblank_not_reached = false;
}

static void *psp_init(const video_info_t *video,
      const input_driver_t **input, void **input_data)
{
   /* TODO : add ASSERT() checks or use main RAM if 
    * VRAM is too low for desired video->input_scale. */

   int pixel_format, lut_pixel_format, lut_block_count;
   unsigned int red_shift, color_mask;
   void *pspinput           = NULL;
   void *displayBuffer      = NULL;
   void *LUT_r              = NULL;
   void *LUT_b              = NULL;
   psp1_video_t *psp        = (psp1_video_t*)calloc(1, sizeof(psp1_video_t));

   if (!psp)
      return NULL;

   sceGuInit();

   psp->vp.x                = 0;
   psp->vp.y                = 0;
   psp->vp.width            = SCEGU_SCR_WIDTH;
   psp->vp.height           = SCEGU_SCR_HEIGHT;
   psp->vp.full_width       = SCEGU_SCR_WIDTH;
   psp->vp.full_height      = SCEGU_SCR_HEIGHT;

   /* Make sure anything using uncached pointers reserves 
    * whole cachelines (memory address and size need to be a multiple of 64)
    * so it isn't overwritten by an unlucky cache writeback.
    *
    * This includes display lists since the Gu library uses 
    * uncached pointers to write to them. */

   /* Allocate more space if bigger display lists are needed. */
   psp->main_dList          = memalign(64, 256);

   psp->frame_dList         = memalign(64, 256);
   psp->menu.dList          = memalign(64, 256);
   psp->menu.frame          = memalign(16,  2 * 480 * 272);
   psp->frame_coords        = memalign(64,
         (((PSP_FRAME_SLICE_COUNT * sizeof(psp1_sprite_t)) + 63) & ~63));
   psp->menu.frame_coords   = memalign(64,
         (((PSP_FRAME_SLICE_COUNT * sizeof(psp1_sprite_t)) + 63) & ~63));

   memset(psp->frame_coords, 0,
         PSP_FRAME_SLICE_COUNT * sizeof(psp1_sprite_t));
   memset(psp->menu.frame_coords, 0,
         PSP_FRAME_SLICE_COUNT * sizeof(psp1_sprite_t));

   sceKernelDcacheWritebackInvalidateAll();
   psp->frame_coords        = TO_UNCACHED_PTR(psp->frame_coords);
   psp->menu.frame_coords   = TO_UNCACHED_PTR(psp->menu.frame_coords);

   psp->frame_coords->v0.x  = 60;
   psp->frame_coords->v0.y  = 0;
   psp->frame_coords->v0.u  = 0;
   psp->frame_coords->v0.v  = 0;

   psp->frame_coords->v1.x  = 420;
   psp->frame_coords->v1.y  = SCEGU_SCR_HEIGHT;
   psp->frame_coords->v1.u  = 256;
   psp->frame_coords->v1.v  = 240;

   psp->vsync               = video->vsync;
   psp->rgb32               = video->rgb32;

   if(psp->rgb32)
   {
      u32 i;
      uint32_t* LUT_r_local = (uint32_t*)(SCEGU_VRAM_BP32_2);
      uint32_t* LUT_b_local = (uint32_t*)(SCEGU_VRAM_BP32_2) + (1 << 8);

      red_shift             = 8 + 8;
      color_mask            = 0xFF;
      lut_block_count       = (1 << 8) / 8;

      psp->texture          = (void*)(LUT_b_local + (1 << 8));
      psp->draw_buffer      = SCEGU_VRAM_BP32_0;
      psp->bpp_log2         = 2;

      pixel_format          = GU_PSM_8888;
      lut_pixel_format      = GU_PSM_T32;

      displayBuffer         = SCEGU_VRAM_BP32_1;

      for (i = 0; i < (1 << 8); i++)
      {
         LUT_r_local[i]     = i;
         LUT_b_local[i]     = i << (8 + 8);
      }

      LUT_r                 = (void*)LUT_r_local;
      LUT_b                 = (void*)LUT_b_local;

   }
   else
   {
      u16 i;
      uint16_t* LUT_r_local = (uint16_t*)(SCEGU_VRAM_BP_2);
      uint16_t* LUT_b_local = (uint16_t*)(SCEGU_VRAM_BP_2) + (1 << 5);

      red_shift             = 6 + 5;
      color_mask            = 0x1F;
      lut_block_count       = (1 << 5) / 8;

      psp->texture          = (void*)(LUT_b_local + (1 << 5));
      psp->draw_buffer      = SCEGU_VRAM_BP_0;
      psp->bpp_log2         = 1;

      pixel_format          = 
         (video_driver_get_pixel_format() == RETRO_PIXEL_FORMAT_0RGB1555) 
         ? GU_PSM_5551 : GU_PSM_5650 ;

      lut_pixel_format      = GU_PSM_T16;

      displayBuffer         = SCEGU_VRAM_BP_1;

      for (i = 0; i < (1 << 5); i++)
      {
         LUT_r_local[i]     = i;
         LUT_b_local[i]     = i << (5 + 6);
      }

      LUT_r                 = (void*)LUT_r_local;
      LUT_b                 = (void*)LUT_b_local;

   }

   psp->tex_filter = video->smooth? GU_LINEAR : GU_NEAREST;

   /* TODO: check if necessary. */
   sceDisplayWaitVblankStart();

   sceGuDisplay(GU_FALSE);

   sceGuStart(GU_DIRECT, psp->main_dList);

   sceGuDrawBuffer(pixel_format, TO_GU_POINTER(psp->draw_buffer),
         SCEGU_VRAM_WIDTH);
   sceGuDispBuffer(SCEGU_SCR_WIDTH, SCEGU_SCR_HEIGHT,
         TO_GU_POINTER(displayBuffer), SCEGU_VRAM_WIDTH);
   sceGuClearColor(0);
   sceGuScissor(0, 0, SCEGU_SCR_WIDTH, SCEGU_SCR_HEIGHT);
   sceGuEnable(GU_SCISSOR_TEST);
   sceGuTexFilter(psp->tex_filter, psp->tex_filter);
   sceGuTexWrap (GU_CLAMP, GU_CLAMP);
   sceGuEnable(GU_TEXTURE_2D);
   sceGuDisable(GU_DEPTH_TEST);
   sceGuCallMode(GU_FALSE);

   sceGuFinish();
   sceGuSync(0, 0);
 
   /* TODO : check if necessary */
   sceDisplayWaitVblankStart();
   sceGuDisplay(GU_TRUE);

   pspDebugScreenSetColorMode(pixel_format);
   pspDebugScreenSetBase(psp->draw_buffer);

   /* fill frame_dList : */
   sceGuStart(GU_CALL, psp->frame_dList);

   sceGuTexMode(pixel_format, 0, 0, GU_FALSE);
   sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB);
   sceGuEnable(GU_BLEND);

   /* green only */
   sceGuBlendFunc(GU_ADD, GU_FIX, GU_FIX, 0x0000FF00, 0xFFFFFFFF);

   sceGuDrawArray(GU_SPRITES, GU_TEXTURE_32BITF | GU_VERTEX_32BITF | 
         GU_TRANSFORM_2D, PSP_FRAME_VERTEX_COUNT, NULL,
         (void*)(psp->frame_coords));
   
   /* restore */
   sceGuBlendFunc(GU_ADD, GU_FIX, GU_FIX, 0xFFFFFFFF, 0xFFFFFFFF);

   sceGuTexMode(lut_pixel_format, 0, 0, GU_FALSE);

   sceGuClutMode(pixel_format, red_shift, color_mask, 0);
   sceGuClutLoad(lut_block_count, LUT_r);

   sceGuDrawArray(GU_SPRITES, GU_TEXTURE_32BITF | GU_VERTEX_32BITF |
         GU_TRANSFORM_2D, PSP_FRAME_VERTEX_COUNT, NULL,
         (void*)(psp->frame_coords));

   sceGuClutMode(pixel_format, 0, color_mask, 0);
   sceGuClutLoad(lut_block_count, LUT_b);
   sceGuDrawArray(GU_SPRITES, GU_TEXTURE_32BITF | GU_VERTEX_32BITF | 
         GU_TRANSFORM_2D, PSP_FRAME_VERTEX_COUNT, NULL,
         (void*)(psp->frame_coords));

   sceGuFinish();

   if (input && input_data)
   {
      settings_t *settings = config_get_ptr();
      pspinput             = input_psp.init(settings->arrays.input_joypad_driver);
      *input               = pspinput ? &input_psp : NULL;
      *input_data          = pspinput;
   }

   psp->vblank_not_reached = true;
   sceKernelRegisterSubIntrHandler(PSP_VBLANK_INT, 0, psp_on_vblank, psp);
   sceKernelEnableSubIntr(PSP_VBLANK_INT, 0);

   psp->keep_aspect        = true;
   psp->should_resize      = true;
   psp->hw_render          = false;

   return psp;
}

#if 0
#define DISPLAY_FPS
#endif

static bool psp_frame(void *data, const void *frame,
      unsigned width, unsigned height, uint64_t frame_count,
      unsigned pitch, const char *msg, video_frame_info_t *video_info)
{
#ifdef DISPLAY_FPS
   uint32_t diff;
   static uint64_t currentTick,lastTick;
   static int frames;
   static float fps                        = 0.0;
#endif
   psp1_video_t *psp                       = (psp1_video_t*)data;

   if (!width || !height)
      return false;

   if (((uint32_t)frame&0x04000000) || (frame == RETRO_HW_FRAME_BUFFER_VALID))
      psp->hw_render = true;
   else if (frame)
      psp->hw_render = false;

   if (!psp->hw_render)
      sceGuSync(0, 0); /* let the core decide when to sync when HW_RENDER */

   pspDebugScreenSetBase(psp->draw_buffer);

   pspDebugScreenSetXY(0,0);

   if (video_info->fps_show)
   {
      pspDebugScreenSetXY(68 - strlen(video_info->fps_text) - 1,0);
      pspDebugScreenPuts(video_info->fps_text);
      pspDebugScreenSetXY(0,1);
   }

   if (msg)
      pspDebugScreenPuts(msg);

   if ((psp->vsync)&&(psp->vblank_not_reached))
      sceDisplayWaitVblankStart();

   psp->vblank_not_reached = true;

#ifdef DISPLAY_FPS
   frames++;
   sceRtcGetCurrentTick(&currentTick);
   diff = currentTick - lastTick;
   if(diff > 1000000)
   {
      fps = (float)frames * 1000000.0 / diff;
      lastTick = currentTick;
      frames = 0;
   }

   pspDebugScreenSetXY(0,0);
   pspDebugScreenPrintf("%f", fps);
#endif

   psp->draw_buffer = FROM_GU_POINTER(sceGuSwapBuffers());

   if (psp->should_resize)
      psp_update_viewport(psp, video_info);

   psp_set_tex_coords(psp->frame_coords, width, height);

   sceGuStart(GU_DIRECT, psp->main_dList);

   sceGuTexFilter(psp->tex_filter, psp->tex_filter);
   sceGuClear(GU_COLOR_BUFFER_BIT);

   /* frame in VRAM ? texture/palette was 
    * set in core so draw directly */
   if (psp->hw_render) 
      sceGuDrawArray(GU_SPRITES, GU_TEXTURE_32BITF | GU_VERTEX_32BITF | 
            GU_TRANSFORM_2D, PSP_FRAME_VERTEX_COUNT, NULL,
            (void*)(psp->frame_coords));
   else
   {
      if (frame)
      {
         sceKernelDcacheWritebackRange(frame,pitch * height);
         sceGuCopyImage(psp->rgb32? GU_PSM_8888 : GU_PSM_5650, ((u32)frame & 0xF) >> psp->bpp_log2,
               0, width, height, pitch >> psp->bpp_log2,
               (void*)((u32)frame & ~0xF), 0, 0, width, psp->texture);
      }
      sceGuTexImage(0, next_pow2(width), next_pow2(height), width, psp->texture);
      sceGuCallList(psp->frame_dList);
   }

   sceGuFinish();

#ifdef HAVE_MENU
   menu_driver_frame(video_info);
#endif

   if(psp->menu.active)
   {
      sceGuSendList(GU_TAIL, psp->menu.dList, &(psp->menu.context_storage));
      sceGuSync(0, 0);
   }

   return true;
}

static void psp_set_nonblock_state(void *data, bool toggle)
{
   psp1_video_t *psp = (psp1_video_t*)data;

   if (psp)
      psp->vsync = !toggle;
}

static bool psp_alive(void *data)
{
   (void)data;
   return true;
}

static bool psp_focus(void *data)
{
   (void)data;
   return true;
}

static bool psp_suppress_screensaver(void *data, bool enable)
{
   (void)data;
   (void)enable;
   return false;
}

static void psp_free(void *data)
{
   psp1_video_t *psp = (psp1_video_t*)data;

   if(!(psp) || !(psp->main_dList))
      return;

   sceDisplayWaitVblankStart();
   sceGuDisplay(GU_FALSE);
   sceGuTerm();

   if (psp->main_dList)
      free(psp->main_dList);
   if (psp->frame_dList)
      free(psp->frame_dList);
   if (psp->frame_coords)
      free(TO_CACHED_PTR(psp->frame_coords));
   if (psp->menu.frame_coords)
      free(TO_CACHED_PTR(psp->menu.frame_coords));
   if (psp->menu.dList)
      free(psp->menu.dList);
   if (psp->menu.frame)
      free(psp->menu.frame);

   free(data);

   sceKernelDisableSubIntr(PSP_VBLANK_INT, 0);
   sceKernelReleaseSubIntrHandler(PSP_VBLANK_INT,0);
}

static void psp_set_texture_frame(void *data, const void *frame, bool rgb32,
                               unsigned width, unsigned height, float alpha)
{
   psp1_video_t *psp = (psp1_video_t*)data;

   (void) rgb32;
   (void) alpha;

#ifdef DEBUG
   /* psp->menu.frame buffer size is (480 * 272)*2 Bytes */
   retro_assert((width*height) < (480 * 272));
#endif

   psp_set_screen_coords(psp->menu.frame_coords, 0, 0,
         SCEGU_SCR_WIDTH, SCEGU_SCR_HEIGHT, 0);
   psp_set_tex_coords(psp->menu.frame_coords, width, height);

   sceKernelDcacheWritebackRange(frame, width * height * 2);

   sceGuStart(GU_DIRECT, psp->main_dList);
   sceGuCopyImage(GU_PSM_4444, 0, 0, width, height, width,
         (void*)frame, 0, 0, width, psp->menu.frame);
   sceGuFinish();

   sceGuStart(GU_SEND, psp->menu.dList);
   sceGuTexMode(GU_PSM_4444, 0, 0, GU_FALSE);
   sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB);
   sceGuTexFilter(GU_LINEAR, GU_LINEAR);
   sceGuTexImage(0, next_pow2(width), next_pow2(height), width, psp->menu.frame);
   sceGuEnable(GU_BLEND);

#if 0
   /* default blending */
   sceGuBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0);
#endif
   sceGuBlendFunc(GU_ADD, GU_FIX, GU_FIX, 0xF0F0F0F0, 0x0F0F0F0F);
;
   sceGuDrawArray(GU_SPRITES, GU_TEXTURE_32BITF | GU_VERTEX_32BITF | 
         GU_TRANSFORM_2D, PSP_FRAME_VERTEX_COUNT, NULL,
         psp->menu.frame_coords);
   sceGuFinish();

}

static void psp_set_texture_enable(void *data, bool state, bool full_screen)
{
   (void) full_screen;

   psp1_video_t *psp = (psp1_video_t*)data;

   if (psp)
      psp->menu.active = state;
}

static void psp_update_viewport(psp1_video_t* psp,
      video_frame_info_t *video_info)
{
   int x                = 0;
   int y                = 0;
   float device_aspect  = ((float)SCEGU_SCR_WIDTH) / SCEGU_SCR_HEIGHT;
   float width          = SCEGU_SCR_WIDTH;
   float height         = SCEGU_SCR_HEIGHT;
   settings_t *settings = config_get_ptr();

   if (settings->bools.video_scale_integer)
   {
      video_viewport_get_scaled_integer(&psp->vp, SCEGU_SCR_WIDTH,
            SCEGU_SCR_HEIGHT, video_driver_get_aspect_ratio(), psp->keep_aspect);
      width  = psp->vp.width;
      height = psp->vp.height;
   }
   else if (psp->keep_aspect)
   {
#if defined(HAVE_MENU)
      if (settings->uints.video_aspect_ratio_idx == ASPECT_RATIO_CUSTOM)
      {
         x      = video_info->custom_vp_x;
         y      = video_info->custom_vp_y;
         width  = video_info->custom_vp_width;
         height = video_info->custom_vp_height;
      }
      else
#endif
      {
         float delta;
         float desired_aspect = video_driver_get_aspect_ratio();

         if ((fabsf(device_aspect - desired_aspect) < 0.0001f)
               || (fabsf((16.0/9.0) - desired_aspect) < 0.02f))
         {
            /* If the aspect ratios of screen and desired aspect 
             * ratio are sufficiently equal (floating point stuff),
             * assume they are actually equal.
             */
         }
         else if (device_aspect > desired_aspect)
         {
            delta = (desired_aspect / device_aspect - 1.0f)
               / 2.0f + 0.5f;
            x     = (int)roundf(width * (0.5f - delta));
            width = (unsigned)roundf(2.0f * width * delta);
         }
         else
         {
            delta  = (device_aspect / desired_aspect - 1.0f)
               / 2.0f + 0.5f;
            y      = (int)roundf(height * (0.5f - delta));
            height = (unsigned)roundf(2.0f * height * delta);
         }
      }

      psp->vp.x      = x;
      psp->vp.y      = y;
      psp->vp.width  = width;
      psp->vp.height = height;
   }
   else
   {
      psp->vp.x = psp->vp.y = 0;
      psp->vp.width = width;
      psp->vp.height = height;
   }

   psp->vp.width += psp->vp.width&0x1;
   psp->vp.height += psp->vp.height&0x1;

   psp_set_screen_coords(psp->frame_coords, psp->vp.x,
         psp->vp.y, psp->vp.width, psp->vp.height, psp->rotation);

   psp->should_resize = false;

}

static void psp_set_rotation(void *data, unsigned rotation)
{
   psp1_video_t *psp = (psp1_video_t*)data;

   if (!psp)
      return;

   psp->rotation = rotation;
   psp->should_resize = true;
}
static void psp_set_filtering(void *data, unsigned index, bool smooth)
{
   psp1_video_t *psp = (psp1_video_t*)data;

   if (psp)
      psp->tex_filter = smooth? GU_LINEAR : GU_NEAREST;
}

static void psp_set_aspect_ratio(void *data, unsigned aspect_ratio_idx)
{
   psp1_video_t *psp = (psp1_video_t*)data;

   switch (aspect_ratio_idx)
   {
      case ASPECT_RATIO_SQUARE:
         video_driver_set_viewport_square_pixel();
         break;

      case ASPECT_RATIO_CORE:
         video_driver_set_viewport_core();
         break;

      case ASPECT_RATIO_CONFIG:
         video_driver_set_viewport_config();
         break;

      default:
         break;
   }

   video_driver_set_aspect_ratio_value(aspectratio_lut[aspect_ratio_idx].value);

   psp->keep_aspect = true;
   psp->should_resize = true;
}

static void psp_apply_state_changes(void *data)
{
   psp1_video_t *psp = (psp1_video_t*)data;

   if (psp)
      psp->should_resize = true;
}

static void psp_viewport_info(void *data, struct video_viewport *vp)
{
   psp1_video_t *psp = (psp1_video_t*)data;
   
   if (psp)
      *vp = psp->vp;
}

static const video_poke_interface_t psp_poke_interface = {
   NULL,
   NULL,
   NULL,
   psp_set_filtering,
   NULL, /* get_video_output_size */
   NULL, /* get_video_output_prev */
   NULL, /* get_video_output_next */
   NULL, /* get_current_framebuffer */
   NULL, /* get_proc_address */
   psp_set_aspect_ratio,
   psp_apply_state_changes,
#ifdef HAVE_MENU
   psp_set_texture_frame,
   psp_set_texture_enable,
#endif
   NULL,
   NULL,
   NULL
};

static void psp_get_poke_interface(void *data,
      const video_poke_interface_t **iface)
{
   (void)data;
   *iface = &psp_poke_interface;
}

static bool psp_read_viewport(void *data, uint8_t *buffer, bool is_idle)
{
   void* src_buffer;
   int i, j, src_bufferwidth, src_pixelformat, src_x, src_y, src_x_max, src_y_max;
   uint8_t* dst = buffer;
   psp1_video_t *psp = (psp1_video_t*)data;

   (void)data;
   (void)buffer;

   sceDisplayGetFrameBuf(&src_buffer, &src_bufferwidth, &src_pixelformat, PSP_DISPLAY_SETBUF_NEXTFRAME);

   src_x     = (psp->vp.x > 0)? psp->vp.x : 0;
   src_y     = (psp->vp.y > 0)? psp->vp.y : 0;
   src_x_max = ((psp->vp.x + psp->vp.width) < src_bufferwidth)? (psp->vp.x + psp->vp.width): src_bufferwidth;
   src_y_max = ((psp->vp.y + psp->vp.height) < SCEGU_SCR_HEIGHT)? (psp->vp.y + psp->vp.height): SCEGU_SCR_HEIGHT;

   switch(src_pixelformat)
   {
   case PSP_DISPLAY_PIXEL_FORMAT_565:
      for (j = (src_y_max - 1); j >= src_y ; j--)
      {
         uint16_t* src = (uint16_t*)src_buffer + src_bufferwidth * j + src_x;
         for (i = src_x; i < src_x_max; i++)
         {

            *(dst++) = ((*src) >> 11) << 3;
            *(dst++) = (((*src) >> 5) << 2) &0xFF;
            *(dst++) = ((*src) & 0x1F) << 3;
            src++;
         }
      }
      return true;

   case PSP_DISPLAY_PIXEL_FORMAT_5551:
      for (j = (src_y_max - 1); j >= src_y ; j--)
      {
         uint16_t* src = (uint16_t*)src_buffer + src_bufferwidth * j + src_x;
         for (i = src_x; i < src_x_max; i++)
         {

            *(dst++) = (((*src) >> 10) << 3) &0xFF;
            *(dst++) = (((*src) >> 5) << 3) &0xFF;
            *(dst++) = ((*src) & 0x1F) << 3;
            src++;
         }
      }
      return true;

   case PSP_DISPLAY_PIXEL_FORMAT_4444:
      for (j = (src_y_max - 1); j >= src_y ; j--)
      {
         uint16_t* src = (uint16_t*)src_buffer + src_bufferwidth * j + src_x;
         for (i = src_x; i < src_x_max; i++)
         {

            *(dst++) = ((*src) >> 4) & 0xF0;
            *(dst++) = (*src)        & 0xF0;
            *(dst++) = ((*src) << 4) & 0xF0;
            src++;
         }
      }
      return true;

   case PSP_DISPLAY_PIXEL_FORMAT_8888:
      for (j = (src_y_max - 1); j >= src_y ; j--)
      {
         uint32_t* src = (uint32_t*)src_buffer + src_bufferwidth * j + src_x;
         for (i = src_x; i < src_x_max; i++)
         {

            *(dst++) = ((*src) >> 16) & 0xFF;
            *(dst++) = ((*src) >> 8 ) & 0xFF;
            *(dst++) = (*src) & 0xFF;
            src++;
         }
      }
      return true;
   }


   return false;
}

static bool psp_set_shader(void *data,
      enum rarch_shader_type type, const char *path)
{
   (void)data;
   (void)type;
   (void)path;

   return false; 
}

video_driver_t video_psp1 = {
   psp_init,
   psp_frame,
   psp_set_nonblock_state,
   psp_alive,
   psp_focus,
   psp_suppress_screensaver,
   NULL, /* has_windowed */
   psp_set_shader,
   psp_free,
   "psp1",
   NULL, /* set_viewport */
   psp_set_rotation,
   psp_viewport_info,
   psp_read_viewport,
   NULL, /* read_frame_raw */
#ifdef HAVE_OVERLAY
   NULL,
#endif
   psp_get_poke_interface
};
