use std::{time::Duration, fmt::format};

use bevy::{prelude::*, render::texture::ImageSettings, sprite::{Material2d, collide_aabb::collide}, log::LogSettings};
use bevy_prototype_lyon::prelude::*;
use bevy_tweening::{*, lens::*};
use rand::Rng;
use bevy_kira_audio::prelude::*;
use bevy::prelude::*;


fn main() {
    App::new()
    .add_plugins(DefaultPlugins)
    .add_plugin(ShapePlugin)
    .add_plugin(TweeningPlugin)
    .add_plugin(AudioPlugin)
    .insert_resource(Msaa{ samples: 1 })
    .insert_resource(ImageSettings::default_nearest())
    .add_startup_system(setup)
    //.add_system(position_back)
    .add_system(mouse_click_system)
    .add_system(mouse_follower)
    .add_system(move_bullets)
    .add_system(remove_dead_bullets)
    .add_system(animate_impact_smoke)
    .add_system(move_tourelle)
    .add_system(spawn_monster)
    .add_system(collision)
    .add_system(remove_dead_monsters)
    .add_system(clear_bullets)
    .add_system(play_delayed_audio)
    .add_system(keyboard_input_system)
    .add_system(shake_entities)
    .add_system(update_score)
    .run();
}

const BACK_SCALE:f32 = 5.0;
const SOUND_DISTANCE:f32 = 4.0;

#[derive(Component)]
struct BackSprite(String); 

#[derive(Component)]
struct TourelleGroup; 

#[derive(Component)]
struct Collider;

#[derive(Component)]
struct Tourelle{
    ammunitions:i64,
    shooting:Timer,
    is_shooting:bool
}

#[derive(Component)]
struct FireTicker(Timer); 

#[derive(Component)]
struct MonsterTicker(Timer); 

#[derive(Component)]
struct ViewFinder; 

#[derive(Component,Default)]
struct Bullet{
    x: f32,
    horizon:f32,
    dead:bool,
    vitesse: f32,
    dir_x: f32,
    angle:f32
}

#[derive(Component)]
struct Monster{
    life:i32,
    dead:bool
}


#[derive(Component)]
struct FuseTimeAudio {
    /// track when the audio should play (non-repeating timer)
    timer: Timer,
    path: String,
    volume: f64,
}

#[derive(Component)]
struct ShakeOnShoot;

#[derive(Component)]
struct Tireur;


#[derive(Clone)]
struct NeedAmmoTexture(Handle<Image>);
#[derive(Clone)]
struct ShooterTexture(Handle<Image>);

#[derive(Component)]
struct ScoreText{
    score:i64
}


fn setup(mut commands: Commands, asset_server: Res<AssetServer>, windows: ResMut<Windows>) {
    //let window = windows.get_primary().unwrap();
    commands.insert_resource(LogSettings {
        filter: "error,symphonia_core=error,symphonia_format_ogg=error,mygame=error".into(),
        level: bevy::log::Level::ERROR,
    });
    commands.spawn_bundle(Camera2dBundle::default());
    
    //splashscreen
    let tween_move = Tween::new(
        EaseFunction::QuadraticIn,
        TweeningType::Once,
        Duration::from_secs(10),
        TransformPositionLens {
            start: Vec3::new(0.0,0.0,100.0),
            end: Vec3::new(0.0,720.0,100.0),
        },
    ).with_completed_event(1000 as u64);
    commands.spawn_bundle(SpriteBundle {
        texture: asset_server.load("splash.png"),
        transform : Transform::from_translation(Vec3::new(0.0,0.0,100.0)),
        ..default()
    }).insert(Animator::new(tween_move));

    //ajout du fond 
    commands.spawn_bundle(SpriteBundle {
        texture: asset_server.load("scene2.png"),
        transform : Transform::from_scale(Vec3::new(BACK_SCALE,BACK_SCALE,0.0)),
        ..default()
    }).insert(BackSprite("fond".to_string()));

    //ajout du viseur
    let viewfinder = shapes::RegularPolygon {
        sides: 5,
        feature: shapes::RegularPolygonFeature::Radius(10.0),
        ..shapes::RegularPolygon::default()
    };
    commands.spawn_bundle(GeometryBuilder::build_as(
        &viewfinder,
        DrawMode::Stroke(StrokeMode::new(Color::RED, 1.0)),
        Transform::from_translation(Vec3::new(0.0,0.0,10.0)),
    )).insert(ViewFinder);

    //la limite de tir
    commands.insert_resource(FireTicker(Timer::from_seconds(0.1, true)));

    //le tireur
    let shooter_texture=asset_server.load("tireur.png");
    commands.insert_resource(ShooterTexture(shooter_texture.clone()));
    commands.spawn_bundle(SpriteBundle {
        texture: shooter_texture,
        transform : Transform::from_translation(Vec3::new(0.0,-300.0,11.3)),
        ..default()
    }).insert(TourelleGroup).insert(Tireur).insert(ShakeOnShoot);
    //la tourelle
    commands.spawn_bundle(SpriteBundle {
        texture: asset_server.load("mor1.png"),
        transform : Transform::from_translation(Vec3::new(0.0,-300.0,11.1)),
        ..default()
    }).insert(TourelleGroup);
    //le tireur
    commands.spawn_bundle(SpriteBundle {
        texture: asset_server.load("mor2.png"),
        transform : Transform::from_translation(Vec3::new(0.0,-300.0,11.2)),
        ..default()
    }).insert(TourelleGroup);
    commands.spawn().insert(Tourelle{
        ammunitions:100,
        shooting:Timer::from_seconds(10.0, false),
        is_shooting:false
    });

    //le spawner d'ennemi
    commands.insert_resource(MonsterTicker(Timer::from_seconds(1.0, true)));

    commands.insert_resource(NeedAmmoTexture(asset_server.load("need_ammo.png")));

    //texte du score
    let font = asset_server.load("fonts/FiraSans-Bold.ttf");
    let text_style = TextStyle {
        font: font,
        font_size: 22.0,
        color: Color::WHITE,
        
    };
    commands.spawn_bundle(Text2dBundle {
        text: Text::from_section("Let's kill some monsters\nPress R to reload ammo", text_style.clone()).with_alignment(TextAlignment::TOP_CENTER),
        transform:Transform::from_translation(Vec3::new(0.0,280.0,99.0)),
        ..default()
    }).insert(ScoreText{score:0});

}


fn update_score(mut query: Query<(&mut Text,&ScoreText),With<ScoreText>>){
    let (mut q,score) = query.single_mut();
    if score.score>0{
        q.sections[0].value=score.score.to_string();
    }
}

//fait trembler quand ça tire
fn shake_entities(mut query:Query<(&ShakeOnShoot,&mut Transform)>,tourelle: Query<&Tourelle>,){
    let mut rng = rand::thread_rng();
    let tourelle = tourelle.single();
    if tourelle.is_shooting{
        for (comp,mut t) in query.iter_mut(){
            let shakex:f32=rng.gen_range(-1.0..1.0);
            t.translation.x+=shakex;
            let shakey:f32=rng.gen_range(-0.5..0.5);
            t.translation.y+=shakey;
        }
    }
}

//bouge la visée avec la souris
fn mouse_follower(mut query: Query<&mut Transform,With<ViewFinder>>, mut cursor_evr: EventReader<CursorMoved>, mut windows: ResMut<Windows>){
    let window = windows.get_primary_mut().unwrap();
    window.set_cursor_visibility(false);
    for ev in cursor_evr.iter() {
        let x_mouse = ev.position.x;
        let y_mouse = ev.position.y;
        for mut transform in query.iter_mut() {
            transform.translation.x = x_mouse-window.width()/2.0;
            transform.translation.y = y_mouse-window.height()/2.0;
            let scale = (720.0-y_mouse)/720.0;
            transform.scale=Vec3::new(scale,scale,0.0);

        }
    }

}

fn move_tourelle(mut query:Query<&mut Transform,With<TourelleGroup>>,mut cursor_evr: EventReader<CursorMoved>,){
    let move_limit:f32 = 100.0;
    for ev in cursor_evr.iter() {
        let x_mouse = ev.position.x;
        let y_mouse = ev.position.y;
        for mut t in query.iter_mut(){
            t.translation.x = if t.translation.z==11.1{
                (x_mouse-640.0)/move_limit
            }else if t.translation.z==11.3{
                -(x_mouse-640.0)/move_limit
            }else{
                t.translation.x
            };
            if y_mouse>360.0{
                t.translation.y = if t.translation.z==11.1{
                    -298.0
                }else if t.translation.z==11.3{
                    -302.0
                }else{
                    t.translation.y 
                };
            }else{
                t.translation.y=-300.0;
            }
        }
    }
}

fn mouse_click_system(
    mouse_button_input: Res<Input<MouseButton>>,
    mut tourelle: Query<&mut Tourelle>,
    mut commands: Commands,
    mut vf: Query<&mut Transform,With<ViewFinder>>,
    time: Res<Time>,
    mut fire_ticker: ResMut<FireTicker>,
    asset_server: Res<AssetServer>,
    audio: Res<bevy_kira_audio::Audio>,
    mut q_tireur: Query<(&mut Tireur,&mut Handle<Image>)>,
    texture:ResMut<ShooterTexture>
) {
    //forme de la balle
    let shape = shapes::RegularPolygon {
        sides: 4,
        feature: shapes::RegularPolygonFeature::Radius(1.0),
        ..shapes::RegularPolygon::default()
    };
    //position du viseur
    let vf_y= vf.single().translation.y;
    let vf_x= vf.single().translation.x;

    let mut rng = rand::thread_rng();

    let mut tourelle = tourelle.single_mut();

    if mouse_button_input.pressed(MouseButton::Left) {
        //info!("Click @ {};{} {}",vf_x, vf_y,720.0-(360.0-vf_y));
        if fire_ticker.0.tick(time.delta()).just_finished(){
            if tourelle.ammunitions<=0{
                tourelle.is_shooting=false;
                audio.play(asset_server.load("sounds/empty.ogg"));
                return;
            }
            tourelle.ammunitions -= 1;
            tourelle.shooting.tick(time.delta());
            tourelle.is_shooting=true;
            if tourelle.shooting.just_finished(){
                info!("FINIII");
                return;
            }
            let x_al:f32=rng.gen_range(-4.0..4.0);

            let pos =Vec2::new(vf_x,vf_y);
            let target = Vec2::new(0.0,360.0);
            //info!("angle {} {} {}",pos,target, (pos-target).angle_between(pos));
            commands.spawn_bundle(
                GeometryBuilder::build_as(
                    &shape,
                    DrawMode::Fill(FillMode::color(Color::RED)),
                    Transform::from_translation(Vec3::new(0.0,-360.0,10.0)),
                )
            ).insert(Bullet{
                x:x_al,
                horizon:720.0-(360.0-vf_y),
                dead:false,
                vitesse:1.0,
                dir_x:vf_x,
                angle:(target-pos).angle_between(target)
            }).insert(Collider);

            audio.play(asset_server.load("sounds/shoot.ogg"));
            let (tireur,mut handle) = q_tireur.single_mut();
            *handle = texture.0.clone();

        }
                //info!("left mouse currently pressed");
    }
    if mouse_button_input.just_pressed(MouseButton::Left) {

    }
    if mouse_button_input.just_released(MouseButton::Left) {
        let duration = tourelle.shooting.duration();
        tourelle.shooting.set_duration(duration-time.delta());
        info!("----> {:?}",tourelle.shooting.duration());
        tourelle.is_shooting=false;
    }
}

fn move_bullets(mut query: Query<(&mut Transform,&mut Bullet,&mut DrawMode)>,){
    let mut rng = rand::thread_rng();
    let linear_speed:f32 = 0.5;
    let speed_factor:f32 = 20.0;
    let horizon_change:f32 = 2.0/3.0;
    for (mut t, mut bullet, mut draw_mode) in query.iter_mut(){
        t.translation.x = bullet.x;
        let world_y=t.translation.y+360.0;
        let distance = bullet.horizon - world_y;
        let ratio_distance = (bullet.horizon-distance)/bullet.horizon;
        t.rotation = Quat::from_rotation_z(bullet.angle);
        if distance<1.0{
            bullet.dead = true;

        }else if distance>bullet.horizon*horizon_change {
            bullet.vitesse = distance/speed_factor;
            //t.translation.y += (distance-(bullet.horizon*horizon_change))/speed_factor + linear_speed;
            t.scale.y = 8.0;
            t.scale.x = 1.5;
            //*draw_mode = DrawMode::Fill(FillMode::color(Color::BLUE))
        }else{
            bullet.vitesse = distance/speed_factor/2.0;
            //t.translation.y += (distance/bullet.horizon)*linear_speed*4.0;
            //t.translation.y += linear_speed * (distance/bullet.horizon);
            t.scale.y = 1.5;
            t.scale.x = 1.5;
            let doit:i32=rng.gen_range(1..3);
            let alpha=rng.gen_range(0.0..1.0);
            if doit>=2{
                *draw_mode = DrawMode::Fill(FillMode::color(Color::Rgba { red: 1.0, green: 0.1, blue: 0.0, alpha: alpha}))
            }
        }
        t.translation.y += bullet.vitesse;
        t.translation.x += bullet.dir_x*ratio_distance;
        //info!("distance {} reste {}", distance, bullet.angle);
    }
}

#[derive(Component)]
struct Impact{
    base_y:f32,
    timer:Timer,
    passed:bool
}

fn remove_dead_bullets(query: Query<(Entity,&Bullet,&Transform),With<Bullet>>,mut commands: Commands,asset_server: Res<AssetServer>, audio: Res<bevy_kira_audio::Audio>){
    //forme de l'impact
    let shape = shapes::RegularPolygon {
        sides: 4,
        feature: shapes::RegularPolygonFeature::Radius(2.0),
        ..shapes::RegularPolygon::default()
    };
    for (entity,bullet,t) in query.iter(){
        if bullet.dead{
            let world_y = t.translation.y+360.0;
            let volume :f32 = (720.0-world_y)/720.0;
            let yellow_power:Color = if world_y<360.0 {
                Color::rgba(1.0, 1.0, 0.0, 1.0)
            }else{
                Color::rgba(1.0, 1.0, 0.0, 1.0-(world_y/720.0))
            };
            commands.entity(entity).despawn();
            commands.spawn_bundle(
                GeometryBuilder::build_as(
                    &shape,
                    DrawMode::Fill(FillMode::color(yellow_power)),
                    Transform::from_translation(Vec3::new(t.translation.x,t.translation.y,9.0)),
                )
            ).insert(Impact{base_y:world_y, timer:Timer::from_seconds(10.0, false),passed:false});
            commands.spawn().insert(FuseTimeAudio{
                path:"sounds/sol.ogg".to_string(),
                volume:volume as f64,
                timer:Timer::from_seconds((1.0-volume)*SOUND_DISTANCE, false)
            });
            //audio.play(asset_server.load("sounds/sol.ogg"));
        }
    }
}

fn animate_impact_smoke(mut query: Query<(Entity, &mut Impact, &mut Transform,&mut DrawMode)>,mut commands: Commands,time: Res<Time>,){
    for (entity,mut impact, mut t, mut draw_mode) in query.iter_mut(){
        if impact.timer.tick(time.delta()).just_finished(){
            commands.entity(entity).despawn();
        }
        let maximize = if impact.base_y<360.0{
            (360.0-impact.base_y)/360.0/40.0
        }else{
            0.0
        };
        t.translation.y+=0.01+maximize;
        let max_scale=(720.0-(impact.base_y+360.0))/10.0;
        if t.scale.x>max_scale{
            //commands.entity(entity).despawn();
        }else{
            t.scale.x += 0.005+(maximize/2.0);
            t.scale.y += 0.01+maximize;
        }
        *draw_mode = DrawMode::Fill(FillMode::color(Color::Rgba { red: 0.05, green: 0.05, blue: 0.1, alpha: 1.0-impact.timer.elapsed_secs()/10.0}));
        //info!("=== {}",max_scale)
    }
}

fn collision(
    mut monsters: Query<(&Transform,&mut Monster),With<Monster>>,
    mut impacts: Query<(&Transform,&mut Impact),With<Impact>>,
    mut commands: Commands,
    mut score: Query<&mut ScoreText>
){
    const COLLIDE_BOX : f32 = 4.0;
    //forme de la balle
    let shape = shapes::RegularPolygon {
        sides: 4,
        feature: shapes::RegularPolygonFeature::Radius(1.0),
        ..shapes::RegularPolygon::default()
    };
    let mut rng = rand::thread_rng();
    let x_al=rng.gen_range(-400.0..400.0);
    let doit:bool=rng.gen_range(0..1)==0;
    let mut score = score.single_mut();

    for (t_monster,mut monster) in monsters.iter_mut(){
        for (t_impact, mut impact) in impacts.iter_mut(){
            if impact.passed{
                continue;
            }
            let collision = collide(
                t_monster.translation,
                Vec2::splat(COLLIDE_BOX),
                t_impact.translation,
                Vec2::splat(COLLIDE_BOX)
            );
            let volume = (720.0-impact.base_y)/720.0;
            //info!("volume {}", volume);
            if let Some(collision) = collision {
                //info!("collision! {:?}", t_monster);
                impact.passed=true;
                monster.life -= 1;
                commands.spawn().insert(FuseTimeAudio{
                    path:"sounds/explosion.ogg".to_string(),
                    volume:(volume/2.0) as f64,
                    timer:Timer::from_seconds((1.0-volume)*SOUND_DISTANCE, false)
                });
                
                if monster.life<= 0{
                    monster.dead=true;
                    score.score += 1;
                }

                //faire rebondir la balle
                if doit{
                    let tween_move = Tween::new(
                        EaseFunction::QuadraticOut,
                        TweeningType::Once,
                        Duration::from_secs(10),
                        TransformPositionLens {
                            start: Vec3::new(t_monster.translation.x,t_monster.translation.y,t_monster.translation.z),
                            end: Vec3::new(t_monster.translation.x+x_al,t_monster.translation.y+400.0,t_monster.translation.z),
                        },
                    ).with_completed_event(volume as u64);
                    commands.spawn_bundle(
                        GeometryBuilder::build_as(
                            &shape,
                            DrawMode::Fill(FillMode::color(Color::ORANGE_RED)),
                            Transform::default(),
                        )
                    ).insert(Animator::new(tween_move));
                }
            }
        }
    }
}

fn spawn_monster(mut commands: Commands,time: Res<Time>, mut monster_ticker: ResMut<MonsterTicker>,asset_server: Res<AssetServer>, audio: Res<bevy_kira_audio::Audio>){
    //forme du monstre
    let shape = shapes::RegularPolygon {
        sides: 7,
        feature: shapes::RegularPolygonFeature::Radius(4.0),
        ..shapes::RegularPolygon::default()
    };

    //position aleatoire
    let mut rng = rand::thread_rng();
    let x_al=rng.gen_range(-600.0..600.0);

    let tween_move = Tween::new(
        EaseFunction::QuadraticIn,
        TweeningType::Once,
        Duration::from_secs(100),
        TransformPositionLens {
            start: Vec3::new(x_al,-10.0,5.0),
            end: Vec3::new(0.0, -360.0, 5.0),
        },
    ).with_completed_event(44 as u64);
    let tween_down = Tween::new(
        EaseFunction::SineInOut,
        TweeningType::Once,
        Duration::from_secs(10),
        TransformPositionLens {
            start: Vec3::new(x_al,100.0,5.0),
            end: Vec3::new(x_al, -10.0, 5.0),
        },
    );
    let seq_move = tween_down.then(tween_move);

    let tween_scale = Tween::new(
        EaseFunction::QuadraticIn,
        TweeningType::Once,
        Duration::from_secs(80),
        TransformScaleLens {
            start: Vec3::new(0.2,0.2,1.0),
            end: Vec3::new(1.0,1.0,1.0),
        },
    );

    let tracks = Tracks::new([
        seq_move,
        Sequence::new([tween_scale]),
    ]);
    if monster_ticker.0.tick(time.delta()).just_finished(){
        commands.spawn_bundle(
            GeometryBuilder::build_as(
                &shape,
                DrawMode::Outlined { 
                    fill_mode: FillMode::color(Color::ALICE_BLUE), 
                    outline_mode: StrokeMode::color(Color::MIDNIGHT_BLUE)
                },
                Transform::default(),
            )
        ).insert(Animator::new(tracks)).insert(Monster{life:4,dead:false});
        audio.play(asset_server.load("sounds/monster.ogg")).with_volume(0.1);
        
    }
}

fn remove_dead_monsters(monsters : Query<(&Monster,&Transform,Entity)>, mut commands: Commands,asset_server: Res<AssetServer>, audio: Res<bevy_kira_audio::Audio>){
    //forme de la balle
    let shape = shapes::RegularPolygon {
        sides: 4,
        feature: shapes::RegularPolygonFeature::Radius(1.0),
        ..shapes::RegularPolygon::default()
    };
    let mut rng = rand::thread_rng();
    let doit:bool=rng.gen_range(0..10)==1;

    for (m,t, entity) in monsters.iter(){
        if m.dead{
            commands.entity(entity).despawn();
        }else{
            let volume = (t.translation.y+360.0)/720.0;
            if doit && volume>0.8{
                info!("vol {}",volume);
                audio.play(asset_server.load("sounds/monster.ogg"))
                .with_volume(volume as f64)
                .with_panning((t.translation.x/640.0) as f64);
            }
        }
    }
}

fn clear_bullets(
    mut query_event: EventReader<TweenCompleted>, 
    mut commands: Commands,
    mut monster_ticker: ResMut<MonsterTicker>,
    asset_server: Res<AssetServer>,
    mut q_score: Query<(&mut Text,&mut Transform,&ScoreText),With<ScoreText>>
){
    for ev in query_event.iter() {
        commands.entity(ev.entity).despawn();
        //info!("DESPAWN IT {:?}", ev.entity);
        if ev.user_data == 44{
            //le monstre est arrivé en bas c gameover!
            monster_ticker.0.pause();
            commands.spawn_bundle(SpriteBundle {
                texture: asset_server.load("gameover.png"),
                transform : Transform::from_translation(Vec3::new(0.0,0.0,101.0)),
                ..default()
            });
            let (mut text,mut t,score) = q_score.single_mut();
            text.sections[0].value = format!("Score : {}", score.score);
            t.translation.z=200.0;
        }
    }
}

fn play_delayed_audio(
    mut commands: Commands,
    mut q: Query<(Entity, &mut FuseTimeAudio)>,
    time: Res<Time>,
    asset_server: Res<AssetServer>,
    audio: Res<bevy_kira_audio::Audio>
) {
    for (entity, mut fuse_audio) in q.iter_mut() {
        // timers gotta be ticked, to work
        fuse_audio.timer.tick(time.delta());

        // if it finished, despawn the bomb
        if fuse_audio.timer.finished() {
            //info!("volume {}",fuse_audio.volume);
            audio.play(asset_server.load(&fuse_audio.path.to_string()))
            .with_volume(fuse_audio.volume)
            .with_playback_rate(fuse_audio.volume);
            commands.entity(entity).despawn();
        }
    }
}

fn keyboard_input_system(
    asset_server: Res<AssetServer>,
    keyboard_input: Res<Input<KeyCode>>,
    mut q_tourelle: Query<&mut Tourelle>,
    mut q_tireur: Query<(&mut Tireur,&mut Handle<Image>)>,
    texture:ResMut<NeedAmmoTexture>,
    audio: Res<bevy_kira_audio::Audio>
) {
    let mut tourelle = q_tourelle.single_mut();
    let (mut tireur, mut handle) = q_tireur.single_mut();
    let mut rng = rand::thread_rng();
    
    if keyboard_input.just_pressed(KeyCode::R) {
        if tourelle.ammunitions<=0 {
            let ammo_audio_indx = rng.gen_range(1..11);
            let file_path = format!("sounds/ammo/{}.ogg", ammo_audio_indx);
            audio.play(asset_server.load(&file_path.to_string()));
            tourelle.ammunitions=100;
        }
    }
    if keyboard_input.pressed(KeyCode::R) {
        *handle = texture.0.clone();
    }else{

    }

}