// Used in the GasaGen.cpp translation unit
#if GASA_INTELLISENSE_DIRECTIVES
#pragma once
#define GEN_EXPOSE_BACKEND
#include "gen.hpp"
#include "gen.builder.hpp"
#include "GasaGenCommon.cpp"
#endif

struct GAS_AttributeEntry
{
	StringCached Name;
	StringCached MinName;
	StringCached MaxName;
	float Min;
	float Max;
};

void def_attribute_properties                   ( CodeBody body, Array<GAS_AttributeEntry> properties );
void def_attribute_field_on_reps                ( CodeBody body, Array<GAS_AttributeEntry> properties );
void def_attribute_field_property_getters       ( CodeBody body, StrC class_name, Array<GAS_AttributeEntry> properties );
void def_attribute_field_value_getters          ( CodeBody body, Array<GAS_AttributeEntry> properties );
void def_attribute_field_value_setters          ( CodeBody body, Array<GAS_AttributeEntry> properties );
void def_attribute_field_property_setter_inlines( CodeBody body, StrC class_name, Array<GAS_AttributeEntry> properties );
void def_attribute_field_initers                ( CodeBody body, Array<GAS_AttributeEntry> properties );
void impl_attribute_fields                      ( CodeBody body, StrC class_name, Array<GAS_AttributeEntry> properties );

Array<GAS_AttributeEntry> get_gasa_attribute_fields()
{
	local_persist
	Array<GAS_AttributeEntry> attribute_fields = Array<GAS_AttributeEntry>::init_reserve(GlobalAllocator, 64);

	for (local_persist s32 do_once = 0; do_once == 0; ++ do_once) {
		StringCached str_Health    = get_cached_string(txt("Health"));
		StringCached str_MaxHealth = get_cached_string(txt("MaxHealth"));
		StringCached str_Mana      = get_cached_string(txt("Mana"));
		StringCached str_MaxMana   = get_cached_string(txt("MaxMana"));

		GAS_AttributeEntry Health    = { str_Health,    {nullptr}, str_MaxHealth, 0, 100.f   };
		GAS_AttributeEntry MaxHealth = { str_MaxHealth, {nullptr}, {nullptr},     0, 99999.f };
		GAS_AttributeEntry Mana      = { str_Mana,      {nullptr}, str_MaxMana,   0, 50.f    };
		GAS_AttributeEntry MaxMana   = { str_MaxMana,   {nullptr}, {nullptr},     0, 99999.f };

		attribute_fields.append(Health);
		attribute_fields.append(MaxHealth);
		attribute_fields.append(Mana);
		attribute_fields.append(MaxMana);
	}
	return attribute_fields;
}

void gen_UGasaAttributeSet()
{
	CodeType   type_UAttributeSet = def_type( txt("UAttributeSet") );
	CodeComment generation_notice = def_comment(txt("Generated by GasaGen/GasaGen_UGasaAttributeSet.cpp"));

	Array<GAS_AttributeEntry> attribute_fields = get_gasa_attribute_fields();

	StrC class_name = txt("UGasaAttributeSet");

	Builder header = Builder::open( path_gasa_ability_system "GasaAttributeSet.h");
	{
		header.print(generation_notice);
		header.print(pragma_once);
		header.print(fmt_newline);
		{
			CodeInclude Include_AttributeSet               = def_include(txt("AttributeSet.h"));
			CodeInclude Include_GasaAttributeSet_Generated = def_include(txt("GasaAttributeSet.generated.h"));

			CodeAttributes api_attribute= def_attributes( UModule_GASA_API->Name);

			CodeClass GasaAttributeSet = {};
			{
				CodeBody body = def_body( CodeT::Class_Body );
				{
					body.append( UHT_GENERATED_BODY);
					body.append( access_public );

					body.append( def_constructor() );
					body.append(fmt_newline);

					def_attribute_properties( body, attribute_fields);
					body.append(fmt_newline);
					def_attribute_field_on_reps( body, attribute_fields);

					body.append(fmt_newline);

					body.append( fmt_newline );
					body.append( def_pragma(txt( "region Getters" )));
					def_attribute_field_property_getters( body, class_name, attribute_fields );
					body.append( fmt_newline );
					def_attribute_field_value_getters( body, attribute_fields );
					body.append( def_pragma(txt( "endregion Getters" )));
					body.append( fmt_newline );

					body.append( def_pragma(txt( "region Setters" )));
					def_attribute_field_value_setters( body, attribute_fields );
					body.append( fmt_newline );
					body.append( fmt_newline );
					def_attribute_field_initers( body, attribute_fields );
					body.append( def_pragma(txt( "endregion Setters" )));
					body.append( fmt_newline );

					body.append( def_pragma( txt("region AttributeSet")));
					body.append( code_str(
						void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
						void PostGameplayEffectExecute(FGameplayEffectModCallbackData const& Data) override;
					));
					body.append( def_pragma( txt("endregion AttributeSet")));
					body.append(fmt_newline);

					body.append( def_pragma( txt("region UObject")));
					CodeFn GetLifetimeOfReplicatedProps = parse_function( code(
						void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
					));
					body.append( GetLifetimeOfReplicatedProps );
					body.append( def_pragma( txt("endregion UObject")));
				}
				GasaAttributeSet = def_class( class_name, body
					, type_UAttributeSet, AccessSpec::Public
					, api_attribute
				);
			}

			header.print( Include_AttributeSet);
			header.print( Include_GasaAttributeSet_Generated);
			header.print( fmt_newline);
			header.print( UHT_UCLASS );
			header.print(GasaAttributeSet);
		}
		header.write();
		format_file(path_gasa_ability_system "GasaAttributeSet.h");
	}

	Builder inlines = Builder::open( path_gasa_ability_system "GasaAttributeSet_Inlines.h");
	{
		inlines.print(generation_notice);
		inlines.print(pragma_once);
		inlines.print(fmt_newline);
		inlines.print( def_include( txt("GasaAttributeSet.h")));
		inlines.print( def_include(txt("AbilitySystemComponent.h")));
		inlines.print(fmt_newline);

		CodeBody body = def_body(CodeT::Global_Body);
		{
			def_attribute_field_property_setter_inlines( body, class_name, attribute_fields );
		}
		inlines.print(body);
		inlines.print(fmt_newline);

		CodeNS ns_gasa = parse_namespace( code(
			namespace Gasa
			{
				inline
				UGasaAttributeSet const* GetAttributeSet( UAbilitySystemComponent* ASC )
				{
					return Cast<UGasaAttributeSet>(ASC->GetAttributeSet( UGasaAttributeSet::StaticClass() ));
				}
			}
		));

		inlines.print(ns_gasa);
		inlines.write();
		format_file(path_gasa_ability_system "GasaAttributeSet_Inlines.h");
	}

	Builder source = Builder::open( path_gasa_ability_system "GasaAttributeSet.cpp" );
	{
		source.print(generation_notice);
		header.print(fmt_newline);
		source.print( def_include( txt("GasaAttributeSet.h")));
		source.print( def_include( txt("GasaAttributeSet_Inlines.h")));
		source.print( def_include( txt("EffectProperties.h")));
		source.print(fmt_newline);
		source.print( def_include( txt("AbilitySystemComponent.h")));
		source.print( def_include( txt("Net/UnrealNetwork.h")));
		source.print( def_include( txt("Networking/GasaNetLibrary.h")));
		{
			CodeBody body = def_body( CodeT::Global_Body );
			body.append(fmt_newline);

			CodeConstructor constructor_for_UGasaAttributeSet = parse_constructor( code(
				UGasaAttributeSet::UGasaAttributeSet()
				{
					InitHealth( 100.f );
					InitMaxHealth( 100.f );
					InitMana( 50.f );
					InitMaxMana( 50.f );
				}
			));
			body.append(constructor_for_UGasaAttributeSet );

			impl_attribute_fields( body, class_name, attribute_fields);

			Code PostGameplayEffectExecute = parse_function( code(
				void UGasaAttributeSet::PostGameplayEffectExecute(FGameplayEffectModCallbackData const& Data)
				{
					Super::PostGameplayEffectExecute(Data);
					FEffectProperties Props;
					Props.Populate(Data);
				}
			));
			body.append(PostGameplayEffectExecute);
			body.append(fmt_newline);

			CodeFn PreAttributeChange;
			{
				CodeBody attribute_clamps = def_body( CodeT::Function_Body );
				attribute_clamps.append(fmt_newline);
				attribute_clamps.append(fmt_newline);
				for (GAS_AttributeEntry field : attribute_fields)
				{
					String clamp_min;
					if (field.MinName.Data)
						clamp_min = get_cached_string(token_fmt( "MinName", (StrC)field.MinName, "Get<MinName>()"));
					else
						clamp_min = String::fmt_buf(GlobalAllocator, "%f", field.Min);

					String clamp_max;
					if (field.MaxName.Data)
						clamp_max = get_cached_string(token_fmt( "MaxName", (StrC)field.MaxName, "Get<MaxName>()"));
					else
						clamp_max = String::fmt_buf(GlobalAllocator, "%f", field.Max);

					attribute_clamps.append( code_fmt(
						"field",     (StrC)field.Name,
						"clamp_min", (StrC)clamp_min,
						"clamp_max", (StrC)clamp_max,
					stringize(
						if (Attribute == Get<field>Attribute())
						{
							NewValue = FMath::Clamp(NewValue, <clamp_min>, <clamp_max>);
						}
					)));
				}
				attribute_clamps.append(fmt_newline);
				attribute_clamps.append(fmt_newline);
				PreAttributeChange = parse_function( token_fmt( "attribute_clamps", (StrC)attribute_clamps.to_string(), stringize(
					void UGasaAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
					{
						Super::PreAttributeChange(Attribute, NewValue);
						<attribute_clamps>
					}
				)));
			}
			body.append(PreAttributeChange);
			body.append(fmt_newline);

			CodeFn GetLifetimeOfReplicatedProps;
			{
				CodeBody field_lifetimes = def_body( CodeT::Function_Body);
				field_lifetimes.append(fmt_newline);
				field_lifetimes.append(fmt_newline);
				for (GAS_AttributeEntry field : attribute_fields)
				{
					field_lifetimes.append( code_fmt( "field", (StrC)field.Name, stringize(
						DOREPLIFETIME_DEFAULT_GAS(UGasaAttributeSet, <field>);
					)));
				}

				GetLifetimeOfReplicatedProps = parse_function( token_fmt( "field_lifetimes", (StrC)(field_lifetimes.to_string()), stringize(
					void UGasaAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
					{
						Super::GetLifetimeReplicatedProps(OutLifetimeProps);
						<field_lifetimes>
					}
				)));
			}
			body.append(GetLifetimeOfReplicatedProps);

			source.print(body);
		}
		source.write();
		format_file(path_gasa_ability_system "GasaAttributeSet.cpp");
	}
}

void def_attribute_properties( CodeBody body, Array<GAS_AttributeEntry> properties )
{
	for ( GAS_AttributeEntry property : properties )
	{
		Code field_uproperty = code_fmt( "property", (StrC)property.Name, stringize(
			UPROPERTY(ReplicatedUsing=Client_OnRep_<property>, EditAnywhere, BlueprintReadWrite, Category="Attributes")
			FGameplayAttributeData <property>;
		));
		body.append(field_uproperty);
	}
}

void def_attribute_field_on_reps( CodeBody body, Array<GAS_AttributeEntry> properties )
{
	for ( GAS_AttributeEntry property : properties )
	{
		Code umeta_UFUNCTION = code_str( UFUNCTION() );

		body.append(fmt_newline);
		body.append( umeta_UFUNCTION );
		body.append(fmt_newline);
		body.append( code_fmt( "property", (StrC)property.Name, stringize(
			void Client_OnRep_<property>(FGameplayAttributeData& Prev<property>);
		)));
	}
}

void def_attribute_field_property_getters( CodeBody body, StrC class_name, Array<GAS_AttributeEntry> properties )
{
	for ( GAS_AttributeEntry property : properties )
	{
		CodeFn generated_get_attribute = parse_function(
			token_fmt( "class_name", class_name, "property", (StrC)property.Name,
			stringize(
				static FGameplayAttribute Get<property>Attribute()
				{
					static FProperty* Prop = FindFieldChecked<FProperty>(<class_name>::StaticClass(), GET_MEMBER_NAME_CHECKED(<class_name>, <property>));
					return Prop;
				}
			)));
		body.append( generated_get_attribute );
	}
}

#pragma push_macro("FORCEINLINE")
#undef FORCEINLINE
void def_attribute_field_value_getters( CodeBody body, Array<GAS_AttributeEntry> properties )
{
	for ( GAS_AttributeEntry property : properties )
	{
		body.append( code_fmt( "property", (StrC)property.Name,
		stringize(
			FORCEINLINE float Get<property>() const
			{
				return <property>.GetCurrentValue();
			}
		)));
	}
}

void def_attribute_field_value_setters( CodeBody body, Array<GAS_AttributeEntry> properties )
{
	for ( GAS_AttributeEntry property : properties )
	{
		body.append( code_fmt( "property", (StrC)property.Name,
		stringize(
			FORCEINLINE void Set<property>(float NewVal);
		)));
	}
}

void def_attribute_field_property_setter_inlines( CodeBody body, StrC class_name, Array<GAS_AttributeEntry> properties )
{
	body.append(def_pragma( txt("region Attribute Setters")));
	for ( GAS_AttributeEntry property : properties )
	{
		CodeFn generated_get_attribute = parse_function(
			token_fmt( "class_name", class_name, "property", (StrC)property.Name,
			stringize(
				FORCEINLINE void <class_name>::Set<property>(float NewVal)
				{
					UAbilitySystemComponent* AbilityComp = GetOwningAbilitySystemComponent();
					if (ensure(AbilityComp))
					{
						AbilityComp->SetNumericAttributeBase(Get<property>Attribute(), NewVal);
					};
				}
			)));
		body.append( generated_get_attribute );
	}
	body.append(def_pragma( txt("endregion Attribute Setters")));
}

void def_attribute_field_initers ( CodeBody body, Array<GAS_AttributeEntry> properties )
{
	for ( GAS_AttributeEntry property : properties )
	{
		body.append( code_fmt( "property", (StrC)property.Name,
		stringize(
			FORCEINLINE void Init<property>(float NewVal)
			{
				<property>.SetBaseValue(NewVal);
				<property>.SetCurrentValue(NewVal);
			}
		)));
	}
}

void impl_attribute_fields( CodeBody body, StrC class_name, Array<GAS_AttributeEntry> properties )
{
	body.append(fmt_newline);
	body.append(def_pragma( txt("region Rep Notifies")));
	for ( GAS_AttributeEntry property : properties )
	{
		CodeFn field_impl = parse_function( token_fmt(
			"class_name", class_name, "property", (StrC)property.Name, "from_notice", txt("\n// From GAMEPLAYATTRIBUTE_REPNOTIFY\n"),
		stringize(
			void <class_name>::Client_OnRep_<property>(FGameplayAttributeData& Prev<property>)
			{
				<from_notice>
				static FProperty* <class_name>Property = FindFieldChecked<FProperty>( StaticClass(), GET_MEMBER_NAME_CHECKED(<class_name>, <property>));
				GetOwningAbilitySystemComponentChecked()->SetBaseAttributeValueFromReplication(FGameplayAttribute(<class_name>Property), <property>, Prev<property>);
			}
		)));

		body.append( field_impl );
	}
	body.append( def_pragma( txt("endregion Rep Notifies")));
	body.append(fmt_newline);
}

inline
Code gen_GAMEPLAYATTRIBUTE_REPNOTIFY(StrC class_name, StrC property_name, StrC old_value)
{
	Code rep_notify = code_fmt(
		  "class_name",    class_name
		, "property_name", property_name
		, "old_value",     old_value,
	stringize(
		static FProperty* <class_name>Property = FindFieldChecked<FProperty>(<class_name>::StaticClass(), GET_MEMBER_NAME_CHECKED(<class_name>, <property_name>));
		GetOwningAbilitySystemComponentChecked()->SetBaseAttributeValueFromReplication(FGameplayAttribute(<class_name>Property), <property_name>, <old_value>);
	));
}
#pragma pop_macro("FORCEINLINE")
