#![allow(dead_code)]
#![allow(clippy::type_complexity)]
#![allow(clippy::too_many_arguments)]
#![feature(trim_prefix_suffix)]

mod debug;
mod mono;

use bevy::{picking::hover::HoverMap, platform::hash::RandomState};
use debug::*;
use games::*;
use mono::*;
use std::hash::BuildHasher;

fn main() {
    App::new()
        .add_plugins(BaseGamePlugin {
            name: "trees".into(),
            ..default()
        })
        .add_plugins(MonologueAssetsPlugin)
        .add_plugins(TreesDebugPlugin)
        .add_message::<DialogEvent>()
        .add_message::<PlantTree>()
        .add_message::<AssignMonologue>()
        .init_state::<DialogState>()
        .insert_resource(ClearColor(WHITE.into()))
        .add_systems(
            Startup,
            (
                init_trees,
                init_ui,
                load_monologues,
                position_camera.after(create_camera_3d),
            ),
        )
        // When we're done loading, plant forest
        .add_systems(OnEnter(LoadingState::Idle), plant_forest.run_if(run_once))
        .add_systems(
            Update,
            (
                // Start a dialog if none are running
                start_dialog
                    .run_if(in_state(DebuggingState::Off))
                    .run_if(in_state(DialogState::None))
                    .run_if(on_message::<Pointer<Click>>),
                // Close the dialog box if it is idle (not choosing)
                end_dialog
                    .run_if(in_state(DebuggingState::Off))
                    .run_if(in_state(DialogState::Idle))
                    .run_if(on_message::<Pointer<Click>>),
                handle_plant_tree.run_if(on_message::<PlantTree>),
                assign_monologue_to_tree
                    .run_if(on_message::<AssignMonologue>)
                    .after(handle_plant_tree),
                dialog_engine.run_if(on_message::<DialogEvent>),
                auto_scroll.run_if(any_component_added::<DialogOption>),
                dialog_box_visibility.run_if(state_changed::<DialogState>),
                scale_window.run_if(on_message::<WindowResized>),
            ),
        )
        .add_observer(add_tree_monologue)
        .add_observer(remove_tree_monologue)
        .run();
}

/// Tree marker component
#[derive(Component)]
struct Tree;

#[derive(Component, PartialEq, Clone)]
struct TreeMonologue(AssetId<Monologue>);

/// Initialize the trees, currently placeholders
/// Trees are 2d cards in a 3d world for flexibility
/// Might move fully 2d if the art style allows it
fn init_trees(mut ambient_light: ResMut<AmbientLight>) {
    // Global light
    ambient_light.brightness = 500.0;
}

/// Dialog box marker component
#[derive(Component)]
struct DialogBox;

/// Initialize the UI which consists soley of a dialog box (for now?)
fn init_ui(mut commands: Commands) {
    commands
        .spawn((
            DialogBox,
            BackgroundColor(BLACK.with_alpha(0.9).into()),
            DialogState::Ongoing,
            Node {
                align_self: AlignSelf::Start,
                justify_self: JustifySelf::Center,
                width: Val::Percent(98.0),
                max_height: Val::Percent(50.0),
                align_items: AlignItems::Center,
                margin: UiRect::all(Val::Percent(1.0)),
                padding: UiRect::all(Val::Percent(1.0)),
                flex_direction: FlexDirection::Column,
                // Scroll on the Y axis
                overflow: Overflow::scroll_y(),
                ..default()
            },
        ))
        .observe(scroll)
        .observe(hover_dialog_box_over)
        .observe(hover_dialog_box_out);
}

fn hover_dialog_box_over(
    event: On<Pointer<Over>>,
    mut query: Query<&mut BackgroundColor, With<DialogBox>>,
) {
    if let Ok(mut bg) = query.get_mut(event.entity) {
        bg.0.set_alpha(0.95);
    }
}

fn hover_dialog_box_out(
    event: On<Pointer<Out>>,
    mut query: Query<&mut BackgroundColor, With<DialogBox>>,
) {
    if let Ok(mut bg) = query.get_mut(event.entity) {
        bg.0.set_alpha(0.9);
    }
}

/// On startup move the camera to a suitable position
/// This should be mostly static for the entire game
fn position_camera(mut query: Query<&mut Transform, (With<Camera>, With<Camera3d>)>) {
    use std::f32::consts::PI;

    query.iter_mut().for_each(|mut t| {
        *t = Transform::from_xyz(0.0, 100.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y);
        t.rotate_y(-PI * 0.5)
    })
}

/// Automatically scrolls dialog when a new batch of options are added
fn auto_scroll(
    added: Query<Entity, Added<DialogOption>>,
    mut scroll_positions: Query<&mut ScrollPosition>,
) {
    debug_assert!(
        !added.is_empty(),
        "Should only scroll when dialog options are added"
    );

    scroll_positions.iter_mut().for_each(|mut sp| {
        sp.y = f32::MAX;
    });
}

/// A possible line of dialog the user can choose
#[derive(Component)]
struct DialogOption;

/// A line of dialog which has been chosen, so is permanent
#[derive(Component)]
struct DialogLine;

/// Events that drive the dialog engine
#[derive(Message, PartialEq)]
enum DialogEvent {
    Start(Entity, AssetId<Monologue>),
    NextBatch,
    End,
}

/// State tracking if we are actively "doing a monologue"
#[derive(States, Debug, Hash, Eq, PartialEq, Clone, Component, Default)]
enum DialogState {
    // Dialog is running and being interacted with
    Ongoing,
    // There is no more dialog so it's just sitting there
    Idle,
    // Dialog box is not visible
    #[default]
    None,
}

/// Start dialog
fn start_dialog(
    mut click_events: MessageReader<Pointer<Click>>,
    mut dialog_events: MessageWriter<DialogEvent>,
    query: Query<&TreeMonologue, With<Tree>>,
) {
    click_events.read().for_each(|event| {
        debug!("Click event detected in start dialog systme");
        if let Ok(TreeMonologue(id)) = query.get(event.entity) {
            debug!("Tree Monologue received, sending start dialog event");
            dialog_events.write(DialogEvent::Start(event.entity, *id));
            dialog_events.write(DialogEvent::NextBatch);
        }
    })
}

/// When dialog is complete and you click away from the dialog box, close it out
fn end_dialog(
    mut click_events: MessageReader<Pointer<Click>>,
    mut dialog_events: MessageWriter<DialogEvent>,
    query: Query<Entity, Or<(With<TreeMonologue>, With<DialogBox>, With<DialogOption>)>>,
) {
    click_events.read().for_each(|event| {
        debug!("Click even evented end of dialog: {:?}", event.entity);
        if !query.contains(event.entity) {
            dialog_events.write(DialogEvent::End);
        }
    });
}

/// System which puts DialogOptions into the DialogBox
fn dialog_engine(
    // React to dialog events
    mut events: MessageReader<DialogEvent>,
    // Reference to DialogBox
    dialog_box: Single<Entity, With<DialogBox>>,
    // EntityCommands for Dialog Box
    mut commands: Commands,
    // Handle to "active" monologue
    mut asset_id: Local<AssetId<Monologue>>,
    // Track active entity as well as the monologue
    mut tree_entity: Local<Option<Entity>>,
    // Index into "active" monologue
    mut idx: Local<usize>,
    // Inform the rest of the game what state of dialog we are in
    mut next_state: ResMut<NextState<DialogState>>,
    // Monologue assets for obvious reasons
    monologues: Res<Assets<Monologue>>,
    // Dialog lines to despawn them at the end/start of a dialog
    lines: Query<Entity, With<DialogLine>>,
) {
    debug_assert!(
        !events.is_empty(),
        "Dialog engine is evented by Dialog Events"
    );

    events.read().for_each(|event| {
        match event {
            DialogEvent::Start(e, id) => {
                debug!("Dialog start: {:?}", id);

                // Set state to "Active"
                next_state.set(DialogState::Ongoing);

                // Copy monologue asset into local
                *asset_id = *id;
                *tree_entity = Some(*e);
            }
            DialogEvent::NextBatch => {
                debug!("Dialog batch");

                commands.entity(*dialog_box).with_children(|parent| {
                    // Fetch this monologue from the assets
                    if let Some(monologue) = monologues.get(*asset_id) {
                        // Fetch this batch of options
                        if let Some(batch) = monologue.get(*idx) {
                            // Spawn the dialog options in the dialog box
                            batch.lines.iter().for_each(|line| {
                                parent.spawn((
                                    Text::new(line.clone()),
                                    DialogOption,
                                    TextLayout::new(Justify::Left, LineBreak::NoWrap),
                                ));
                            });
                            *idx += 1;
                        } else {
                            // Set the dialog state to "Idle"
                            next_state.set(DialogState::Idle);
                        }
                    }
                });
            }
            DialogEvent::End => {
                debug!("Dialog ended");

                // Remove the TreeMonologue component from the tree we just "spoke to"
                commands
                    .entity(tree_entity.unwrap())
                    .remove::<TreeMonologue>();

                // Remove lines from dialog box
                lines.iter().for_each(|e| {
                    commands.entity(e).despawn();
                });

                // Reset index into the dialog options array
                *idx = 0;

                // Wipe the current handle from context
                *asset_id = AssetId::default();

                // Set state to "Active"
                next_state.set(DialogState::None);
            }
        }
    });
}

fn hover_dialog_option_over(
    event: On<Pointer<Over>>,
    mut query: Query<(&mut TextColor, &mut BackgroundColor)>,
) {
    if let Ok((mut tc, mut bg)) = query.get_mut(event.entity) {
        *tc = TextColor(DARK_ORANGE.into());
        bg.0.set_alpha(1.0);
    }
}

fn hover_dialog_option_out(
    event: On<Pointer<Out>>,
    mut query: Query<(&mut TextColor, &mut BackgroundColor)>,
) {
    if let Ok((mut tc, mut bg)) = query.get_mut(event.entity) {
        *tc = TextColor(ORANGE.into());
        bg.0.set_alpha(0.0);
    }
}

fn dialog_box_visibility(
    state: Res<State<DialogState>>,
    mut dialog_box: Single<&mut Visibility, With<DialogBox>>,
) {
    **dialog_box = if *state.get() == DialogState::None {
        Visibility::Hidden
    } else {
        Visibility::Inherited
    };
}

fn add_tree_monologue(
    event: On<Add, TreeMonologue>,
    query: Query<&MeshMaterial3d<StandardMaterial>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    // Get the affected entity's MeshMaterial3d
    if let Ok(handle) = query.get(event.entity) {
        // Get the concrete StandardMaterial
        if let Some(material) = materials.get_mut(handle) {
            material.base_color = WHITE.with_alpha(1.0).into();
        }
    }
}

fn remove_tree_monologue(
    event: On<Remove, TreeMonologue>,
    query: Query<&MeshMaterial3d<StandardMaterial>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    // Get the affected entity's MeshMaterial3d
    if let Ok(handle) = query.get(event.entity) {
        // Get the concrete StandardMaterial
        if let Some(material) = materials.get_mut(handle) {
            // Make it dull
            material.base_color = WHITE.with_alpha(0.9).into();
        }
    }
}

fn scale_window(events: MessageReader<WindowResized>, mut window: Single<&mut Window>) {
    debug_assert!(!events.is_empty(), "Only scale window when resized");

    let r = &mut window.resolution;

    let a: f32 = match r.physical_width() as usize {
        0..=640 => 0.6,
        641..=1280 => 1.0,
        1281..=1920 => 1.6,
        1921.. => 2.0,
    };

    let b: f32 = match r.physical_height() as usize {
        0..=360 => 0.6,
        361..=720 => 1.0,
        721..=1080 => 1.6,
        1081.. => 2.0,
    };

    let n = a.min(b);

    r.set_scale_factor(n);
    debug!(
        "Proposed scale factor: ({} -> {a} / {} -> {b}) {n}",
        r.width(),
        r.height(),
    );
}

/// Load all monologues so they are in the asset store and event on-load events
fn load_monologues(server: ResMut<AssetServer>, mut loaded_assets: Local<Vec<AssetId<Monologue>>>) {
    *loaded_assets = include_str!("../../../assets/trees/MONOLOGUES")
        .split("\n")
        .map(|path| server.load(path).id())
        .collect();
}

#[derive(Message)]
struct PlantTree(Option<AssetId<Monologue>>);

/// Plan a tree in the world
/// Handles random placement, 3d model, materials, and observers
fn handle_plant_tree(
    mut events: MessageReader<PlantTree>,
    mut assignments: MessageWriter<AssignMonologue>,
    trees: Query<Entity, With<Tree>>,
    server: Res<AssetServer>,
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    events.read().for_each(|PlantTree(assignment)| {
        debug!("Planing tree");
        let mut tree = commands.spawn(Tree);

        // Generate "random" X and Y Coordinates for this tree
        // 1. Take the top 2 bytes
        // 2. Interpet as u8s
        // 3. Re-interpret as i8s
        // 4. Cast to f32
        let transform = {
            let n = RandomState::default().hash_one(tree.id());
            let [a, b, ..] = n.to_be_bytes();

            let x: f32 = a.cast_signed().wrapping_div(4).into();
            let y: f32 = b.cast_signed().wrapping_div(4).into();
            // Avoid mesh clipping by offsetting each on the z axis
            let z = trees.iter().len() as f32;
            debug!("Coordiantes: {x} {y}");
            Transform::from_xyz(x, z, y).with_scale(Vec3::splat(10.0))
        };

        let material = MeshMaterial3d(materials.add(StandardMaterial {
            base_color_texture: Some(server.load("trees/placeholder/tree.png")),
            base_color: WHITE.with_alpha(0.9).into(),
            alpha_mode: AlphaMode::Blend,
            ..default()
        }));

        let mesh = Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0))));

        tree.insert((mesh, material, transform));

        if let Some(id) = assignment {
            assignments.write(AssignMonologue(*id));
        }
    });
}

#[derive(Message, Debug)]
struct AssignMonologue(AssetId<Monologue>);

/// Assign the given monologue to a tree
fn assign_monologue_to_tree(
    mut events: MessageReader<AssignMonologue>,
    query: Query<Entity, (With<Tree>, Without<TreeMonologue>)>,
    mut notice: ResMut<Notice>,
    mut commands: Commands,
    server: Res<AssetServer>,
) {
    // Kinda a weird hack because query does not update
    // If we do this inline we assign new monologues to the same first tree
    let mut t = query.iter();
    events.read().for_each(|event| {
        debug!("Assigning monologue {:?}", event);

        // Get a valid tree to assign an entity to
        if let Some(tree) = t.next() {
            // Create the TreeMonologue component
            let monologue = TreeMonologue(event.0);
            // Insert the component to the entity
            commands.entity(tree).insert(monologue);
        } else if let Some(path) = server.get_path(event.0) {
            error!("No trees avaliable for {path:?}");
            notice.0 = format!("No trees avaliable for {path:?}");
        } else {
            error!("Monologue is not yet loaded!");
            notice.0 = "Monologue is not yet loaded!".into();
        }
    });
}

/// On startup, plant a forest (add a few trees to the game)
fn plant_forest(monos: Res<Assets<Monologue>>, mut e_trees: MessageWriter<PlantTree>) {
    let mut i = 10;
    for id in monos.ids() {
        debug!("Planting tree with monologue {:?}", id);
        if i > 5 {
            e_trees.write(PlantTree(Some(id)));
        } else if i > 0 {
            e_trees.write(PlantTree(None));
        } else {
            break;
        }
        i -= 1;
    }
}
