/*
 * Display.c
 *
 * Created: Sun Mar 21 2021 15:52:12
 *  Author Chris
 */
#include "Display.h"

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

#include "hardware/dma.h"

#include "Screens.h"
#include "Display_SPI.h"
#include "Display_Init.h"
#include "Display_Font.h"
#include "Display_Color.h"
#include "Display_Image.h"
#include "Display_Touch.h"
#include "Display_Shapes.h"
#include "Display_Objects.h"
#include "Display_Render_Simple.h"
#include "Display_Render_Complex.h"
#include "Display_Message_Box_Icons.h"

#include "Easings.h"


// ============================================================================================
// Defines
#define DEG2RAD		(float)(M_PI / 180)


// ============================================================================================
// Variables
static Display_Image_Buffer 	_Image_Buffer; 
static Display_Image_Buffer		_Image_Buffer_Backup; 
static Display_Image_Buffer* 	_Current_Buffer;

static int 					_DMA_Channel_Copy_Buffer;
static dma_channel_config 	_DMA_Config_Copy_Buffer;

static int _Touched_Button_Return_Value;

static bool _Object_Selected 				= false;
static uint _Frame_Counter 					= 0;
static bool _Touch_Initialized				= false;
static bool _Draw_Touch_Reference_Points 	= false;
static bool _Draw_Center_Lines				= false;

static Screen_Transition_Settings_t _Transition_Settings;


// ============================================================================================
// Function Declarations
void Display_Draw_Style     (Coordinates* coordinates, Style* style, uint32_t content_width, uint32_t content_height, bool do_draw);
void Display_Draw_Focused   (Coordinates* coordinates, uint32_t width, uint32_t height);

Animation_State Display_Animation_Tick(Display_Object* object);

void Display_Draw_Touch_Refernce_Points(Display_Color color);
void Display_Draw_Touch_Marker(Display_Color color);
void Display_Draw_Center_Lines(Display_Color color);

void 			Display_Check_Button_Touch(int16_t x, int16_t y);
void 			Display_Object_Select_Next(void);
void 			Display_Object_Select_Previous(void);
Display_Object* Display_Get_Selected_Object(void); 
int				Display_Get_Index_Of_Object(Display_Object* object);

void Display_Screen_Transition_Tick();
void Display_Buffer_Shift_Left(uint32_t steps);
void Display_Buffer_Shift_Right(uint32_t steps);
void Display_Buffer_Shift_Up(uint32_t steps);
void Display_Buffer_Shift_Down(uint32_t steps);

void Display_Set_Current_Image_Buffer(Display_Image_Buffer* buffer);
void Display_Copy_Buffer(Display_Image_Buffer *src, Display_Image_Buffer *dest);


/*******************************************************************
	Functions
*******************************************************************/
void Display_Init(Display_Color initial_color, bool send_buffer, bool init_touch)
{
	_Touch_Initialized = init_touch;
	
	_Touched_Button_Return_Value = -1;

	Display_Set_Current_Image_Buffer(&_Image_Buffer);


	_Transition_Settings.Direction_Out = TRANSITION_NONE;
	_Transition_Settings.Direction_In = TRANSITION_NONE;
	_Transition_Settings.Offset.X	= 0;
	_Transition_Settings.Offset.Y	= 0;
	_Transition_Settings.Type = LINEAR;
	_Transition_Settings.Frame_Duration = 0;
	_Transition_Settings.Step = 0;
	_Transition_Settings.Position_In = 0;
	_Transition_Settings.Position_Out = 0;


	_DMA_Channel_Copy_Buffer = dma_claim_unused_channel(true);
	_DMA_Config_Copy_Buffer = dma_channel_get_default_config(_DMA_Channel_Copy_Buffer);
	channel_config_set_transfer_data_size(&_DMA_Config_Copy_Buffer, DMA_SIZE_32);
	channel_config_set_read_increment(&_DMA_Config_Copy_Buffer, true);
	channel_config_set_write_increment(&_DMA_Config_Copy_Buffer, true);


	Display_Shapes_Init(&_Current_Buffer);
	Display_Font_Init();
	Display_Image_Init(&_Current_Buffer, initial_color);
    Display_Render_Simple_Init(&_Transition_Settings);

	Display_Init_GPIOs();
	Display_SPI_Init(init_touch);

	Display_Init_Reset();
	Display_Init_Registers();
	Display_Init_WakeUp();

	if(init_touch) {
		Display_Touch_Init();
	}

	Display_Objects_Init(initial_color);

	if(send_buffer) {
		Display_Render_Objects();
	}
}

void Display_Issue_Touch_Event(int16_t x_screen, int16_t y_screen)
{
	// Display_Check_Button_Touch(x_screen, y_screen);
	_Screen_Touch_Event(x_screen, y_screen);
}

void Display_Set_Draw_Touch_Reference_Points(bool do_draw)
{
	_Draw_Touch_Reference_Points = do_draw;
}

void Display_Set_Draw_Center_Lines(bool do_draw)
{
	_Draw_Center_Lines = do_draw;
}

void Display_Screen_Transition_Start(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration)
{
	_Transition_Settings.Direction_Out = direction_out;
	_Transition_Settings.Direction_In = TRANSITION_NONE;
	_Transition_Settings.Offset.X = 0;
	_Transition_Settings.Offset.Y = 0;
	_Transition_Settings.Type = type;
	_Transition_Settings.Frame_Duration = frame_duration;
	_Transition_Settings.Step = 0;
	_Transition_Settings.Position_In = 0;
	_Transition_Settings.Position_Out = 0;

	if(_Transition_Settings.Frame_Duration == 0) {
		return;
	}

	switch (direction_in)
	{
	case TRANSITION_NONE:
		break;

	case TRANSITION_LEFT:
		_Transition_Settings.Offset.X = +DISPLAY_WIDTH;
		_Transition_Settings.Direction_In = direction_in;
		break;

	case TRANSITION_RIGHT:
		_Transition_Settings.Offset.X = -DISPLAY_WIDTH;
		_Transition_Settings.Direction_In = direction_in;
		break;

	case TRANSITION_UP:
		_Transition_Settings.Offset.Y = +DISPLAY_HEIGHT;
		_Transition_Settings.Direction_In = direction_in;
		break;

	case TRANSITION_DOWN:
		_Transition_Settings.Offset.Y = -DISPLAY_HEIGHT;
		_Transition_Settings.Direction_In = direction_in;
		break;
	
	default:
		break;
	}

	if(_Transition_Settings.Direction_Out != TRANSITION_NONE) {
		Display_Copy_Buffer(&_Image_Buffer, &_Image_Buffer_Backup);
	}
}

void Display_Screen_Transition_Tick()
{
	if(_Transition_Settings.Direction_Out == TRANSITION_NONE && _Transition_Settings.Direction_In == TRANSITION_NONE) {
		return;
	}

	float Transition_X = ((float)_Transition_Settings.Step) / ((float)_Transition_Settings.Frame_Duration);
	float New_Percent = 0.0f;

	New_Percent = ApplyEasing1(Transition_X, _Transition_Settings.Type);

	// Calculate position targets separately for incoming and outgoing directions
	int16_t Position_Target_In = 0;
	int16_t Position_Target_Out = 0;

	// Set position target for incoming screen (only if there is an incoming transition)
	if (_Transition_Settings.Direction_In != TRANSITION_NONE)
	{
		switch (_Transition_Settings.Direction_In)
		{
			case TRANSITION_LEFT:
			case TRANSITION_RIGHT:
				Position_Target_In = DISPLAY_WIDTH;
				break;

			case TRANSITION_UP:
			case TRANSITION_DOWN:
				Position_Target_In = DISPLAY_HEIGHT;
				break;

			default:
				_Transition_Settings.Offset.X = 0;
				_Transition_Settings.Offset.Y = 0;
				_Transition_Settings.Direction_In = TRANSITION_NONE;
				return;
		}
	}

	// Set position target for outgoing screen (only if there is an outgoing transition)
	if (_Transition_Settings.Direction_Out != TRANSITION_NONE)
	{
		switch (_Transition_Settings.Direction_Out)
		{
			case TRANSITION_LEFT:
			case TRANSITION_RIGHT:
				Position_Target_Out = DISPLAY_WIDTH;
				break;

			case TRANSITION_UP:
			case TRANSITION_DOWN:
				Position_Target_Out = DISPLAY_HEIGHT;
				break;

			case TRANSITION_NONE:
				Position_Target_Out = 0;  // No outgoing transition
				break;

			default:
				Position_Target_Out = 0;
				break;
		}
	}

	// Calculate new positions and shift steps
	int16_t New_Position_In 	= 0;
	int16_t Shift_Step_In 		= 0;
	
	// Only calculate incoming position if there's an incoming transition
	if (_Transition_Settings.Direction_In != TRANSITION_NONE) {
		New_Position_In = (uint)(New_Percent * Position_Target_In);
		Shift_Step_In = New_Position_In - _Transition_Settings.Position_In;
	}

	// Handle incoming screen movement
	if (_Transition_Settings.Direction_In != TRANSITION_NONE)
	{
		switch (_Transition_Settings.Direction_In)
		{
			case TRANSITION_LEFT:	_Transition_Settings.Offset.X -= Shift_Step_In;	break;
			case TRANSITION_RIGHT:	_Transition_Settings.Offset.X += Shift_Step_In;	break;
			case TRANSITION_UP:		_Transition_Settings.Offset.Y -= Shift_Step_In;	break;
			case TRANSITION_DOWN:	_Transition_Settings.Offset.Y += Shift_Step_In;	break;
		}

		_Transition_Settings.Position_In += Shift_Step_In;
	}

	int16_t New_Position_Out 	= 0;
	int16_t Shift_Step_Out 		= 0;

	// Only calculate outgoing position if there's an outgoing transition
	if (_Transition_Settings.Direction_Out != TRANSITION_NONE) {
		New_Position_Out = (uint)(New_Percent * Position_Target_Out);
		Shift_Step_Out = New_Position_Out - _Transition_Settings.Position_Out;
	}

	// Handle outgoing screen movement (if there is one)
	if (_Transition_Settings.Direction_Out != TRANSITION_NONE)
	{
		Display_Set_Current_Image_Buffer(&_Image_Buffer_Backup);
		switch (_Transition_Settings.Direction_Out)
		{
			case TRANSITION_LEFT:	Display_Buffer_Shift_Left(Shift_Step_Out);	break;
			case TRANSITION_RIGHT:	Display_Buffer_Shift_Right(Shift_Step_Out);	break;
			case TRANSITION_UP:		Display_Buffer_Shift_Up(Shift_Step_Out);	break;
			case TRANSITION_DOWN:	Display_Buffer_Shift_Down(Shift_Step_Out);	break;
		}
		Display_Set_Current_Image_Buffer(&_Image_Buffer);

		_Transition_Settings.Position_Out += Shift_Step_Out;
	}

	// Update step counter and position
	if (_Transition_Settings.Step < _Transition_Settings.Frame_Duration) {
		_Transition_Settings.Step++;
	}
	else {
		_Transition_Settings.Offset.X = 0;
		_Transition_Settings.Offset.Y = 0;
		_Transition_Settings.Direction_In = TRANSITION_NONE;

		_Transition_Settings.Direction_Out = TRANSITION_NONE;
	}
}

bool Display_Screen_Transition_Ongoing()
{
	if(_Transition_Settings.Direction_Out == TRANSITION_NONE && _Transition_Settings.Direction_In == TRANSITION_NONE) {
		return false;
	}

	return true;
}

void Display_Render_Objects(void)
{
    Object_Menu_Select*	    	MS;
	Object_Menu_Icon_Row*	    MI;
	Object_Select_YesNo*	    YN;
	Object_Select_List*	    	SL;
	Object_Select_Value*	    SV;
	Object_Entry_Indicator*     EI;

	Animation_State	Animation_State = COMPLETE;

	if(_Transition_Settings.Direction_Out != TRANSITION_NONE) {
		Display_Copy_Buffer(&_Image_Buffer_Backup, &_Image_Buffer);
	}
	else {
		Display_Shapes_Fill_Screen(Display_Objects_Background_Color_Get());
	}

	for(uint i=0;i<Display_Objects_Count();i++)
	{
		Display_Object* Object 		= Display_Objects_Get_By_ID(i);
		Style*			Style		= Object->Style;
		Animation*		Animation	= Object->Animation;
		
		if(Animation != NULL) {
			if(Animation->Animation_Start == START_AFTER_PREVIOUS && Animation_State != COMPLETE) {
				continue;
			}

			Animation_State = Display_Animation_Tick(Object);

			if(Animation_State == NO_STARTED || Animation_State == DELAYING) {
				continue;
			}
		}

		if(Object->Enabled == false) {
			continue;
		}

		Coordinates Coordinates_Object = Object->Coordinates;
		Coordinates_Object.X += Object->Content_Offset.X;
		Coordinates_Object.Y += Object->Content_Offset.Y;

		Coordinates_Object.X += _Transition_Settings.Offset.X;
		Coordinates_Object.Y += _Transition_Settings.Offset.Y;
		
		if(Style != NULL) {
			bool Do_Draw_Style = true;
			
			Coordinates Coordinates_Style = Object->Coordinates;
			Coordinates_Style.X += _Transition_Settings.Offset.X;
			Coordinates_Style.Y += _Transition_Settings.Offset.Y;
			Display_Draw_Style(&Coordinates_Style, Style, Object->Dimension.Width, Object->Dimension.Height, Do_Draw_Style);
		}

		if(Object->Focused == true) {
			Coordinates Coordinates_Focus = Object->Coordinates;
			Coordinates_Focus.X += _Transition_Settings.Offset.X;
			Coordinates_Focus.Y += _Transition_Settings.Offset.Y;

			Display_Draw_Focused(&Coordinates_Focus, Object->Dimension.Width, Object->Dimension.Height);
		}

		switch (Object->Type)
		{
        ///////////////////
        // Render Simple //
        /////////////////// 
		case FLOAT:             Display_Render_Simple_Float(&Coordinates_Object, (Object_Float*)Object->Data);                      break;
		case INTEGER:           Display_Render_Simple_Integer(&Coordinates_Object, (Object_Integer*)Object->Data);                  break;
		case TEXT:              Display_Render_Simple_Text(&Coordinates_Object, (Object_Text*)Object->Data);                        break;
		case IMAGE:             Display_Render_Simple_Image(&Coordinates_Object, (Object_Image_Color*)Object->Data);                break;
		case BOOLEAN:           Display_Render_Simple_Bool(&Coordinates_Object, (Object_Bool*)Object->Data);                        break;
		case SHAPE:             Display_Render_Simple_Shape(&Coordinates_Object, (Object_Shape*)Object->Data);                      break;
		
        ////////////////////
        // Render Complex //
        //////////////////// 
        case VALUE_BAR_RECT:    Display_Render_Complex_Value_Bar_Rect(&Coordinates_Object, (Object_Value_Bar_Rect*)Object->Data);   break;
		case VALUE_BAR_ARC:     Display_Render_Complex_Value_Bar_Arc(&Coordinates_Object, (Object_Value_Bar_Arc*)Object->Data);     break;
        case GRAPH:             Display_Render_Complex_Graph(&Coordinates_Object, (Object_Graph*)Object->Data);                     break;
		case BUTTON:            Display_Render_Complex_Button(&Coordinates_Object, (Object_Button*)Object->Data);                   break;
        case CANVAS:            Display_Render_Complex_Canvas(&Coordinates_Object, (Object_Canvas*)(Object->Data));                 break;

		case MESSAGE_BOX:
			Display_Render_Complex_Message_Box(&Coordinates_Object, (Object_Message_Box*)(Object->Data), Object->Dimension.Width, Object->Dimension.Height);
			break;

		case MENU_SELECT:
			MS = (Object_Menu_Select*)(Object->Data);
			Display_Render_Complex_Menu_Select(&Coordinates_Object, MS->Menu_Titles, MS->Menu_Entry_Count, MS->Title_Char_Length, *MS->Selected_Entry, MS->Config);
			break;

		case MENU_ICON_ROW:
			MI = (Object_Menu_Icon_Row*)(Object->Data);
			Display_Render_Complex_Menu_Icon_Row(&Coordinates_Object, MI->Items, MI->Item_Count, *MI->Selected_Item, MI->Config);
			break;

		case MENU_RING:
			Display_Render_Complex_Menu_Ring(&Coordinates_Object, (Object_Menu_Ring*)(Object->Data));
			break;

        case MENU_HIERARCHICAL:
            Display_Render_Complex_Menu_Hierarchical(&Coordinates_Object, (Object_Menu_Hierarchical*)(Object->Data));
            break;

		case SELECT_YESNO:
			YN = (Object_Select_YesNo*)(Object->Data);
			Display_Render_Complex_Select_YesNo(&Coordinates_Object, YN->Title, YN->Title_Length, *YN->Value, YN->Config);
			break;

		case SELECT_LIST:
			SL = (Object_Select_List*)(Object->Data);
			Display_Render_Complex_Select_List(&Coordinates_Object, SL->List_Titles, SL->List_Entry_Count, SL->List_Char_Length, *SL->Selected_Entry, SL->Initial_Entry, SL->Config);
			break;

		case SELECT_VALUE:
			SV = (Object_Select_Value*)(Object->Data);
			Display_Render_Complex_Select_Value(&Coordinates_Object, SV->Title, SV->Title_Length, *SV->Value, SV->Max, SV->Min, SV->Format, SV->Config);
			break;
		
		case SELECT_RGB:
			Display_Render_Complex_Select_RGB(&Coordinates_Object, (Object_Select_RGB*)(Object->Data));
			break;

		case ENTRY_INDICATOR:
			EI = (Object_Entry_Indicator*)(Object->Data);
			Display_Render_Complex_Entry_Indicator(&Coordinates_Object, EI->Entry_Count, *EI->Entry_Value, EI->Config);
			break;

		default:
			break;
		}
	}

	if(_Draw_Touch_Reference_Points) {
		Display_Draw_Touch_Refernce_Points(DISPLAY_COLOR_GREEN);
	}

	if(_Draw_Center_Lines) {
		Display_Draw_Center_Lines(DISPLAY_COLOR_GREENYELLOW);
	}

	Display_Draw_Touch_Marker(DISPLAY_COLOR_BLUE);
}

void Display_Send_Buffer(void)
{
	Display_SPI_Start_Command(DISPLAY_MEMORY_WRITE);
	Display_SPI_Send_Data((uint8_t *)_Image_Buffer.Dim_1, DISPLAY_IMAGE_BUFFER_BYTE_SIZE, true);
}

bool Display_Send_Buffer_Completed(void)
{
	return Display_SPI_DMA_Transfer_Completed();
}

void Display_Show_Test_Screen(void)
{
	int16_t Width = DISPLAY_WIDTH / 5;
	
	Display_Shapes_Draw_Rect_Filled(0*Width, 0, Width, DISPLAY_HEIGHT, DISPLAY_COLOR_RED);
	Display_Shapes_Draw_Rect_Filled(1*Width, 0, Width, DISPLAY_HEIGHT, DISPLAY_COLOR_GREEN);
	Display_Shapes_Draw_Rect_Filled(2*Width, 0, Width, DISPLAY_HEIGHT, DISPLAY_COLOR_BLUE);
	Display_Shapes_Draw_Rect_Filled(3*Width, 0, Width, DISPLAY_HEIGHT, DISPLAY_COLOR_BLACK);
	Display_Shapes_Draw_Rect_Filled(4*Width, 0, Width, DISPLAY_HEIGHT, DISPLAY_COLOR_WHITE);
}

int	Display_Get_Button_Touch_Return_Value(void)
{
	int Return_Value = _Touched_Button_Return_Value;

	_Touched_Button_Return_Value = -1;

	return Return_Value;
}

void Display_Select_First_Object(void)
{
	Display_Action_CW();
}

void Display_Action_CW(void)
{
	if(_Object_Selected == false)
	{
		(*_Screen_On_Objects_Defocused)(Display_Get_Index_Of_Object(Display_Get_Selected_Object()));
		Display_Object_Select_Next();
		(*_Screen_On_Objects_Focused)(Display_Get_Index_Of_Object(Display_Get_Selected_Object()));
	}
	else
	{
		(*_Screen_Action_CW)(Display_Get_Index_Of_Object(Display_Get_Selected_Object()));
	}
}

void Display_Action_CCW(void)
{
	if(_Object_Selected == false)
	{
		(*_Screen_On_Objects_Defocused)(Display_Get_Index_Of_Object(Display_Get_Selected_Object()));
		Display_Object_Select_Previous();
		(*_Screen_On_Objects_Focused)(Display_Get_Index_Of_Object(Display_Get_Selected_Object()));
	}
	else
	{
		(*_Screen_Action_CCW)(Display_Get_Index_Of_Object(Display_Get_Selected_Object()));
	}
}

void Display_Action_SW(void)
{
	if(_Object_Selected == false)
	{
		_Object_Selected = true;
		(*_Screen_On_Object_Select)(Display_Get_Index_Of_Object(Display_Get_Selected_Object()));
	}
	else
	{
		_Object_Selected = false;
		(*_Screen_On_Object_Deselect)(Display_Get_Index_Of_Object(Display_Get_Selected_Object()));
	}
}

void Display_Select_Object(void)
{
	_Object_Selected = true;
}

void Display_Unselect_Object(void)
{
	_Object_Selected = false;
}

void Display_Menu_Icon_Row_Set(uint32_t initially_selected_item, uint32_t icon_space_width)
{
	Display_Render_Complex_Menu_Icon_Row_Set(initially_selected_item, icon_space_width);
}

void Display_Inc_Frame_Counter(void)
{
	_Frame_Counter++;
}

uint* Display_Get_Frame_Counter_Reference(void)
{
	return &_Frame_Counter;
}

Display_Color Display_Get_Pixel(uint32_t pixel_number)
{
    if(pixel_number >= DISPLAY_HEIGHT * DISPLAY_WIDTH) {
        return 0;
    }

    return _Image_Buffer.Dim_1[pixel_number];
}


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


void Display_Draw_Style(Coordinates* coordinates, Style* style, uint32_t content_width, uint32_t content_height, bool do_draw)
{
	/*
		INFO (2021-11-11):
		Width and Height information from the Style-Struct is currently not used. The dimension is stored in the Object
		itself and does not change during runtime. A width or height adapted to the contect size is currently not available.
		It is also not possible to align the content within the style. This has to be done by setting the Padding values
		properly.
		I am not sure if the above mentioned functionality is needed or will be used by at all. This is why I will not 
		implement these functions and keep things simple until I see I really need it.
	*/
	
	uint16_t Height 			= content_height;
	uint16_t Width 				= content_width;
	Display_Color Border_Color 	= style->Border_Color;
	uint16_t Border_Thickness 	= style->Border_Thickness;
	uint16_t Border_Radius		= style->Border_Radius;

	if(!do_draw) {
		return;
	}
	
	if(style->Background_Color != Display_Objects_Background_Color_Get()) {
		if(Border_Radius > 0) {
			Display_Shapes_Draw_Round_Rect_Filled(coordinates->X + Border_Thickness, coordinates->Y + Border_Thickness, Width - 2 * Border_Thickness, Height - 2 * Border_Thickness, Border_Radius, style->Background_Color);
		} else {
			Display_Shapes_Draw_Rect_Filled(coordinates->X + Border_Thickness, coordinates->Y + Border_Thickness, Width - 2 * Border_Thickness, Height - 2 * Border_Thickness, style->Background_Color);
		}
	}

	if(style->Border_Color != Display_Objects_Background_Color_Get() && style->Border_Thickness > 0) {
		Display_Shapes_Draw_Round_Rect_Frame(coordinates->X, coordinates->Y, Width, Height, Border_Radius, Border_Thickness, Border_Color);
	}
}

void Display_Draw_Focused(Coordinates* coordinates, uint32_t width, uint32_t height)
{
	// No focus indicator needed for this projects
    
    if(_Object_Selected == true) {
		// Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y, 3, 3, DISPLAY_COLOR_RED);
	}
	else {
		// Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y, 3, 3, DISPLAY_COLOR_GREEN);
	}
}

Animation_State Display_Animation_Tick(Display_Object* object)
{
	Animation* 			Animation 			= object->Animation;
	Animation_Status* 	Animation_Status 	= object->Animation_Status;

	if(Animation == NULL || Animation_Status == NULL) {
		return COMPLETE;
	}
	
	switch (Animation_Status->State)
	{
	case NO_STARTED:
		Animation_Status->Target.X = object->Coordinates.X;
		Animation_Status->Target.Y = object->Coordinates.Y;

//		break;	// Skipped on purpose
	
	case DELAYING:
		if(Animation_Status->State == DELAYING) {
			Animation_Status->Tick_Counter--;
		} else {
			Animation_Status->Tick_Counter 	= Animation->Tick_Delay;
			Animation_Status->State 		= DELAYING;
		}

		if(Animation_Status->Tick_Counter > 0) {
			break;
		}

//		break;	// Skipped on purpose

	case MOVING:
		if(Animation_Status->State == MOVING) {
			Animation_Status->Tick_Counter--;
		} else {
			Animation_Status->Tick_Counter 	= Animation->Tick_Duration;
			Animation_Status->State 		= MOVING;
		}

		float Progress = (float)Animation_Status->Tick_Counter / (float)Animation->Tick_Duration;

		object->Coordinates.X = Animation_Status->Target.X + (int16_t)((float)Animation->Offset.X * Progress);
		object->Coordinates.Y = Animation_Status->Target.Y + (int16_t)((float)Animation->Offset.Y * Progress);

		if(Animation_Status->Tick_Counter == 0) {
			Animation_Status->State = COMPLETE;
		}
		break;

	case COMPLETE:
	default:
		Animation_Status->State = COMPLETE;
		break;
	}

	return Animation_Status->State;
}

void Display_Draw_Touch_Refernce_Points(Display_Color color)
{
	for(int i=0;i<Display_Touch_Get_Reference_Point_Count();i++)
	{
		Coordinates* Point = Display_Touch_Get_Reference_Point(i);
		if(Point != NULL) {
			Display_Shapes_Draw_Circle_Filled(Point->X-5, Point->Y-5, 5, color);
		}
	}
}

void Display_Draw_Touch_Marker(Display_Color color)
{
	int Radius = 5;
	uint Marker_Count = Display_Touch_Get_Marker_Count();

	for(uint i=0;i<Marker_Count;i++) {
		Coordinates* Coordinates = Display_Touch_Get_Marker_Coordinates(i);
		if(Coordinates == NULL) {
			continue;
		}

		Display_Shapes_Draw_Circle_Filled(Coordinates->X-Radius, Coordinates->Y-Radius, Radius, color);
	}
}

void Display_Draw_Center_Lines(Display_Color color)
{
	Display_Shapes_Draw_HLine(0, DISPLAY_HEIGHT/2 - 1, DISPLAY_WIDTH, 2, color);
	Display_Shapes_Draw_VLine(DISPLAY_WIDTH/2 - 1, 0, DISPLAY_HEIGHT, 2, color);
}

void Display_Check_Button_Touch(int16_t x, int16_t y)
{
	for(uint i=0;i<Display_Objects_Count();i++)
	{
		Display_Object* Object = Display_Objects_Get_By_ID(i);

		if(Object->Type != BUTTON) { continue; }

		Coordinates		Coordinates = Object->Coordinates;
		Style*			Style		= Object->Style;
		Object_Button*	N			= (Object_Button*)Object->Data;

		if(Style != NULL) {
			Display_Draw_Style(&Coordinates, Style, N->Dimension.Width, N->Dimension.Height, false);
		}

		if(x >= Coordinates.X && x <= Coordinates.X + N->Dimension.Width && y >= Coordinates.Y && y <= Coordinates.Y + N->Dimension.Height)
		{
			_Touched_Button_Return_Value = N->Return_Value;
		}
	}
}

void Display_Object_Select_Next(void)
{
	Display_Object* Current_Object = Display_Get_Selected_Object();
	int Current_Object_Index = Display_Get_Index_Of_Object(Current_Object);

	if(Current_Object_Index == -1)
	{
		Current_Object_Index = 0;
		Current_Object = Display_Objects_Get_By_ID(Current_Object_Index);
		if(Current_Object->Selectable == true && Current_Object->Enabled == true)
		{
			Current_Object->Focused = true;
			return;
		}
	}

	for(uint i=0;i<Display_Objects_Count();i++)
	{
		uint Index = i + Current_Object_Index + 1;
		if(Index >= Display_Objects_Count())
		{
			Index -= Display_Objects_Count();
		}

		Display_Object* Next_Possible_Object = Display_Objects_Get_By_ID(Index);

		if(Next_Possible_Object->Selectable == true && Next_Possible_Object->Enabled == true)
		{
			Current_Object->Focused = false;
			Next_Possible_Object->Focused = true;
			return;
		}
	}
}

void Display_Object_Select_Previous(void)
{
	Display_Object* Current_Object = Display_Get_Selected_Object();
	int Current_Object_Index = Display_Get_Index_Of_Object(Current_Object);

	if(Current_Object_Index == -1)
	{
		Current_Object_Index = 0;
	}

	for(uint i=0;i<Display_Objects_Count();i++)
	{
		uint Index = (Display_Objects_Count() - i - 1) + Current_Object_Index;
		if(Index >= Display_Objects_Count())
		{
			Index -= Display_Objects_Count();
		}

		Display_Object* Next_Possible_Object = Display_Objects_Get_By_ID(Index);

		if(Next_Possible_Object->Selectable == true && Next_Possible_Object->Enabled == true)
		{
			Current_Object->Focused = false;
			Next_Possible_Object->Focused = true;
			return;
		}
	}

	if(Current_Object->Selectable == true && Current_Object->Enabled == true)
	{
		Current_Object->Focused = true;
	}
}

Display_Object* Display_Get_Selected_Object(void)
{
	for(uint i=0;i<Display_Objects_Count();i++)
	{
		Display_Object* Object = Display_Objects_Get_By_ID(i);
		if(Object->Selectable == true && Object->Focused == true)
		{
			return Object;
		}
	}

	return NULL;
}

int	Display_Get_Index_Of_Object(Display_Object* object)
{
	if(object == NULL) { return -1; }

	for(uint i=0;i<Display_Objects_Count();i++)
	{
		if(Display_Objects_Get_By_ID(i) == object)
		{
			 return i;
		}
	}
}

void Display_Buffer_Shift_Left(uint32_t steps)
{
	if (steps >= DISPLAY_WIDTH) {
        // Clear entire buffer
        Display_Shapes_Fill_Screen(Display_Objects_Background_Color_Get());
        return;
    }

	Display_Color BG_Color = Display_Objects_Background_Color_Get();
	
	// Move columns to the left
    for (int16_t row = 0; row < DISPLAY_HEIGHT; row++) 
	{
        // Move the row data left
        memmove(&_Current_Buffer->Dim_2[row][0], &_Current_Buffer->Dim_2[row][steps], (DISPLAY_WIDTH - steps) * sizeof(Display_Color));
        
        // Fill the rightmost area with background color
        for (int16_t col = DISPLAY_WIDTH - steps; col < DISPLAY_WIDTH; col++) {
            _Current_Buffer->Dim_2[row][col] = BG_Color;
        }
    }
}

void Display_Buffer_Shift_Right(uint32_t steps)
{
	if (steps >= DISPLAY_WIDTH) {
        Display_Shapes_Fill_Screen(Display_Objects_Background_Color_Get());
        return;
    }
    
    Display_Color BG_Color = Display_Objects_Background_Color_Get();
    
    // Move columns to the right (start from rightmost to avoid overwriting)
    for (int16_t row = 0; row < DISPLAY_HEIGHT; row++)
	{
        // Move the row data right
        memmove(&_Current_Buffer->Dim_2[row][steps], &_Current_Buffer->Dim_2[row][0], (DISPLAY_WIDTH - steps) * sizeof(Display_Color));
        
        // Fill the leftmost area with background color
        for (int16_t col = 0; col < steps; col++) {
            _Current_Buffer->Dim_2[row][col] = BG_Color;
        }
    }
}

void Display_Buffer_Shift_Up(uint32_t steps)
{
	if (steps >= DISPLAY_HEIGHT) {
        Display_Shapes_Fill_Screen(Display_Objects_Background_Color_Get());
        return;
    }
    
    // Calculate how many pixels to move
    uint32_t Pixels_To_Move = DISPLAY_WIDTH * (DISPLAY_HEIGHT - steps);
    
    // Single memmove for the entire shift operation
    memmove(&_Current_Buffer->Dim_1[0], &_Current_Buffer->Dim_1[DISPLAY_WIDTH * steps], Pixels_To_Move * sizeof(Display_Color));
    
    // Fill the bottom area using optimized rectangle fill
    Display_Shapes_Draw_Rect_Filled(0, DISPLAY_HEIGHT - steps, DISPLAY_WIDTH, steps, Display_Objects_Background_Color_Get());
}

void Display_Buffer_Shift_Down(uint32_t steps)
{
	if (steps >= DISPLAY_HEIGHT) {
        Display_Shapes_Fill_Screen(Display_Objects_Background_Color_Get());
        return;
    }
    
    // Calculate how many pixels to move
    uint32_t Pixels_To_Move = DISPLAY_WIDTH * (DISPLAY_HEIGHT - steps);
    
    // Single memmove for the entire shift operation
    memmove(&_Current_Buffer->Dim_1[DISPLAY_WIDTH * steps], &_Current_Buffer->Dim_1[0], Pixels_To_Move * sizeof(Display_Color));
    
    // Fill the top area using optimized rectangle fill
    Display_Shapes_Draw_Rect_Filled(0, 0, DISPLAY_WIDTH, steps, Display_Objects_Background_Color_Get());
}

void Display_Set_Current_Image_Buffer(Display_Image_Buffer* buffer)
{
	if(buffer != NULL) {
		_Current_Buffer = buffer;
	}
}

void Display_Copy_Buffer(Display_Image_Buffer *src, Display_Image_Buffer *dest)
{
	dma_channel_configure(_DMA_Channel_Copy_Buffer, &_DMA_Config_Copy_Buffer, dest, src, DISPLAY_IMAGE_BUFFER_PIXEL_SIZE/2, false);
    dma_channel_start(_DMA_Channel_Copy_Buffer);
    dma_channel_wait_for_finish_blocking(_DMA_Channel_Copy_Buffer);
}