@tool
class_name GrassMultiMesh extends MultiMeshInstance2D

@export_group("Visuals")
@export var texture_sheet : Texture2D:
	set(value):
		texture_sheet = value
		texture = value
@export var blade_size : Vector2 = Vector2(16, 16)
@export var texture_sheet_size : Vector2 = Vector2(64, 16)
@export var grass_tint : Color = Color.WHITE:
	set(value):
		grass_tint = value
		material.set_shader_parameter("modulate", grass_tint)

@export_group("Editor")
@export var editing : bool = false:
	set(value):
		editing = value
		notify_property_list_changed()
@export var brush_radius : float = 50.0
@export var brush_density : int = 5

# TODO: save to file, enabnle filesytem warning
@export_storage var grass_data : Array[Dictionary] = []

enum GrassState {NORMAL = 0, CUT = 1}

var _blade_half_height: float

func _ready() -> void:
	if not multimesh:
		multimesh = MultiMesh.new()
		multimesh.transform_format = MultiMesh.TRANSFORM_2D
		multimesh.use_colors = true
		multimesh.use_custom_data = true
	
	multimesh.mesh = _create_mesh_rect()
	
	if not material:
		material = ShaderMaterial.new()
		if ResourceLoader.exists("res://shaders/grass_shader.gdshader"):
			material.shader = load("res://shaders/grass_shader.gdshader")
	
	if material is ShaderMaterial:
		material.set_shader_parameter("frame_size", blade_size)
		material.set_shader_parameter("sheet_size", texture_sheet_size)
		material.set_shader_parameter("modulate", grass_tint)
		
		if material.get_shader_parameter("noise_tex") == null:
			var noise = FastNoiseLite.new()
			noise.frequency = 0.005
			var noise_tex = NoiseTexture2D.new()
			noise_tex.noise = noise
			noise_tex.seamless = true
			material.set_shader_parameter("noise_tex", noise_tex)

	if texture_sheet:
		texture = texture_sheet
		
	_blade_half_height = blade_size.y / 2.0
	_rebuild_multimesh()

func _process(_delta):
	if editing and Engine.is_editor_hint():
		queue_redraw()
		
		if Input.is_key_pressed(KEY_K):
			_brush_paint()
		elif Input.is_key_pressed(KEY_E):
			_brush_erase()


func interact_with_grass(global_target_pos: Vector2, rect_size: Vector2, new_state: int):
	if global_position.distance_squared_to(global_target_pos) > 500 * 500:
		return

	var local_target = to_local(global_target_pos)
	
	var half_w = rect_size.x * 0.5
	var half_h = rect_size.y * 0.5
	var left = local_target.x - half_w
	var right = local_target.x + half_w
	var top = local_target.y - half_h
	var bottom = local_target.y + half_h

	for i in range(multimesh.instance_count):
		var t_origin = multimesh.get_instance_transform_2d(i).origin
		
		if t_origin.x > left and t_origin.x < right and t_origin.y > top and t_origin.y < bottom:
			
			var custom_data = multimesh.get_instance_custom_data(i)
			if int(custom_data.b) == new_state:
				continue
			
			custom_data.b = float(new_state)
			multimesh.set_instance_custom_data(i, custom_data)
			grass_data[i]["state"] = new_state

func _create_mesh_rect() -> QuadMesh:
	var mesh = QuadMesh.new()
	mesh.size = blade_size
	return mesh

func _rebuild_multimesh() -> void:
	multimesh.instance_count = grass_data.size()
	var pivot_offset = Vector2(0, -_blade_half_height)
	
	for i in range(grass_data.size()):
		var d = grass_data[i]
		var xform = Transform2D().translated(d.pos).rotated(d.get("rot", 0.0)).translated(pivot_offset)
		
		multimesh.set_instance_transform_2d(i, xform)
		
		var custom = Color(
			randf(),
			float(d.get("tex_idx", 0)), 
			float(d.get("state", 0)),   
			0.0
		)
		multimesh.set_instance_custom_data(i, custom)

func _brush_paint() -> void:
	var mouse_pos = get_local_mouse_position()
	
	var columns = int(texture_sheet_size.x / blade_size.x)
	if columns == 0: columns = 1
	 
	for i in range(brush_density):
		var angle = randf() * TAU
		var rad = sqrt(randf()) * brush_radius
		var pos = mouse_pos + Vector2(cos(angle), sin(angle)) * rad
		
		var new_blade = {
			"pos": pos,
			"tex_idx": randi() % columns, 
			"state": GrassState.NORMAL,
			"rot": 0# randf_range(-0.2, 0.2)
		}
		grass_data.append(new_blade)
	
	_rebuild_multimesh()

func _brush_erase() -> void:
	var mouse_pos = get_local_mouse_position()
	var r_sq = brush_radius * brush_radius
	
	for i in range(grass_data.size() - 1, -1, -1):
		if grass_data[i].pos.distance_squared_to(mouse_pos) < r_sq:
			grass_data.remove_at(i)
	_rebuild_multimesh()

func _draw() -> void:
	if editing and Engine.is_editor_hint():
		draw_arc(get_local_mouse_position(), brush_radius, 0, TAU, 32, Color.YELLOW, 2.0)
