package main

import "core:fmt"
import "core:math"
import "core:math/linalg"
import "core:math/rand"
import rl "vendor:raylib"

Car :: struct {
	pos:          rl.Vector3,
	vel:          rl.Vector3,
	engine_state: EngineState,
	drivetrain:   Drivetrain,
	speed:        f32,
	forward:      rl.Vector3,
	wheel_yaw:    f32,
}

EngineState :: struct {
	throttle: f32,
	brake:    f32,
	rpm:      f32,
	torque:   f32,
	fuel_cut: bool,
	engine:   Engine,
	graph:    Graph,
}

MIN_RPM :: 1000
MAX_RPM :: 7000
RPM_STEP :: 20

Engine :: struct {
	idle_rpm: f32,
	max_rpm:  f32,
	bypass:   f32,
	fric:     f32,
	inertia:  f32,
	hyst:     f32,
	rpm_lut:  []f32,
}

MASS :: 1200
DRAG_COEF :: 0.40
ROLL_FORCE :: 176
CLUTCH_K :: 0.15
CLUTCH_MAX :: 300 // предел трения сцепления, N·m
BRAKE_MAX :: 10500 // ~0.9g для 1200 кг

Drivetrain :: struct {
	ratios:       [5]f32,
	final_ratio:  f32,
	wheel_radius: f32,
	efficiency:   f32,
	gear:         i32,
}

Graph :: struct {
	points: []rl.Vector2,
	pos:    rl.Vector2,
	opts:   GraphOpts,
}


GRAPH_STEP :: 75
GRAPH_MIN_VAL_Y :: 0
GRAPH_MAX_VAL_Y :: 500
GRAPH_MIN_VAL_X :: 1000
GRAPH_MAX_VAL_X :: 7000

GRAPH_WIDTH :: 300
GRAPH_HEIGHT :: 200
GRAPH_PADDING :: 5

RPMSample :: struct {
	rpm:  f32,
	torq: f32,
}

update_car :: proc(car: ^Car) {
	update_gear(&car.drivetrain, car.speed)
	update_engine_state(car)
	update_turning(car)
	car.pos += car.vel * car.speed * rl.GetFrameTime()
}

update_turning :: proc(car: ^Car) {
	dt := rl.GetFrameTime()

	// 1. Get input direction (-1 for left, 1 for right)
	input: f32 = 0
	if rl.IsKeyDown(.D) || rl.IsKeyDown(.RIGHT) {input -= 1}
	if rl.IsKeyDown(.A) || rl.IsKeyDown(.LEFT) {input += 1}

	// 2. Turn sensitivity scaling based on speed
	speed_scale := math.clamp(car.speed / 200.0, 0.0, 1.0)
	turn_speed := f32(6.5) // Adjust turn speed in radians per second

	// 3. Update the car's orientation angle (yaw)
	car.wheel_yaw += input * turn_speed * speed_scale * dt

	// 4. Calculate forward vector directly from yaw
	// (X = sin, Z = cos gives standard 3D forward direction where 0 rad points North/Forward)
	car.forward = rl.Vector3 {
		math.sin(car.wheel_yaw),
		0,
		math.cos(car.wheel_yaw),
	}

	// 5. Arcade Grip / Blend Velocity towards Forward Vector
	// Lower grip value (e.g. 5.0) = loose / drifting turn
	// Higher grip value (e.g. 20.0) = tight / glued to road
	grip_factor: f32 = 5.0
	car.vel = linalg.lerp(car.vel, car.forward, grip_factor * dt)

	// Normalize velocity vector to prevent unintended scaling
	if rl.Vector3Length(car.vel) > 0 {
		car.vel = rl.Vector3Normalize(car.vel)
	}
}

update_gear :: proc(drivetrain: ^Drivetrain, speed: f32) {
	if rl.IsKeyPressed(.E) {
		if try_shift(drivetrain, speed, drivetrain.gear + 1) do drivetrain.gear += 1
	}
	if rl.IsKeyPressed(.Q) {
		if try_shift(drivetrain, speed, drivetrain.gear - 1) do drivetrain.gear -= 1
	}
}

try_shift :: proc(drivetrain: ^Drivetrain, speed: f32, new_gear: i32) -> bool {
	if new_gear < 0 || new_gear > 5 do return false
	if new_gear == 0 do return true
	G := drivetrain.ratios[new_gear - 1] * drivetrain.final_ratio
	projected_rpm := speed * G * 9.549 / drivetrain.wheel_radius
	return projected_rpm <= MAX_RPM
}

update_engine_state :: proc(car: ^Car) {
	dt := rl.GetFrameTime()
	if rl.IsKeyDown(.S) {
		car.engine_state.brake = linalg.lerp(car.engine_state.brake, 1, 0.2)
	} else {
		if car.engine_state.brake >= 0.1 {
			car.engine_state.brake = linalg.lerp(
				car.engine_state.brake,
				0,
				0.2,
			)
		} else {
			car.engine_state.brake = 0
		}
	}
	if rl.IsKeyDown(.W) {
		car.engine_state.throttle = linalg.lerp(
			car.engine_state.throttle,
			1,
			0.2,
		)
	} else {
		if car.engine_state.throttle >= 0.1 {
			car.engine_state.throttle = linalg.lerp(
				car.engine_state.throttle,
				0,
				0.2,
			)
		} else {
			car.engine_state.throttle = 0
		}
	}

	if car.engine_state.brake > 0 && car.drivetrain.gear != 0 do car.engine_state.throttle = 0


	if car.engine_state.rpm > MAX_RPM do car.engine_state.fuel_cut = true
	else if car.engine_state.rpm < MAX_RPM - car.engine_state.engine.hyst do car.engine_state.fuel_cut = false

	eng_torq :=
		car.engine_state.fuel_cut ? 0 : car.engine_state.throttle * torq_from_lut(car.engine_state.rpm, &car.engine_state.engine) + car.engine_state.engine.bypass
	t_net := eng_torq - car.engine_state.engine.fric * car.engine_state.rpm

	// сила двигателя на колёсах (0 на нейтрали) и эффективная масса
	force: f32 = 0
	mass_eff := f32(MASS)

	if car.drivetrain.gear == 0 {
		// нейтраль: свободный оборот, на колёса ничего не идёт
		car.engine_state.rpm +=
			t_net / car.engine_state.engine.inertia * 9.549 * dt
	} else {
		G :=
			car.drivetrain.ratios[car.drivetrain.gear - 1] *
			car.drivetrain.final_ratio
		coupled_rpm := car.speed * G * 9.549 / car.drivetrain.wheel_radius

		if coupled_rpm < car.engine_state.rpm {
			clutch_tq := min(
				CLUTCH_K * (car.engine_state.rpm - coupled_rpm),
				CLUTCH_MAX,
			)
			clutch_tq = max(clutch_tq, 0.0)
			car.engine_state.rpm +=
				(t_net - clutch_tq) /
				car.engine_state.engine.inertia *
				9.549 *
				dt
			car.engine_state.rpm = max(
				car.engine_state.rpm,
				car.engine_state.engine.idle_rpm,
			)
			force =
				clutch_tq *
				G *
				car.drivetrain.efficiency /
				car.drivetrain.wheel_radius
		} else {
			force =
				t_net *
				G *
				car.drivetrain.efficiency /
				car.drivetrain.wheel_radius
			mass_eff =
				MASS +
				car.engine_state.engine.inertia *
					G *
					G /
					(car.drivetrain.wheel_radius * car.drivetrain.wheel_radius)
			car.engine_state.rpm = max(
				coupled_rpm,
				car.engine_state.engine.idle_rpm,
			)
		}
	}

	// скорость — всегда, с тормозом (и на нейтрали тоже)
	brake_force := car.engine_state.brake * BRAKE_MAX
	car.speed +=
		(force -
			DRAG_COEF * car.speed * car.speed -
			ROLL_FORCE -
			brake_force) /
		mass_eff *
		dt
	car.speed = max(car.speed, 0)

	car.engine_state.torque = torq_from_lut(
		car.engine_state.rpm,
		&car.engine_state.engine,
	)

	// eng_torq :=
	// 	state.fuel_cut ? 0 : state.throttle * torq_from_lut(state.rpm, &state.engine) + state.engine.bypass
	//
	// load_torq := state.engine.fric * state.rpm
	//
	// state.rpm += (eng_torq - load_torq) / state.engine.inertia * 9.549 * dt
	//
	// state.torque = torq_from_lut(state.rpm, &state.engine)

}

init_car :: proc() -> Car {
	car: Car
	car.engine_state.engine = init_engine()
	car.pos = rl.Vector3{0, 0, 0}

	car.engine_state.graph.pos = rl.Vector2{950, 50}

	steps := (GRAPH_MAX_VAL_X - GRAPH_MIN_VAL_X) / GRAPH_STEP + 1

	car.engine_state.graph.opts = GraphOpts {
		min_x = i32(car.engine_state.graph.pos.x) + GRAPH_PADDING,
		max_x = i32(
			car.engine_state.graph.pos.x,
		) + GRAPH_WIDTH - GRAPH_PADDING,
		min_y = i32(car.engine_state.graph.pos.y) + GRAPH_PADDING,
		max_y = i32(
			car.engine_state.graph.pos.y,
		) + GRAPH_HEIGHT - GRAPH_PADDING,
	}

	car.engine_state.graph.points = make([]rl.Vector2, steps)


	for i in 0 ..< steps {
		rpm := f32(GRAPH_MIN_VAL_X + i * GRAPH_STEP)
		draw_coords := get_graph_coords(
			rpm,
			&car.engine_state.engine,
			car.engine_state.graph.opts,
		)
		car.engine_state.graph.points[i] = draw_coords
	}

	car.engine_state.rpm = MIN_RPM

	car.drivetrain = Drivetrain {
		ratios       = {3.4, 2.1, 1.5, 1.15, 0.9},
		final_ratio  = 3.3,
		wheel_radius = 0.33,
		efficiency   = 0.85,
		gear         = 0,
	}

	car.forward = rl.Vector3{0, 0, 1}

	return car
}

init_engine :: proc() -> Engine {
	engine: Engine
	dyno_points := []RPMSample {
		RPMSample{rpm = 1000, torq = 190},
		RPMSample{rpm = 2000, torq = 205},
		RPMSample{rpm = 2700, torq = 220},
		RPMSample{rpm = 2850, torq = 230},
		RPMSample{rpm = 2950, torq = 240},
		RPMSample{rpm = 3100, torq = 250},
		RPMSample{rpm = 3300, torq = 260},
		RPMSample{rpm = 4050, torq = 390},
		RPMSample{rpm = 4500, torq = 400},
		RPMSample{rpm = 5000, torq = 390},
		RPMSample{rpm = 5500, torq = 361},
		RPMSample{rpm = 6000, torq = 320},
		RPMSample{rpm = 6500, torq = 280},
		RPMSample{rpm = 7000, torq = 240},
	}

	engine.idle_rpm = 1000
	engine.max_rpm = 7000

	engine.bypass = 25
	engine.fric = 0.025
	engine.inertia = 0.4
	engine.hyst = 150

	lut := make([]f32, (MAX_RPM - MIN_RPM) / RPM_STEP + 1)

	for i in 0 ..< len(lut) {
		rpm := f32(MIN_RPM + i * RPM_STEP)
		lut[i] = torq_at_rpm(dyno_points, rpm)
	}

	engine.rpm_lut = lut


	return engine
}

GraphOpts :: struct {
	min_x: i32,
	min_y: i32,
	max_x: i32,
	max_y: i32,
}

draw_graph :: proc(state: ^EngineState) {

	pos := state.graph.pos

	rl.DrawRectangle(
		i32(pos.x),
		i32(pos.y),
		GRAPH_WIDTH,
		GRAPH_HEIGHT,
		rl.WHITE,
	)
	rl.DrawRectangleLines(
		i32(pos.x),
		i32(pos.y),
		GRAPH_WIDTH,
		GRAPH_HEIGHT,
		rl.BLACK,
	)

	rl.DrawLine(
		state.graph.opts.min_x,
		state.graph.opts.min_y,
		state.graph.opts.min_x,
		state.graph.opts.max_y,
		rl.BLACK,
	)
	rl.DrawLine(
		state.graph.opts.min_x,
		state.graph.opts.max_y,
		state.graph.opts.max_x,
		state.graph.opts.max_y,
		rl.BLACK,
	)

	for p_pos in state.graph.points {
		rl.DrawCircle(i32(p_pos.x), i32(p_pos.y), 2, rl.BLUE)
	}


	{
		rpm := state.rpm
		draw_coords := get_graph_coords(rpm, &state.engine, state.graph.opts)
		rl.DrawCircle(i32(draw_coords.x), i32(draw_coords.y), 2, rl.RED)
	}


}

get_graph_coords :: proc(
	rpm: f32,
	engine: ^Engine,
	opts: GraphOpts,
) -> rl.Vector2 {
	torq := torq_from_lut(rpm, engine)
	x := map_to_range(
		f32(GRAPH_MIN_VAL_X),
		f32(GRAPH_MAX_VAL_X),
		f32(opts.min_x),
		f32(opts.max_x),
		rpm,
	)
	y := map_to_range(
		f32(GRAPH_MIN_VAL_Y),
		f32(GRAPH_MAX_VAL_Y),
		f32(opts.max_y),
		f32(opts.min_y),
		torq,
	)
	return rl.Vector2{x, y}
}

map_to_range :: proc(
	in_min, in_max: f32,
	out_min, out_max: f32,
	val: f32,
) -> f32 {
	t := (val - in_min) / (in_max - in_min)
	return out_min + t * (out_max - out_min)
}

torq_from_lut :: proc(rpm: f32, engine: ^Engine) -> f32 {
	x := (rpm - f32(MIN_RPM)) / f32(RPM_STEP)

	i := int(x)
	t := x - f32(i)

	if i < 0 {
		return engine.rpm_lut[0]
	}

	if i >= len(engine.rpm_lut) - 1 {
		return engine.rpm_lut[len(engine.rpm_lut) - 1]
	}

	return linalg.lerp(engine.rpm_lut[i], engine.rpm_lut[i + 1], t)
}

hermite :: proc(y0, y1: f32, m0, m1: f32, x0, x1: f32, x: f32) -> f32 {
	h := x1 - x0
	t := (x - x0) / h

	t2 := t * t
	t3 := t2 * t

	h00 := 2 * t3 - 3 * t2 + 1
	h10 := t3 - 2 * t2 + t
	h01 := -2 * t3 + 3 * t2
	h11 := t3 - t2

	return h00 * y0 + h10 * h * m0 + h01 * y1 + h11 * h * m1
}

calc_slope :: proc(points: []RPMSample, i: int) -> f32 {
	if i == 0 {
		return(
			(points[1].torq - points[0].torq) /
			(points[1].rpm - points[0].rpm) \
		)
	}

	if i == len(points) - 1 {
		return(
			(points[i].torq - points[i - 1].torq) /
			(points[i].rpm - points[i - 1].rpm) \
		)
	}

	return(
		(points[i + 1].torq - points[i - 1].torq) /
		(points[i + 1].rpm - points[i - 1].rpm) \
	)
}

torq_at_rpm :: proc(points: []RPMSample, rpm: f32) -> f32 {
	if rpm <= points[0].rpm {
		return points[0].torq
	}
	if rpm >= points[len(points) - 1].rpm {
		return points[len(points) - 1].torq
	}
	for i in 0 ..< len(points) - 1 {
		p0 := points[i]
		p1 := points[i + 1]

		if rpm >= p0.rpm && rpm <= p1.rpm {
			m0 := calc_slope(points, i)
			m1 := calc_slope(points, i + 1)
			return hermite(p0.torq, p1.torq, m0, m1, p0.rpm, p1.rpm, rpm)
		}
	}
	return 0
}

draw_gauges :: proc(car: ^Car) {
	base_pos := rl.Vector2{1050, 600}
	offset := rl.Vector2{40, 0}
	text_offset := rl.Vector2{10, 102}

	current_pos := base_pos

	gear_offset := rl.Vector2{0, 0}
	speed_offset := rl.Vector2{40, 0}
	throttle_offset := rl.Vector2{80, 0}
	rpm_offset := rl.Vector2{120, 0}
	torque_offset := rl.Vector2{160, 0}

	draw_val(current_pos + text_offset, f32(car.drivetrain.gear))

	current_pos += offset

	draw_bar(current_pos, car.speed * 3.6 * 100 / 300, rl.GREEN)
	draw_val(current_pos + text_offset, car.speed * 3.6)

	current_pos += offset

	draw_bar(current_pos, car.engine_state.throttle * 100, rl.GREEN)
	draw_val(current_pos + text_offset, car.engine_state.throttle * 100)

	current_pos += offset

	draw_bar(current_pos, car.engine_state.rpm * 100 / 10000, rl.RED)
	draw_val(current_pos + text_offset, car.engine_state.rpm)

	current_pos += offset

	draw_bar(current_pos, car.engine_state.torque * 100 / 600, rl.BLUE)
	draw_val(current_pos + text_offset, car.engine_state.torque)
}

draw_bar :: proc(pos: rl.Vector2, percentage: f32, color: rl.Color) {
	bar_size_x: i32 = 20
	bar_size_y: i32 = 100
	rl.DrawRectangle(i32(pos.x), i32(pos.y), bar_size_x, bar_size_y, rl.GRAY)

	target_h := i32(percentage * f32(bar_size_y) / 100)

	rl.DrawRectangle(
		i32(pos.x),
		i32(pos.y) + bar_size_y - target_h,
		bar_size_x,
		target_h,
		color,
	)


	rl.DrawRectangleLines(
		i32(pos.x),
		i32(pos.y),
		bar_size_x,
		bar_size_y,
		rl.BLACK,
	)
}

draw_val :: proc(pos: rl.Vector2, val: f32) {
	str := fmt.ctprintf("%v", i32(val))
	str_len := rl.MeasureText(str, 15)
	rl.DrawText(str, i32(pos.x) - str_len / 2, i32(pos.y), 15, rl.WHITE)
}

draw_stats :: proc(car: ^Car) {
	draw_graph(&car.engine_state)
}
