/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2025 Mauricio G.
 *
 * 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 <stdlib.h>
#include <string.h>

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

#include "debug.h"
#include "system.h"
#include "lcd.h"
#include "lcd-remote.h"

static fb_remote_data *top_fb = NULL;
static bool borders_cleared = false;

extern struct frame_buffer_t lcd_remote_framebuffer_default;

/*
 * Copy and rotate the dirty rectangle from the Rockbox remote framebuffer
 * (320x240, row-major) to the 3DS portrait-native top screen GPU buffer
 * (240x400, column-major). The 320-pixel-wide image is centered vertically
 * in the 400-row physical display, leaving 40 rows of black above and below.
 */
static void copy_framebuffer_remote(u16 *dest, int dest_width, int dest_height,
                                    const u16 *source, int source_width,
                                    int src_x, int src_y,
                                    int width, int height)
{
    int y_offset = (dest_height - source_width) / 2;

    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 * (y_offset + src_x + x)] =
                source[src_row + x];
        }
    }
}

void lcd_remote_init_device(void)
{
    u32 bufsize = FRAMEBUFFER_REMOTE_SIZE;

    top_fb = (fb_remote_data *) linearAlloc(bufsize);
    if (top_fb == NULL) {
        exit(EXIT_FAILURE);
    }

    memset(top_fb, 0x00, bufsize);

    lcd_remote_framebuffer_default.fb_remote_ptr = top_fb;
}

void lcd_remote_update(void)
{
    lcd_remote_update_rect(0, 0, LCD_REMOTE_WIDTH, LCD_REMOTE_HEIGHT);
}

void lcd_remote_update_rect(int x_start, int y_start, int width, int height)
{
    if (!top_fb)
        return;

    /* Clip to remote LCD dimensions */
    if (x_start < 0) { width += x_start; x_start = 0; }
    if (y_start < 0) { height += y_start; y_start = 0; }
    if (x_start + width > LCD_REMOTE_WIDTH)
        width = LCD_REMOTE_WIDTH - x_start;
    if (y_start + height > LCD_REMOTE_HEIGHT)
        height = LCD_REMOTE_HEIGHT - y_start;
    if (width <= 0 || height <= 0)
        return;

    u16 fb_width, fb_height;
    u8 *fb = gfxGetFramebuffer(GFX_TOP, GFX_LEFT, &fb_width, &fb_height);

    /* Clear the black borders once on first update; they never change.
     * Written to both swap buffers. */
    if (!borders_cleared) {
        int y_offset = (fb_height - LCD_REMOTE_WIDTH) / 2;
        memset(fb, 0, (size_t)fb_width * y_offset * 2);
        memset(fb + (size_t)fb_width * (y_offset + LCD_REMOTE_WIDTH) * 2, 0,
               (size_t)fb_width * (fb_height - y_offset - LCD_REMOTE_WIDTH) * 2);
        GSPGPU_FlushDataCache(fb, (u32)fb_width * fb_height * 2);
        gfxScreenSwapBuffers(GFX_TOP, true);

        fb = gfxGetFramebuffer(GFX_TOP, GFX_LEFT, &fb_width, &fb_height);
        memset(fb, 0, (size_t)fb_width * y_offset * 2);
        memset(fb + (size_t)fb_width * (y_offset + LCD_REMOTE_WIDTH) * 2, 0,
               (size_t)fb_width * (fb_height - y_offset - LCD_REMOTE_WIDTH) * 2);
        GSPGPU_FlushDataCache(fb, (u32)fb_width * fb_height * 2);
        borders_cleared = true;
    }

    /* Write the dirty rect to both swap buffers to avoid flicker */
    copy_framebuffer_remote((u16 *)fb, fb_width, fb_height,
                            (u16 *)top_fb, LCD_REMOTE_WIDTH,
                            x_start, y_start, width, height);
    GSPGPU_FlushDataCache(fb, (u32)fb_width * fb_height * 2);
    gfxScreenSwapBuffers(GFX_TOP, true);

    fb = gfxGetFramebuffer(GFX_TOP, GFX_LEFT, &fb_width, &fb_height);
    copy_framebuffer_remote((u16 *)fb, fb_width, fb_height,
                            (u16 *)top_fb, LCD_REMOTE_WIDTH,
                            x_start, y_start, width, height);
    GSPGPU_FlushDataCache(fb, (u32)fb_width * fb_height * 2);
}

void lcd_remote_on(void) {}
void lcd_remote_off(void) {}
void lcd_remote_set_contrast(int val) { (void)val; }
void lcd_remote_set_invert_display(bool yesno) { (void)yesno; }
unsigned lcd_remote_color_to_native(unsigned color) { return color; }
