#include "PromptWidget.h"
#include "InputDeviceTracker.h"
#include "UtilityLibrary.h"
#include "PlayerControllerBase.h"
#include "InputAction.h"
#include "InputMappingContext.h"
#include "Widgets/SOverlay.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Layout/SScaleBox.h"

//
// Atlas contents:
//
// Row 1: Keyboard Button no Glyph, Left Mouse, Middle Mouse, Right Mouse
// Row 2: Left Trigger, Right Trigger, Left Shoulder, Right Shoulder
// Row 3: XBFace Down, XBFace Left, XBFace Up, XBFace Right
// Row 4: PSFace Down, PSFace Left, PSFace Up, PSFace Right
// Row 5: Arrow  Down, Arrow  Left, Arrow  Up, Arrow  Right
// Row 6: DPad   Down, DPad   Left, DPad   Up, DPad   Right
// Row 7: Space Bar, unused, unused, unused
// Row 8: unused, unused, unused, unused.


int32 UlxPromptWidget::ChooseIcon(bool Playstation, FKey Key) const
{
	// Mouse buttons (row 1)
	if (Key == EKeys::LeftMouseButton)        return  1; 
	if (Key == EKeys::MiddleMouseButton)      return  2; 
	if (Key == EKeys::RightMouseButton)       return  3; 

	// Gamepad triggers and shoulders (row 2)
	if (Key == EKeys::Gamepad_LeftTrigger)    return  4; 
	if (Key == EKeys::Gamepad_RightTrigger)   return  5; 
	if (Key == EKeys::Gamepad_LeftShoulder)   return  6; 
	if (Key == EKeys::Gamepad_RightShoulder)  return  7; 

	// Gamepad Face buttons (rows 3 and 4)
	if (Key == EKeys::Gamepad_FaceButton_Bottom) return Playstation ? 12 :  8; 
	if (Key == EKeys::Gamepad_FaceButton_Left)   return Playstation ? 13 :  9; 
	if (Key == EKeys::Gamepad_FaceButton_Top)    return Playstation ? 14 : 10; 
	if (Key == EKeys::Gamepad_FaceButton_Right)  return Playstation ? 15 : 11; 

	// Arrow keys (row 5)
	if (Key == EKeys::Down)                   return 16; 
	if (Key == EKeys::Left)                   return 17; 
	if (Key == EKeys::Up)                     return 18; 
	if (Key == EKeys::Right)                  return 19; 

	// Gamepad D-Pad (row 6)
	if (Key == EKeys::Gamepad_DPad_Down)  return 20; 
	if (Key == EKeys::Gamepad_DPad_Left)  return 21; 
	if (Key == EKeys::Gamepad_DPad_Up)    return 22; 
	if (Key == EKeys::Gamepad_DPad_Right) return 23; 

	// Space bar (row 7)
	if (Key == EKeys::SpaceBar)               return 24; 

	// No icon for this key.  Use the blank icon with a label.
	return 0;
}

void UlxPromptWidget::ChooseAppearance(int32 &OutIcon, FString &OutGlyph)
{
	FKey Key = (ControllerType == ElxControllerType::KeyboardMouse) ? KeyboardKey : GamepadKey;
	OutIcon = ChooseIcon(ControllerType == ElxControllerType::PlayStationGamepad, Key);
	if (OutIcon == 0) OutGlyph = Key.GetDisplayName().ToString();
	else OutGlyph = TEXT("");
}


FBox2f UlxPromptWidget::GetIconUVs(int32 IconIndex)
{
	const float ColWidth  = 1.0f / 4.0f;
	const float RowHeight = 1.0f / 8.0f;
	int32 Col = IconIndex % 4;
	int32 Row = IconIndex / 4;
	float U = Col * ColWidth;
	float V = Row * RowHeight;
	return FBox2f(FVector2f(U, V), FVector2f(U + ColWidth, V + RowHeight));
}

FMargin UlxPromptWidget::GetScaledMargins() const
{
	return FMargin(
		GlyphMargins.Left   * Size.X,
		GlyphMargins.Top    * Size.Y,
		GlyphMargins.Right  * Size.X,
		GlyphMargins.Bottom * Size.Y);
}

void UlxPromptWidget::SynchronizeProperties()
{
	Super::SynchronizeProperties();
	if (!MyImage.IsValid()) return;

	int32 Icon = 0;
	FString Glyph;
	ChooseAppearance(Icon, Glyph);

	MyBrush.SetUVRegion(GetIconUVs(Icon));
	MyImage->InvalidateImage();
	MyBox->SetWidthOverride(Size.X);
	MyBox->SetHeightOverride(Size.Y);
	const float Extra = Depressed ? 0.05f : 0.0f;
	const FMargin ExtraPadding(Extra * Size.X, Extra * Size.Y, Extra * Size.X, Extra * Size.Y);
	MyImageSlot->SetPadding(ExtraPadding);
	MyGlyphSlot->SetPadding(GetScaledMargins() + ExtraPadding);
	MyGlyph->SetColorAndOpacity(GlyphColor);

	if (!Glyph.IsEmpty())
	{
		MyGlyph->SetText(FText::FromString(Glyph));
		MyGlyph->SetVisibility(EVisibility::HitTestInvisible);
	}
	else
	{
		MyGlyph->SetVisibility(EVisibility::Collapsed);
	}
}

void UlxPromptWidget::SetSize(FVector2D InSize)
{
	if (Size != InSize)
	{
		Size = InSize;
		SynchronizeProperties();
	}
}

void UlxPromptWidget::SetGlyphMargins(FMargin InMargins)
{
	if (GlyphMargins != InMargins)
	{
		GlyphMargins = InMargins;
		SynchronizeProperties();
	}
}

void UlxPromptWidget::SetGlyphColor(FLinearColor InColor)
{
	if (GlyphColor != InColor)
	{
		GlyphColor = InColor;
		SynchronizeProperties();
	}
}

void UlxPromptWidget::SetDepressed(bool InDepressed)
{
	if (Depressed != InDepressed)
	{
		Depressed = InDepressed;
		SynchronizeProperties();
	}
}

void UlxPromptWidget::SetKeys(FKey InGamepadKey, FKey InKeyboardKey)
{
	if ((GamepadKey != InGamepadKey) || (KeyboardKey != InKeyboardKey))
	{
		GamepadKey = InGamepadKey;
		KeyboardKey = InKeyboardKey;
		SynchronizeProperties();
	}
}

void UlxPromptWidget::SetKeysFromBindings(const UInputMappingContext* InputMappingContext, const UInputAction* EnhancedInputAction)
{
	check(InputMappingContext);
	check(EnhancedInputAction);

	FKey NewFirstKey;
	FKey NewGamepadKey;
	FKey NewKeyboardKey;

	for (const FEnhancedActionKeyMapping& Mapping : InputMappingContext->GetMappings())
	{
		if (Mapping.Action != EnhancedInputAction) continue;
		FKey Key = Mapping.Key;
		if (Key.IsDigital())
		{
			if (!NewFirstKey.IsValid()) NewFirstKey = Mapping.Key;
			if      (Key.IsTouch())        { /* not supported */ }
			else if (Key.IsGesture())      { /* not supported */ }
			else if (Key.IsGamepadKey())   { if (!NewGamepadKey.IsValid()) NewGamepadKey = Key; }
			else { if (!NewKeyboardKey.IsValid()) NewKeyboardKey = Key; }
		}
	}

	if (!NewGamepadKey.IsValid()) NewGamepadKey = NewFirstKey;
	if (!NewKeyboardKey.IsValid()) NewKeyboardKey = NewFirstKey;
	SetKeys(NewGamepadKey, NewKeyboardKey);
}

bool UlxPromptWidget::OnTick(float DeltaTime)
{
	ElxControllerType Type = UlxInputDeviceTracker::GetLastControllerType();
	if (ControllerType != Type)
	{
		ControllerType = Type;
		SynchronizeProperties();
	}
	return true;
}

TSharedRef<SWidget> UlxPromptWidget::RebuildWidget()
{
	if (!IsDesignTime())
	{
		ControllerType = UlxInputDeviceTracker::GetLastControllerType();
		TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateUObject(this, &UlxPromptWidget::OnTick));
	}

	MyBrush.SetResourceObject(ButtonAtlas.Get());

	SAssignNew(MyImage, SImage).Image(&MyBrush);

	SAssignNew(MyGlyph, STextBlock);
	SAssignNew(MyScaleBox, SScaleBox)
		.Stretch(EStretch::ScaleToFit)
		[ MyGlyph.ToSharedRef() ];

	MyOverlay = SNew(SOverlay);
	MyOverlay->AddSlot().Expose(MyImageSlot)
		[ MyImage.ToSharedRef() ];
	MyOverlay->AddSlot().HAlign(HAlign_Fill).VAlign(VAlign_Fill).Expose(MyGlyphSlot)
		[ MyScaleBox.ToSharedRef() ];

	SAssignNew(MyBox, SBox)
		[ MyOverlay.ToSharedRef() ];

	SynchronizeProperties();
	return MyBox.ToSharedRef();
}

void UlxPromptWidget::ReleaseSlateResources(bool bReleaseChildren)
{
	Super::ReleaseSlateResources(bReleaseChildren);
	if (!IsDesignTime())
	{
		FTSTicker::GetCoreTicker().RemoveTicker(TickHandle);
	}
	MyBox.Reset();
	MyOverlay.Reset();
	MyImage.Reset();
	MyScaleBox.Reset();
	MyGlyph.Reset();
	MyImageSlot = nullptr;
	MyGlyphSlot = nullptr;
}
