use bevy::{
    core_pipeline::{bloom::BloomSettings, tonemapping::Tonemapping},
    dev_tools::fps_overlay::{FpsOverlayConfig, FpsOverlayPlugin},
    // input::common_conditions::*,
    prelude::*,
};
use rand::prelude::random;

fn main() {
    App::new()
        .insert_resource(ClearColor(Color::oklch(0.796, 0.126, 299.66)))
        .add_plugins(DefaultPlugins.set(WindowPlugin {
            primary_window: Some(Window {
                resolution: (400., 400.).into(),
                ..default()
            }),
            ..default()
        }))
        .add_plugins(FpsOverlayPlugin {
            config: FpsOverlayConfig {
                text_config: TextStyle {
                    font_size: 50.0,
                    color: Color::oklch(1.0, 0.0, 0.0),
                    font: default(),
                },
            },
        })
        .add_systems(Startup, (setup, spawn_snake))
        .insert_resource(Time::<Fixed>::from_seconds(0.125))
        .add_systems(FixedUpdate, snake_movement)
        .add_systems(Update, snake_movement_input.before(snake_movement))
        .add_systems(PostUpdate, (size_scaling, postion_translation))
        .insert_resource(Time::<Fixed>::from_hz(1.))
        .add_systems(FixedUpdate, food_spawner)
        .insert_resource(SnakeSegments::default())
        .run();
}

#[derive(Component)]
struct SnakeHead {
    direction: Direction,
}

#[derive(Component)]
struct SnakeSegment;

#[derive(Resource, Default)]
struct SnakeSegments(Vec<Entity>);

// Square grid
const GRID_SIZE: u8 = 10;

#[derive(Component, Copy, Clone, Eq, PartialEq)]
struct Position {
    x: i32,
    y: i32,
}

#[derive(Component)]
struct Size(f32);

#[derive(Component)]
struct Food;

#[derive(PartialEq, Copy, Clone)]
enum Direction {
    Left,
    Up,
    Right,
    Down,
}

impl Direction {
    pub fn opposite(self) -> Self {
        match self {
            Self::Left => Self::Right,
            Self::Right => Self::Left,
            Self::Up => Self::Down,
            Self::Down => Self::Up,
        }
    }
}

fn setup(mut commands: Commands) {
    // Create an HDR 2D Camera with bloom, using ACES tonemapping
    commands.spawn((
        Camera2dBundle {
            camera: Camera {
                hdr: true,
                ..default()
            },
            tonemapping: Tonemapping::AcesFitted,
            ..default()
        },
        BloomSettings::default(),
    ));
}

fn spawn_snake(mut commands: Commands, mut segments: ResMut<SnakeSegments>) {
    *segments = SnakeSegments(vec![
        commands
            .spawn(SpriteBundle {
                sprite: Sprite {
                    color: Color::oklch(1.1, 0.126, 299.66),
                    ..default()
                },
                transform: Transform {
                    scale: Vec3::splat(10.0),
                    ..default()
                },
                ..default()
            })
            .insert(SnakeHead {
                direction: Direction::Up,
            })
            .insert(Position { x: 3, y: 3 })
            .insert(Size(0.8))
            .id(),
        spawn_segment(commands, Position { x: 3, y: 2 }),
    ]);
}

fn snake_movement_input(
    keyboard_input: Res<ButtonInput<KeyCode>>,
    mut heads: Query<&mut SnakeHead>,
) {
    if let Some(mut head) = heads.iter_mut().next() {
        let dir: Direction = if keyboard_input.pressed(KeyCode::KeyA) {
            Direction::Left
        } else if keyboard_input.pressed(KeyCode::KeyD) {
            Direction::Right
        } else if keyboard_input.pressed(KeyCode::KeyS) {
            Direction::Down
        } else if keyboard_input.pressed(KeyCode::KeyW) {
            Direction::Up
        } else {
            head.direction
        };

        if dir != head.direction.opposite() {
            head.direction = dir;
        }
    }
}

fn snake_movement(mut heads: Query<(&mut Position, &SnakeHead)>) {
    if let Some((mut pos, head)) = heads.iter_mut().next() {
        match &head.direction {
            Direction::Left => pos.x -= 1,
            Direction::Right => pos.x += 1,
            Direction::Up => pos.y += 1,
            Direction::Down => pos.y -= 1,
        }
    };
}

fn size_scaling(windows: Query<&Window>, mut sprites: Query<(&Size, &mut Transform)>) {
    let window = windows.single();

    for (sz, mut transform) in sprites.iter_mut() {
        transform.scale = Vec3::new(
            sz.0 / GRID_SIZE as f32 * window.width(),
            sz.0 / GRID_SIZE as f32 * window.height(),
            1.,
        );
    }
}

fn postion_translation(windows: Query<&Window>, mut sprites: Query<(&Position, &mut Transform)>) {
    fn convert(pos: f32, window_size: f32) -> f32 {
        let tile_size_in_px = window_size / GRID_SIZE as f32;
        pos / GRID_SIZE as f32 * window_size - (window_size / 2.) + (tile_size_in_px / 2.)
    }

    let window = windows.single();

    for (pos, mut transform) in sprites.iter_mut() {
        transform.translation = Vec3::new(
            convert(pos.x as f32, window.width()),
            convert(pos.y as f32, window.height()),
            0.,
        )
    }
}

fn food_spawner(mut commands: Commands) {
    commands
        .spawn(SpriteBundle {
            sprite: Sprite {
                color: Color::oklch(0.7, 0.274, 330.35),
                ..default()
            },
            ..default()
        })
        .insert(Food)
        .insert(Position {
            x: (random::<f32>() * GRID_SIZE as f32) as i32,
            y: (random::<f32>() * GRID_SIZE as f32) as i32,
        })
        .insert(Size(0.8));
}

fn spawn_segment(mut commands: Commands, position: Position) -> Entity {
    commands
        .spawn(SpriteBundle {
            sprite: Sprite {
                color: Color::oklch(1.0, 0.126, 299.66),
                ..default()
            },
            ..default()
        })
        .insert(SnakeSegment)
        .insert(position)
        .insert(Size(0.65))
        .id()
}