package main 

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


Snake_Health := 1000

Segments: [dynamic]^SnakeSegment

Head: SnakeHead

SnakeState :: enum {
    Chasing,
    Chase_to_Dive,
    Dive_to_Chase,
    Diving,
    Hunt,
    Shot
}

SnakeHead :: struct {
    pos: vec3,
    vel: vec3,
    dir: f32,
    radius: f32,
    health: int,
    max_health: int,
    next: ^SnakeSegment,
    state: SnakeState,
    next_state: SnakeState,
    state_timer: f32,
    shot_timer: f32,
    is_shooting: bool,
    is_dead: bool,
}

SnakeSegment :: struct {
    pos: vec3,
    vel: vec3,
    dir: f32,
    radius: f32,
    collider_radius: f32,
    active: bool,
    health: u8,
    head: ^SnakeHead,
    next: ^SnakeSegment,
    prev: ^SnakeSegment
}

SnakeActive := false

snake_spawn :: proc(pos: vec3, dir: f32, length: int){
    // dir_vec := rl.Vector3RotateByAxisAngle(vec3right, vec3backward, dir)
    dir_vec := vec3down
    direction := math.atan2(-dir_vec.y, dir_vec.x)
    SnakeActive = false
    Head = SnakeHead{
        pos = pos,
        dir = dir,
        radius = 3,
        state = .Diving,
        vel = dir_vec * 20,
        health = 100,
        max_health = 100,
        state_timer = 15,
    }

    for i := 0; i < length; i += 1 {
        segment := new(SnakeSegment)
        segment.active = false
        segment.health = 3
        segment.radius = 2.5
        segment.collider_radius = 2.5
        segment.pos = pos - dir_vec * segment.radius
        segment.dir = dir
        segment.head = &Head
        if i != 0 {
            segment.prev = Segments[i-1]
            Segments[i-1].next = segment
        } else {
            Head.next = segment 
        }
        append(&Segments, segment)
    }
}

snake_clear :: proc() {
    for segment in Segments {
        free(segment)
    }
}

snake_process :: proc(game: ^Game, delta: f32) {
    if !SnakeActive || Head.is_dead{
        return
    }
    // Head.state = .Shot
    // Head.state_timer = 200
    switch Head.state {
    case .Chasing:
        snake_chase_smooth(game, delta)
    case .Diving:
        snake_dive(game, delta)
    case .Chase_to_Dive,.Dive_to_Chase:
        snake_dropdown(game, delta)
    case .Hunt:
        snake_hunt(game, delta)
    case .Shot:
        snake_shot(game, delta)
    }
    total_health := 0
    for segment in Segments {
        total_health += int(segment.health)
    }
    game.snake_health = total_health
    if game.snake_health == 0 {
        if Head.next != nil {
            Head.next = nil 
            Head.state = .Hunt
            Head.state_timer = 20
            change_track(Res.Music.Second)
            return
        }
        trail(Head.pos - get_vec_from_angle(Head.dir) * Head.radius * 0.7, 3, rand.float32_range(0.7, 1.8), rl.RED)
        if Head.health <= 0 && !Head.is_dead {
            Head.is_dead = true
            explode(Head.pos, 9, 0.9, rl.YELLOW)
            rl.StopMusicStream(current_music)
            rl.PlaySound(Res.Sfx.PlayerDead)
            timer_start(3, game, proc(data: rawptr) {
                state := transmute(^Game)data
                win := winning_init(state)
                stack_push(win)
            })
        }
    }
    for segment, i in Segments {
        if segment.prev == nil && total_health == 0 { // Хвост падает, когда у него не осталось жизней
            segment.vel.y -= 30 * delta 
            segment.pos += segment.vel * delta 
            continue
        }

        target_pos := Head.pos
        if segment.prev != nil {
            target_pos = segment.prev.pos
        } else {
            segment.vel = Head.vel
        }
        diff := target_pos - segment.pos
        if rl.Vector3Length(diff) > segment.radius  {
            segment.pos = target_pos - rl.Vector3Normalize(diff) * segment.radius
        }
        segment.dir = math.atan2(-diff.y, diff.x)
    }
}


snake_chase :: proc(game: ^Game, delta: f32) {
    using game 
    diff := player.pos - Head.pos
    target_angle := math.atan2(-diff.y, diff.x)
    dir_diff := angle_cycle(target_angle - Head.dir, -math.PI, math.PI)
    Head.dir = angle_rotate(Head.dir, target_angle, min(abs(dir_diff), math.PI) * delta)
    Head.vel = rl.Vector3RotateByAxisAngle(vec3right, vec3backward, Head.dir) * 500
    Head.pos += Head.vel * delta
}

snake_chase_smooth :: proc(game: ^Game, delta: f32) {
    using game

    if rl.IsKeyPressed(rl.KeyboardKey.K) {
        for segment in Segments {
            segment.health = 0
        }
    }

    Head.state_timer -= delta
    if Head.state_timer <= 0 {
        Head.state = .Chase_to_Dive
        rl.PlaySound(Res.Sfx.SnakeGrowl)
        Head.next_state = .Diving
        Head.state_timer = 10
    }

    if Head.pos.y < -3 && Head.vel.y < 0 {
        Head.pos.y = -3
        Head.vel.y = -Head.vel.y
    }

    diff := player.pos - Head.pos 
    norm := rl.Vector3Normalize(diff)
    Head.vel = rl.Vector3MoveTowards(Head.vel, norm * 70, 70 * delta)
    Head.vel = rl.Vector3ClampValue(Head.vel, 30, 50)
    Head.dir = math.atan2(-Head.vel.y, Head.vel.x)
    Head.pos += Head.vel * delta
}

snake_dive :: proc(game: ^Game, delta: f32) {
    Head.state_timer -= delta
    if Head.state_timer <= 0 {
        Head.state = .Chasing
        Head.state_timer = 20
        rl.PlaySound(Res.Sfx.SnakeGrowl)
    }
    if Head.pos.y < 0 && Segments[len(Segments)-1].pos.y < 0 && Head.vel.y < 0 {
        rl.PlaySound(Res.Sfx.SnakeEarthHit)
        Head.pos.x = game.player.pos.x 
        Head.pos.y = -5
        Head.vel.x = 0
        Head.vel.y = 70
    } else {
        grav : f32 = 20
        if Head.vel.y < 0 { grav = 30 }
        else if Head.pos.y < game.player.pos.y {
            grav = 7
        }
        Head.vel.y -= grav * delta
        Head.vel.x = (game.player.pos.x - Head.pos.x) * 2
    }
    Head.dir = math.atan2(-Head.vel.y, Head.vel.x)
    Head.pos += Head.vel * delta
}

snake_dropdown :: proc(game: ^Game, delta: f32) {
    Head.vel.y -= 100 * delta
    Head.pos += Head.vel * delta
    Head.dir = math.atan2(-Head.vel.y, Head.vel.x)
    if Segments[len(Segments)-1].pos.y < 0 {
        Head.state = Head.next_state
        Head.state_timer = 20
    }
}

snake_hunt :: proc(game: ^Game, delta: f32) {
    Head.state_timer -= delta 
    if Head.state_timer <= 0 {
        Head.state_timer = 10
        Head.state = .Shot
        rl.PlaySound(Res.Sfx.SnakeRoarBlast)
    }
    diff := game.player.pos - Head.pos
    target_pos := game.player.pos + rl.Vector3Normalize(diff) * 10
    target_diff := target_pos - Head.pos

    Head.vel = rl.Vector3MoveTowards(Head.vel, rl.Vector3Normalize(target_diff) * 50, 40 * delta)
    Head.dir = math.atan2(-Head.vel.y, Head.vel.x)
    Head.pos += Head.vel * delta
    if Head.pos.y < Head.radius {
        Head.pos.y = Head.radius
        Head.vel.y = -Head.vel.y / 2
    }
}

snake_shot :: proc(game: ^Game, delta: f32) {
    Head.state_timer -= delta
    if Head.state_timer <= 0 {
        Head.state_timer = 20
        Head.state = .Hunt
    }
    Head.is_shooting = false
    if Head.state_timer < 8 && Head.state_timer > 3 {
        Head.is_shooting = true
        if !rl.IsSoundPlaying(Res.Sfx.SnakeBeam) {
            rl.PlaySound(Res.Sfx.SnakeBeam)
        }
    }

    diff := game.player.pos - Head.pos
    target_angle := math.atan2(-diff.y, diff.x)
    dir_diff := angle_cycle(target_angle - Head.dir, -math.PI, math.PI)
    Head.dir = angle_rotate(Head.dir, target_angle, abs(dir_diff) * delta * 1.5)
    Head.vel = rl.Vector3MoveTowards(Head.vel, {}, 30 * delta)
    Head.pos += Head.vel * delta
}


snake_draw :: proc(game: ^Game) {
    if !SnakeActive || Head.is_dead {
        return
    }
    dir_vector := get_vec_from_angle(Head.dir)
    roll := -math.PI / 2 + math.cos(Head.dir) * math.PI / 2
    rlgl.PushMatrix()
        rlgl.Translatef(Head.pos.x, Head.pos.y, Head.pos.z)
        rlgl.Rotatef(math.to_degrees(Head.dir), 0, 0, -1)
        rlgl.Rotatef(math.to_degrees(roll), 1, 0, 0)
        // rl.DrawCircle3D({}, Head.radius, vec3up, 0, rl.RED)
        // rl.DrawLine3D({}, {-4, 0, 0}, rl.GREEN)
        rl.DrawModel(Res.Models.SnakeHeadTop, {}, 5, rl.WHITE)
        rlgl.PushMatrix()
        if Head.state == .Shot {
            step1 := math.smoothstep(f32(10), f32(8), f32(Head.state_timer))
            step2 := math.smoothstep(f32(0), f32(3), f32(Head.state_timer))
            angle := step1 * step2 * math.PI / 2.5
            fmt.println(Head.state_timer, step1, step2, angle)
            rlgl.Rotatef(math.to_degrees(angle), 0, 0, -1)
        }
        rl.DrawModel(Res.Models.SnakeHeadJaw, {}, 5, rl.WHITE)
        rlgl.PopMatrix()
        rlgl.Translatef(0.5, -1, 0)
        if Head.is_shooting {
            rl.DrawCylinderEx({}, vec3right * 300, 1.4, 1.4, 6, rl.YELLOW)
        }
    rlgl.PopMatrix()
    // rl.DrawCircle3D(Head.pos, Head.radius, vec3up, 0, rl.RED)
    // rl.DrawLine3D(Head.pos, Head.pos + dir_vector * Head.radius, rl.BLACK)


    // if game.snake_health == 0 {
    //     center := Head.pos + dir_vector * Head.radius * 1.5
    //     left := Head.pos + rl.Vector3RotateByAxisAngle(dir_vector, vec3backward, math.PI/3) * Head.radius * 1.5
    //     right := Head.pos + rl.Vector3RotateByAxisAngle(dir_vector, vec3backward, -math.PI/3) * Head.radius * 1.5
    //     rl.DrawLine3D(left, center, rl.YELLOW)
    //     rl.DrawLine3D(right, center, rl.YELLOW)
    // }

    for segment in Segments {
        dir_vector := get_vec_from_angle(segment.dir)
        col := rl.WHITE
        if segment.health == 0 {
            col = rl.Color{255, 150, 150, 255}
        }
        roll := -math.PI / 2 + math.cos(segment.dir) * math.PI / 2
        rlgl.PushMatrix()
            rlgl.Translatef(segment.pos.x, segment.pos.y, segment.pos.z)
            rlgl.Rotatef(math.to_degrees(segment.dir), 0, 0, -1)
            rlgl.Rotatef(math.to_degrees(roll), 1, 0, 0)
            rl.DrawModel(Res.Models.SnakeBody, {}, 4, col)
        rlgl.PopMatrix()
    }
}