/*
    Hurry Curry! - a game about cooking
    Copyright 2024 metamuffin
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, version 3 of the License only.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.
    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see .
*/
use crate::data::index::GamedataIndex;
use hurrycurry_client_lib::{Involvement, Item};
use hurrycurry_protocol::{Gamedata, ItemLocation, PacketC, PlayerID, Recipe, Score, TileIndex};
use log::info;
use std::collections::VecDeque;
#[allow(clippy::too_many_arguments)]
pub fn interact(
    data: &Gamedata,
    edge: bool,
    tile: Option,
    player: Option,
    this: &mut Option- ,
    this_loc: ItemLocation,
    other: &mut Option- ,
    other_loc: ItemLocation,
    score: &mut Score,
    score_changed: &mut bool,
    automated: bool,
    packet_out: &mut VecDeque,
) {
    let interactable = automated
        || tile
            .map(|tile| data.is_tile_interactable(tile))
            .unwrap_or(true);
    if interactable && other.is_none() {
        if let Some(item) = this {
            if let Some(active) = &mut item.active {
                let recipe = &data.recipe(active.recipe);
                if recipe.supports_tile(tile) {
                    if let Recipe::Active { outputs, speed, .. } = recipe {
                        if edge {
                            active.speed += speed;
                        } else {
                            active.speed -= speed;
                            active.speed = active.speed.max(0.); // in case of "release without press" when items cool on active tile
                        }
                        if active.position >= 1. {
                            let this_had_item = this.is_some();
                            let other_had_item = other.is_some();
                            *other = outputs[0].map(|kind| Item { kind, active: None });
                            *this = outputs[1].map(|kind| Item { kind, active: None });
                            produce(
                                this_had_item,
                                other_had_item,
                                this,
                                this_loc,
                                other,
                                other_loc,
                                score_changed,
                                packet_out,
                            );
                        } else {
                            packet_out.push_back(PacketC::SetProgress {
                                player,
                                item: this_loc,
                                position: active.position,
                                speed: active.speed,
                                warn: active.warn,
                            });
                        }
                        return;
                    }
                }
            }
        }
    }
    if !edge {
        return;
    }
    if interactable {
        for (ri, recipe) in data.recipes() {
            if !recipe.supports_tile(tile) {
                continue;
            }
            match recipe {
                Recipe::Active { input, speed, .. } => {
                    if other.is_none() {
                        if let Some(item) = this {
                            if item.kind == *input && item.active.is_none() {
                                info!("start active recipe {ri:?}");
                                item.active = Some(Involvement {
                                    player,
                                    recipe: ri,
                                    speed: *speed,
                                    position: 0.,
                                    warn: false,
                                });
                            }
                        }
                    }
                    if this.is_none() {
                        if let Some(item) = &other {
                            if item.kind == *input && item.active.is_none() {
                                let mut item = other.take().unwrap();
                                info!("start active recipe {ri:?}");
                                item.active = Some(Involvement {
                                    player,
                                    recipe: ri,
                                    speed: *speed,
                                    position: 0.,
                                    warn: false,
                                });
                                *this = Some(item);
                                score.active_recipes += 1;
                                packet_out.push_back(PacketC::MoveItem {
                                    from: other_loc,
                                    to: this_loc,
                                });
                                packet_out.push_back(PacketC::SetProgress {
                                    player,
                                    item: this_loc,
                                    position: 0.,
                                    speed: *speed,
                                    warn: false,
                                });
                                return;
                            }
                        }
                    }
                }
                Recipe::Instant {
                    inputs,
                    outputs,
                    points: pd,
                    ..
                } => {
                    let on_tile = this.as_ref().map(|i| i.kind);
                    let in_hand = other.as_ref().map(|i| i.kind);
                    let ok = inputs[0] == on_tile && inputs[1] == in_hand;
                    let ok_rev = inputs[1] == on_tile && inputs[0] == in_hand;
                    if ok || ok_rev {
                        info!("instant recipe {ri:?} reversed={ok_rev}");
                        let ok_rev = ok_rev as usize;
                        let this_had_item = this.is_some();
                        let other_had_item = other.is_some();
                        *other = outputs[1 - ok_rev].map(|kind| Item { kind, active: None });
                        *this = outputs[ok_rev].map(|kind| Item { kind, active: None });
                        score.points += pd;
                        score.instant_recipes += 1;
                        *score_changed = true;
                        produce(
                            this_had_item,
                            other_had_item,
                            this,
                            this_loc,
                            other,
                            other_loc,
                            score_changed,
                            packet_out,
                        );
                        return;
                    }
                }
                _ => (),
            }
        }
    }
    if interactable && this.is_none() {
        if let Some(item) = other.take() {
            *this = Some(item);
            packet_out.push_back(PacketC::MoveItem {
                from: other_loc,
                to: this_loc,
            });
            return;
        }
    }
    if other.is_none() {
        if let Some(item) = this.take() {
            *other = Some(item);
            packet_out.push_back(PacketC::MoveItem {
                from: this_loc,
                to: other_loc,
            });
        }
    }
}
pub enum TickEffect {
    Progress {
        speed: f32,
        position: f32,
        warn: bool,
    },
    ClearProgress,
    Produce,
}
#[allow(clippy::too_many_arguments)]
pub fn tick_slot(
    dt: f32,
    data: &Gamedata,
    data_index: &GamedataIndex,
    tile: Option,
    slot: &mut Option- ,
    slot_loc: ItemLocation,
    score: &mut Score,
    score_changed: &mut bool,
    packet_out: &mut VecDeque,
) {
    if let Some(item) = slot {
        if let Some(a) = &mut item.active {
            let r = &data.recipe(a.recipe);
            let prev_speed = a.speed;
            if r.supports_tile(tile) {
                if a.speed <= 0. {
                    if let Recipe::Passive { speed, .. } = &data.recipe(a.recipe) {
                        a.speed = *speed;
                    }
                }
            } else if let Some(revert_speed) = r.revert_speed() {
                a.speed = -revert_speed
            } else {
                a.speed = 0.;
            }
            if a.position < 0. {
                item.active = None;
                packet_out.push_back(PacketC::ClearProgress { item: slot_loc });
                return;
            }
            if a.position >= 1. {
                if let Recipe::Passive { output, .. } = &data.recipe(a.recipe) {
                    *slot = output.map(|kind| Item { kind, active: None });
                    score.passive_recipes += 1;
                    *score_changed = true;
                    packet_out.push_back(PacketC::SetProgress {
                        player: None,
                        warn: false,
                        item: slot_loc,
                        position: 1.,
                        speed: 0.,
                    });
                    packet_out.push_back(PacketC::SetItem {
                        location: slot_loc,
                        item: slot.as_ref().map(|i| i.kind),
                    });
                    return;
                };
            }
            a.position += dt * a.speed;
            a.position = a.position.min(1.);
            if a.speed != prev_speed {
                packet_out.push_back(PacketC::SetProgress {
                    player: None,
                    position: a.position,
                    speed: a.speed,
                    warn: a.warn,
                    item: slot_loc,
                });
            }
        } else if let Some(recipes) = data_index.recipe_passive_by_input.get(&item.kind) {
            for &ri in recipes {
                let recipe = data.recipe(ri);
                if recipe.supports_tile(tile) {
                    if let Recipe::Passive {
                        input, warn, speed, ..
                    } = recipe
                    {
                        if *input == item.kind {
                            item.active = Some(Involvement {
                                player: None,
                                recipe: ri,
                                position: 0.,
                                warn: *warn,
                                speed: *speed,
                            });
                            packet_out.push_back(PacketC::SetProgress {
                                player: None,
                                position: 0.,
                                speed: *speed,
                                warn: *warn,
                                item: slot_loc,
                            });
                            return;
                        }
                    }
                }
            }
        }
    }
}
#[allow(clippy::too_many_arguments)]
fn produce(
    this_had_item: bool,
    other_had_item: bool,
    this: &Option- ,
    this_loc: ItemLocation,
    other: &Option- ,
    other_loc: ItemLocation,
    score_changed: &mut bool,
    packet_out: &mut VecDeque,
) {
    info!("produce {this_loc} <~ {other_loc}");
    *score_changed = true;
    if this_had_item {
        packet_out.push_back(PacketC::SetItem {
            location: this_loc,
            item: None,
        });
    }
    if other_had_item {
        packet_out.push_back(PacketC::MoveItem {
            from: other_loc,
            to: this_loc,
        });
        packet_out.push_back(PacketC::SetItem {
            location: this_loc,
            item: None,
        });
    }
    if let Some(i) = &other {
        packet_out.push_back(PacketC::SetItem {
            location: this_loc,
            item: Some(i.kind),
        });
        packet_out.push_back(PacketC::MoveItem {
            from: this_loc,
            to: other_loc,
        })
    }
    if let Some(i) = &this {
        packet_out.push_back(PacketC::SetItem {
            location: this_loc,
            item: Some(i.kind),
        });
    }
}