/*
 * Display_Image.c
 *
 * Created: Thu Nov 25 2021 13:15:42
 *  Author Chris
 */
#include "Display_Image.h"

#include <math.h>
#include "Display_Color.h"
#include "Display_Shapes.h"


// ============================================================================================
// Defines


// ============================================================================================
// Variables
static Display_Image_Buffer**   _Current_Buffer;
static Display_Color            _Transparent_Color;

static const uint8_t IMAGE_DEF_WIDTH 		= 0;
static const uint8_t IMAGE_DEF_HEIGHT       = 1;
static const uint8_t IMAGE_DEF_DATA_SIZE 	= 2;
static const uint8_t IMAGE_DEF_DATA_OFFSET	= 3;


// ============================================================================================
// Function Declarations


/*******************************************************************
	Functions
*******************************************************************/
void Display_Image_Init(Display_Image_Buffer** current_buffer, Display_Color transparent_color)
{
    _Current_Buffer = current_buffer;
    _Transparent_Color  = transparent_color;
}

void Display_Image_Draw_Color(int16_t x, int16_t y, Image_Color* image)
{
	Display_Image_Draw_Color_Alpha(x, y, image, UINT8_MAX);
}

void Display_Image_Draw_Color_Alpha(int16_t x, int16_t y, Image_Color* image, uint8_t alpha)
{
    if(image == NULL) { 
        return;
    }
    
    // Early exit for fully transparent
    if(alpha == 0) {
        return;
    }

    int16_t Width = image[IMAGE_DEF_WIDTH];
    int16_t Height = image[IMAGE_DEF_HEIGHT];
    
    // Early exit for completely off-screen images
    if(x >= DISPLAY_WIDTH || y >= DISPLAY_HEIGHT || x + Width <= 0 || y + Height <= 0) {
        return;
    }

    // Calculate clipping bounds
    int16_t Start_X = (x < 0) ? -x : 0;
    int16_t Start_Y = (y < 0) ? -y : 0;
    int16_t End_X = (x + Width > DISPLAY_WIDTH) ? DISPLAY_WIDTH - x : Width;
    int16_t End_Y = (y + Height > DISPLAY_HEIGHT) ? DISPLAY_HEIGHT - y : Height;

    // Optimized rendering based on alpha value
    if(alpha == 255)
    {
        // Fully opaque - use direct copy for maximum performance
        for(int16_t iy = Start_Y; iy < End_Y; iy++)
        {
            int16_t Screen_Y = y + iy;
            uint32_t Src_Row_Offset = IMAGE_DEF_DATA_OFFSET + iy * Width;
            
            for(int16_t ix = Start_X; ix < End_X; ix++)
            {
                Display_Color Pixel_Color = image[Src_Row_Offset + ix];
                
                if(Pixel_Color != _Transparent_Color) {
                    (*_Current_Buffer)->Dim_2[Screen_Y][x + ix] = Pixel_Color;
                }
            }
        }
    }
    else
    {
        // Alpha blending required
        for(int16_t iy = Start_Y; iy < End_Y; iy++)
        {
            int16_t Screen_Y = y + iy;
            uint32_t Src_Row_Offset = IMAGE_DEF_DATA_OFFSET + iy * Width;
            
            for(int16_t ix = Start_X; ix < End_X; ix++)
            {
                Display_Color Src_Pixel = image[Src_Row_Offset + ix];
                
                if(Src_Pixel != _Transparent_Color) {
                    int16_t Screen_X = x + ix;
                    Display_Color Dst_Pixel = (*_Current_Buffer)->Dim_2[Screen_Y][Screen_X];
                    Display_Color Blended_Pixel = Display_Color_Blend_RGB565(Src_Pixel, Dst_Pixel, alpha);
                    (*_Current_Buffer)->Dim_2[Screen_Y][Screen_X] = Blended_Pixel;
                }
            }
        }
    }
}

void Display_Image_Draw_Color_Scaled(int16_t x, int16_t y, Image_Color* image, float scale)
{
    Display_Image_Draw_Color_Scaled_Alpha(x, y, image, scale, 255);
}

void Display_Image_Draw_Color_Scaled_Alpha(int16_t x, int16_t y, Image_Color* image, float scale, uint8_t alpha)
{
    if(image == NULL || scale <= 0.0f) {
        return;
    }
    
    // Early exit for fully transparent
    if(alpha == 0) {
        return;
    }

    int16_t Original_Width = image[IMAGE_DEF_WIDTH];
    int16_t Original_Height = image[IMAGE_DEF_HEIGHT];
    
    // Calculate scaled dimensions
    int16_t Scaled_Width = (int16_t)(Original_Width * scale);
    int16_t Scaled_Height = (int16_t)(Original_Height * scale);
    
    // Early exit for completely off-screen images
    if(x >= DISPLAY_WIDTH || y >= DISPLAY_HEIGHT || x + Scaled_Width <= 0 || y + Scaled_Height <= 0) {
        return;
    }

    // Calculate clipping bounds
    int16_t Start_X = (x < 0) ? -x : 0;
    int16_t Start_Y = (y < 0) ? -y : 0;
    int16_t End_X = (x + Scaled_Width > DISPLAY_WIDTH) ? DISPLAY_WIDTH - x : Scaled_Width;
    int16_t End_Y = (y + Scaled_Height > DISPLAY_HEIGHT) ? DISPLAY_HEIGHT - y : Scaled_Height;

    // Pre-calculate fixed-point inverse scale factors
    uint32_t Inv_Scale_X = (uint32_t)((65536.0f) / scale);  // 16.16 fixed point
    uint32_t Inv_Scale_Y = (uint32_t)((65536.0f) / scale);

    // Optimized scaling with alpha blending
    if(alpha == 255) {
        // Fully opaque - direct buffer access for maximum performance
        for(int16_t iy = Start_Y; iy < End_Y; iy++)
        {
            // Calculate source Y coordinate using fixed-point math
            int16_t Orig_Y = (iy * Inv_Scale_Y) >> 16;
            
            if(Orig_Y >= Original_Height) {
                break;
            }
            
            // Pre-calculate source row offset
            uint32_t Src_Row_Offset = IMAGE_DEF_DATA_OFFSET + Orig_Y * Original_Width;
            int16_t Screen_Y = y + iy;
            
            for(int16_t ix = Start_X; ix < End_X; ix++)
            {
                // Calculate source X coordinate using fixed-point math
                int16_t Orig_X = (ix * Inv_Scale_X) >> 16;
                
                if(Orig_X >= Original_Width) {
                    break;
                }
                
                Display_Color Pixel_Color = image[Src_Row_Offset + Orig_X];
                
                if(Pixel_Color != _Transparent_Color) {
                    // Direct buffer write
                    (*_Current_Buffer)->Dim_2[Screen_Y][x + ix] = Pixel_Color;
                }
            }
        }
    }
    else {
        // Alpha blending required
        for(int16_t iy = Start_Y; iy < End_Y; iy++)
        {
            // Calculate source Y coordinate using fixed-point math
            int16_t Orig_Y = (iy * Inv_Scale_Y) >> 16;
            
            if(Orig_Y >= Original_Height) {
                break;
            }
            
            // Pre-calculate source row offset
            uint32_t Src_Row_Offset = IMAGE_DEF_DATA_OFFSET + Orig_Y * Original_Width;
            int16_t Screen_Y = y + iy;
            
            for(int16_t ix = Start_X; ix < End_X; ix++)
            {
                // Calculate source X coordinate using fixed-point math
                int16_t Orig_X = (ix * Inv_Scale_X) >> 16;
                
                if(Orig_X >= Original_Width) {
                    break;
                }
                
                Display_Color Src_Pixel = image[Src_Row_Offset + Orig_X];
                
                if(Src_Pixel != _Transparent_Color) {
                    int16_t Screen_X = x + ix;
                    Display_Color Dst_Pixel = (*_Current_Buffer)->Dim_2[Screen_Y][Screen_X];
                    Display_Color Blended_Pixel = Display_Color_Blend_RGB565(Src_Pixel, Dst_Pixel, alpha);
                    (*_Current_Buffer)->Dim_2[Screen_Y][Screen_X] = Blended_Pixel;
                }
            }
        }
    }
}

void Display_Image_Draw_Color_Rotated(int16_t x, int16_t y, Image_Color* image, uint16_t angle)
{
    Display_Image_Draw_Color_Rotated_Alpha(x, y, image, angle, UINT8_MAX);
}

void Display_Image_Draw_Color_Rotated_Alpha(int16_t x, int16_t y, Image_Color* image, uint16_t angle, uint8_t alpha)
{
    if(image == NULL) { 
        return; 
    }
    
    // Early exit for fully transparent
    if(alpha == 0) {
        return;
    }

    int16_t Height = image[IMAGE_DEF_HEIGHT];
    int16_t Width = image[IMAGE_DEF_WIDTH];
    
    // Adjust coordinates so rotation center is at (x, y)
    x += (Width >> 1);
    y += (Height >> 1);

    // Convert angle to radians
    float Angle_Rad = (angle % 360) * M_PI / 180.0f;
    float Cos_Angle = cosf(Angle_Rad);
    float Sin_Angle = sinf(Angle_Rad);
    
    // Calculate image center
    float Center_X = Width / 2.0f;
    float Center_Y = Height / 2.0f;
    
    // Calculate bounding box of rotated image
    float Corners_X[4] = {0, Width-1, Width-1, 0};
    float Corners_Y[4] = {0, 0, Height-1, Height-1};
    
    int16_t Min_X = 0, Max_X = 0, Min_Y = 0, Max_Y = 0;
    
    for(int i = 0; i < 4; i++) {
        float rx = (Corners_X[i] - Center_X) * Cos_Angle - (Corners_Y[i] - Center_Y) * Sin_Angle;
        float ry = (Corners_X[i] - Center_X) * Sin_Angle + (Corners_Y[i] - Center_Y) * Cos_Angle;
        
        if(i == 0) {
            Min_X = Max_X = (int16_t)roundf(rx);
            Min_Y = Max_Y = (int16_t)roundf(ry);
        } else {
            if(rx < Min_X) Min_X = (int16_t)roundf(rx);
            if(rx > Max_X) Max_X = (int16_t)roundf(rx);
            if(ry < Min_Y) Min_Y = (int16_t)roundf(ry);
            if(ry > Max_Y) Max_Y = (int16_t)roundf(ry);
        }
    }
    
    // Optimized rendering based on alpha value
    if(alpha == 255)
    {
        // Fully opaque - use direct pixel writes for maximum performance
        for(int16_t dy = Min_Y; dy <= Max_Y; dy++)
        {
            for(int16_t dx = Min_X; dx <= Max_X; dx++)
            {
                // Reverse rotation to find source pixel
                float Src_X = dx * Cos_Angle + dy * Sin_Angle + Center_X;
                float Src_Y = -dx * Sin_Angle + dy * Cos_Angle + Center_Y;
                
                // Check if source coordinates are within image bounds
                int16_t ix = (int16_t)roundf(Src_X);
                int16_t iy = (int16_t)roundf(Src_Y);
                
                if(ix >= 0 && ix < Width && iy >= 0 && iy < Height)
                {
                    Display_Color Pixel = image[IMAGE_DEF_DATA_OFFSET + iy * Width + ix];
                    if(Pixel != _Transparent_Color) {
                        // Calculate screen coordinates
                        int16_t Screen_X = x + dx;
                        int16_t Screen_Y = y + dy;
                        
                        // Bounds check for screen buffer
                        if(Screen_X >= 0 && Screen_X < DISPLAY_WIDTH && Screen_Y >= 0 && Screen_Y < DISPLAY_HEIGHT) {
                            (*_Current_Buffer)->Dim_2[Screen_Y][Screen_X] = Pixel;
                        }
                    }
                }
            }
        }
    }
    else
    {
        // Alpha blending required
        for(int16_t dy = Min_Y; dy <= Max_Y; dy++)
        {
            for(int16_t dx = Min_X; dx <= Max_X; dx++)
            {
                // Reverse rotation to find source pixel
                float Src_X = dx * Cos_Angle + dy * Sin_Angle + Center_X;
                float Src_Y = -dx * Sin_Angle + dy * Cos_Angle + Center_Y;
                
                // Check if source coordinates are within image bounds
                int16_t ix = (int16_t)roundf(Src_X);
                int16_t iy = (int16_t)roundf(Src_Y);
                
                if(ix >= 0 && ix < Width && iy >= 0 && iy < Height) {
                    Display_Color Src_Pixel = image[IMAGE_DEF_DATA_OFFSET + iy * Width + ix];
                    if(Src_Pixel != _Transparent_Color) {
                        // Calculate screen coordinates
                        int16_t Screen_X = x + dx;
                        int16_t Screen_Y = y + dy;
                        
                        // Bounds check for screen buffer
                        if(Screen_X >= 0 && Screen_X < DISPLAY_WIDTH && Screen_Y >= 0 && Screen_Y < DISPLAY_HEIGHT) {
                            Display_Color Dst_Pixel = (*_Current_Buffer)->Dim_2[Screen_Y][Screen_X];
                            Display_Color Blended_Pixel = Display_Color_Blend_RGB565(Src_Pixel, Dst_Pixel, alpha);
                            (*_Current_Buffer)->Dim_2[Screen_Y][Screen_X] = Blended_Pixel;
                        }
                    }
                }
            }
        }
    }
}

void Display_Image_Draw_Alpha(int16_t x, int16_t y, Image_Alpha* image, Display_Color color)
{
	if(image == NULL) { return; }

	int16_t Height = image[IMAGE_DEF_HEIGHT];
	int16_t Width = image[IMAGE_DEF_WIDTH];
	
	for(int16_t iy = 0; iy < Height ; iy++)
	{
		for (int16_t ix = 0; ix < Width; ix++)
		{
			if(image[IMAGE_DEF_DATA_OFFSET + iy * Width + ix] == UINT8_MAX) {
				Display_Shapes_Draw_Pixel_Safe(x + ix, y + iy, color);
			}
			else if(image[IMAGE_DEF_DATA_OFFSET + iy * Width + ix] > 0) {
				// This is only temporary here...
				// Color needs to be scaled according to alpha value from image array
				// Skipped for now, as only threshold images will be applied
				Display_Shapes_Draw_Pixel_Safe(x + ix, y + iy, color);
			}
		}
	}
}

uint16_t Display_Image_Get_Width(Image_Color* image)
{
	if(image == NULL) { return 0; }

	return image[IMAGE_DEF_WIDTH];
}

uint16_t Display_Image_Get_Height(Image_Color* image)
{
	if(image == NULL) { return 0; }

	return image[IMAGE_DEF_HEIGHT];
}

uint16_t Display_Image_Get_Scaled_Width(Image_Color* image, float scale)
{
	if(image == NULL || scale <= 0.0f) { return 0; }
	
	uint16_t Original_Width = image[IMAGE_DEF_WIDTH];
	return (uint16_t)(Original_Width * scale);
}

uint16_t Display_Image_Get_Scaled_Height(Image_Color* image, float scale)
{
	if(image == NULL || scale <= 0.0f) { return 0; }
	
	uint16_t Original_Height = image[IMAGE_DEF_HEIGHT];
	return (uint16_t)(Original_Height * scale);
}


/*******************************************************************
	Internal Functions
*******************************************************************/


