/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2006 Dan Everton
 *
 * This program 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 Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include <math.h>
#include <stdlib.h>
#include <string.h>

#include <3ds/gfx.h>
#ifdef RGB565
#undef RGB565
#endif

#include <3ds/allocator/linear.h>
#include <3ds/console.h>
#include <3ds/services/cfgu.h>

#include "debug.h"
#include "system.h"
#include "lcd.h"
#ifdef HAVE_REMOTE_LCD
#include "lcd-remote.h"
#endif
#include "button-ctru.h"
#include "screendump.h"
#include "lcd-target.h"

extern struct frame_buffer_t lcd_framebuffer_default;

/*#define LOGF_ENABLE*/
#include "logf.h"

fb_data *dev_fb = 0;

static u8 system_model = CFG_MODEL_3DS;

/*
 * Copy and rotate the Rockbox framebuffer (320x240, row-major) to the 3DS
 * GPU framebuffer (240x320, column-major with rows rotated 90° CW).
 * Only the dirty rectangle [src_x, src_x+width) × [src_y, src_y+height)
 * is copied; the rest of the GPU framebuffer is left untouched.
 */
static void copy_framebuffer_16(u16 *dest, int dest_width,
                                const u16 *source, int source_width,
                                int src_x, int src_y,
                                int width, int height)
{
    for (int y = 0; y < height; ++y) {
        int src_row = (src_y + y) * source_width + src_x;
        int dst_col = dest_width - (src_y + y) - 1;
        for (int x = 0; x < width; ++x) {
            dest[dst_col + dest_width * (src_x + x)] = source[src_row + x];
        }
    }
}

/* lcd-as-memframe.c */
extern void lcd_copy_buffer_rect(fb_data *dst, const fb_data *src,
                                 int width, int height);

void lcd_update_rect(int x, int y, int width, int height)
{
    if (x + width > LCD_WIDTH)
        width = LCD_WIDTH - x; /* Clip right */
    if (x < 0)
        width += x, x = 0; /* Clip left */
    if (width <= 0)
        return; /* nothing left to do */

    if (y + height > LCD_HEIGHT)
        height = LCD_HEIGHT - y; /* Clip bottom */
    if (y < 0)
        height += y, y = 0; /* Clip top */
    if (height <= 0)
        return; /* nothing left to do */

    /* Copy dirty rect from Rockbox framebuffer to dev_fb (same layout) */
    fb_data *dst = LCD_FRAMEBUF_ADDR(x, y);
    fb_data *src = FBADDR(x, y);

    if (width < LCD_WIDTH)
    {
        /* Not full width - do line-by-line */
        lcd_copy_buffer_rect(dst, src, width, height);
    }
    else
    {
        /* Full width - copy as one line */
        lcd_copy_buffer_rect(dst, src, LCD_WIDTH * height, 1);
    }

    /* Rotate and flush the dirty rect from dev_fb to the GPU framebuffer.
     * With double buffering, gfxGetFramebuffer returns the current back
     * buffer. We write to it, swap (making it front/displayed), then write
     * the same rect to the new back buffer so both stay in sync. */
    u16 fb_width, fb_height;

    u8 *fb = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &fb_width, &fb_height);
    copy_framebuffer_16((u16 *)fb, fb_width,
                        (u16 *)dev_fb, LCD_WIDTH,
                        x, y, width, height);
    GSPGPU_FlushDataCache(fb, (u32)fb_width * fb_height * 2);
    gfxScreenSwapBuffers(GFX_BOTTOM, true);

    /* Write the same dirty rect to the other buffer too */
    fb = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &fb_width, &fb_height);
    copy_framebuffer_16((u16 *)fb, fb_width,
                        (u16 *)dev_fb, LCD_WIDTH,
                        x, y, width, height);
    GSPGPU_FlushDataCache(fb, (u32)fb_width * fb_height * 2);
}

void lcd_update(void)
{
    /* update a full screen rect */
    lcd_update_rect(0, 0, LCD_WIDTH, LCD_HEIGHT);
}

void sys_console_init(void)
{
    /* Both screens RGB565 (2 bytes/pixel) to match Rockbox's fb_data type.
     * No console on top screen — it's used entirely by the remote LCD. */
    gfxInit(GSP_RGB565_OES, GSP_RGB565_OES, false);
}

void lcd_init_device(void)
{
    gfxSetDoubleBuffering(GFX_BOTTOM, true);

    /* hidInit(); */

    u16 fb_width, fb_height;
    gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &fb_width, &fb_height);
    u32 bufsize = fb_width * fb_height * 2;

    dev_fb = (fb_data *) linearAlloc(bufsize);
    if (dev_fb == NULL) {
        logf("could not allocate dev_fb size %d bytes\n",
               bufsize);
        exit(EXIT_FAILURE);
    }

    memset(dev_fb, 0x00, bufsize);

    /* Point Rockbox's framebuffer at our 3DS GPU buffer */
    lcd_framebuffer_default.fb_ptr = dev_fb;
    /* Set dpi value from system model */
    CFGU_GetSystemModel(&system_model);
}

void lcd_shutdown(void)
{
    if (dev_fb) {
        linearFree(dev_fb);
        dev_fb = 0;
    }

    /* hidExit(); */
    gfxExit();
}

int lcd_get_dpi(void)
{
    /* link: https://www.reddit.com/r/nintendo/comments/2uzk5y/informative_post_about_dpi_on_the_new_3ds/ */
    /* all values for bottom lcd */
    float dpi = 96.0f;
    switch(system_model) {
    case CFG_MODEL_3DS:
        dpi = 132.45f;
    break;
    case CFG_MODEL_3DSXL:
        dpi = 95.69f;
     break;
    case CFG_MODEL_N3DS:
        dpi = 120.1f;
    break;
    case CFG_MODEL_2DS:
        dpi = 132.45f;
    break;
    case CFG_MODEL_N3DSXL:
        dpi = 95.69f;
    break;
    case CFG_MODEL_N2DSXL:
        dpi = 95.69f;
    break;
    default:
    break;
    };
    return (int) roundf(dpi);
}

