#pragma once

#include "CoreMinimal.h"
#include "WingBasics.h"

struct FWingProperty
{
	// To understand the following fields, imagine an object
	// that contains a struct, which contains another struct,
	// which contains another struct, which contains a field F.
	// In that case, Object points to the object that
	// contains everything, whereas Container points to the
	// innermost struct that contains the property.

	TStrongObjectPtr<UObject> Object = nullptr;
	void* Container = nullptr;
	FProperty* Prop = nullptr;
	bool Editable = false;

	// Construct a property reference.
	//
	FWingProperty(UObject *InObject, void* InContainer, FProperty* InProp, bool Edit)
		: Object(InObject), Container(InContainer), Prop(InProp), Editable(Edit) {}

	// Construct a null property reference.
	//
	FWingProperty() = default;

	// Operator -> forwards to the FProperty.
	//
	FProperty* operator->() const { return Prop; }

	// Store a value.  On failure, return false, and print an
	// error. These will do automatic conversion of numeric
	// types to other numeric types, as long as the value
	// fits. They will also do text to any type, as long as
	// the value parses as a value of the desired type.
	//
	bool SetObject(UObject *Obj, WingOut Errors) const;
	bool SetDouble(double D, WingOut Errors) const;
	bool SetInt64(int64 I, WingOut Errors) const;
	bool SetBool(bool B, WingOut Errors) const;
	bool SetText(FString Value, WingOut Errors) const;

	// Fetch a value.  If an error occurs such as a type
	// mismatch, returns an empty optional and prints an
	// error.  These will do automatic conversion of numeric
	// types to other numeric types, as long as the value
	// fits.  They will also do any type to text, which is
	// why the GetText version needs no error reporting.
	//
	TOptional<UObject*> GetObject(WingOut Errors) const;
	TOptional<double> GetDouble(WingOut Errors) const;
	TOptional<int64> GetInt64(WingOut Errors) const;
	TOptional<bool> GetBool(WingOut Errors) const;
	FString GetText() const;

	// Get the text, replace newlines with whitespace, and truncate to the specified maximum length.
	//
	FString GetTruncatedText(int32 MaxLen) const;

	// Print the property's editflag, type, name, and value.
	//
	void Print(WingOut Out) const;

	// Get the Category metadata.
	//
	FString GetCategory() const;

	// Return true if the property is not the null property reference.
	// 
	explicit operator bool() const { return Prop != nullptr; }

	// Get all the properties of the specified object or struct.
	//
	// This gets the properties that are literally present in the
	// specified object or struct.  No special interpretation is done.
	// If mutable is false, all properties will be marked non-editable.
	//
	static TArray<FWingProperty> GetAll(UObject *Obj, void *Container, UStruct *Struct, bool Mutable);

	// Get all the visible properties of the specified object or struct.
	//
	// This gets the properties that have CPF_Edit marked on them.
	// If mutable is false, all properties will be marked non-editable.
	//
	static TArray<FWingProperty> GetVisible(UObject *Obj, void *Container, UStruct *Struct, bool Mutable);
	static bool PopulateFromArgv(TArray<FWingProperty>& Props, TConstArrayView<FString> Argv, WingOut Errors);

	// Convenience versions of GetAll and GetVisible for UObjects.
	//
	static TArray<FWingProperty> GetAll(UObject *Obj, bool Mutable)
	{ return GetAll(Obj, Obj, Obj->GetClass(), Mutable); }
	static TArray<FWingProperty> GetVisible(UObject *Obj, bool Mutable)
	{ return GetVisible(Obj, Obj, Obj->GetClass(), Mutable); }

	// Get just the names of the properties of the specified struct/class.
	//
	static TArray<FName> GetVisibleNames(UStruct *US);

	// Remove any properties with the specified name.
	//
	static void Remove(TArray<FWingProperty>& Props, FName Name);

	// Move any properties with the specified name to the out array.
	//
	static void Move(TArray<FWingProperty> &Out, TArray<FWingProperty> &In, FName Name);

	// Return the "Details Panel" properties for the specified object.
	//
	// This is the set of properties that would typically appear in the
	// details panel if you were to click the object in the editor.  Note
	// that this is not always the properties of the object itself.  For
	// example, if you click a widget, the details panel shows you
	// properties of the widget, but also properties of the slot.  If you
	// click a material graph node, the details panel shows you properties
	// of the node, but also properties of the material expression.
	//
	// If you do not specify mutable, then all properties will be marked
	// non-editable.
	//
	static TArray<FWingProperty> GetDetails(UObject* Obj, bool Mutable);

private:
	static bool IsUnsigned(FNumericProperty* Prop);
	static bool IsPinTypeProperty(FProperty *Prop);
	void PrintExpectsReceived(const TCHAR *Type, WingOut Errors) const;
	bool CheckImportTextResult(const FString &Value, WingOut Errors) const;
	bool CheckEditable(WingOut Errors) const;

	// Convert int64 to double losslessly.  If it can't be done, generate an error.
	static bool LosslessInt64ToDouble(int64 I, double &D, WingOut Errors);

	// Convert double to int64 losslessly.  If it can't be done, generate an error.
	static bool LosslessDoubleToInt64(double D, int64 &I, WingOut Errors);

	// Unlike SetDouble, this function requires that NProp is a floating point property.
	static bool SetDoubleInternal(FNumericProperty *NProp, void *Container, double D, WingOut Errors);

	// Unlike SetInt64, this function requires that NProp is an integral property.
	static bool SetInt64Internal(FNumericProperty *NProp, void *Container, int64 I, WingOut Errors);
};
