/*
    Undercooked - 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::Gamedata,
    game::{Involvement, Item, Tile},
    protocol::{ItemIndex, TileIndex},
};
use log::info;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Recipe {
    Passive {
        duration: f32,
        revert_duration: Option,
        tile: Option,
        input: ItemIndex,
        output: Option,
        warn: bool,
    },
    Active {
        duration: f32,
        tile: Option,
        input: ItemIndex,
        outputs: [Option; 2],
    },
    Instant {
        tile: Option,
        inputs: [Option; 2],
        outputs: [Option; 2],
        points: i64,
    },
}
impl Recipe {
    pub fn tile(&self) -> Option {
        match self {
            Recipe::Passive { tile, .. } => *tile,
            Recipe::Active { tile, .. } => *tile,
            Recipe::Instant { tile, .. } => *tile,
        }
    }
    pub fn duration(&self) -> Option {
        match self {
            Recipe::Passive { duration, .. } => Some(*duration),
            Recipe::Active { duration, .. } => Some(*duration),
            _ => None,
        }
    }
    pub fn revert_duration(&self) -> Option {
        match self {
            Recipe::Passive {
                revert_duration, ..
            } => *revert_duration,
            _ => None,
        }
    }
    pub fn warn(&self) -> bool {
        match self {
            Recipe::Passive { warn, .. } => *warn,
            _ => false,
        }
    }
    pub fn inputs(&self) -> Vec {
        match self {
            Recipe::Passive { input, .. } => vec![*input],
            Recipe::Active { input, .. } => vec![*input],
            Recipe::Instant { inputs, .. } => {
                inputs.into_iter().flat_map(|e| e.to_owned()).collect()
            }
        }
    }
    pub fn outputs(&self) -> Vec {
        match self {
            Recipe::Passive { output, .. } => output.to_owned().into_iter().collect(),
            Recipe::Active { outputs, .. } => {
                outputs.into_iter().flat_map(|e| e.to_owned()).collect()
            }
            Recipe::Instant { outputs, .. } => {
                outputs.into_iter().flat_map(|e| e.to_owned()).collect()
            }
        }
    }
    pub fn supports_tile(&self, tile: TileIndex) -> bool {
        if let Some(tile_constraint) = self.tile() {
            if tile != tile_constraint {
                false
            } else {
                true
            }
        } else {
            true
        }
    }
}
pub enum InteractEffect {
    Put,
    Take,
    Produce,
}
pub fn interact(
    data: &Gamedata,
    edge: bool,
    tile_kind: TileIndex,
    this: &mut Option- ,
    other: &mut Option- ,
    points: &mut i64,
) -> Option {
    let interactable = data.is_tile_interactable(tile_kind);
    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_kind) {
                    if let Recipe::Active { outputs, .. } = recipe {
                        if edge {
                            active.working += 1;
                        } else {
                            active.working -= 1;
                            if active.progress >= 1. {
                                *other = outputs[0].map(|kind| Item { kind, active: None });
                                *this = outputs[1].map(|kind| Item { kind, active: None });
                                return Some(InteractEffect::Produce);
                            }
                        }
                    }
                }
            }
        }
    }
    if !edge {
        return None;
    }
    if interactable {
        for (ri, recipe) in data.recipes() {
            if !recipe.supports_tile(tile_kind) {
                continue;
            }
            match recipe {
                Recipe::Active { input, .. } => {
                    if other.is_none() {
                        if let Some(item) = this {
                            if item.kind == *input {
                                if item.active.is_none() {
                                    info!("start active recipe {ri:?}");
                                    item.active = Some(Involvement {
                                        recipe: ri,
                                        working: 1,
                                        progress: 0.,
                                    });
                                }
                            }
                        }
                    }
                    if this.is_none() {
                        if let Some(item) = &other {
                            if item.kind == *input {
                                let mut item = other.take().unwrap();
                                if let Some(active) = &mut item.active {
                                    active.working += 1;
                                } else {
                                    info!("start active recipe {ri:?}");
                                    item.active = Some(Involvement {
                                        recipe: ri,
                                        working: 1,
                                        progress: 0.,
                                    });
                                }
                                *this = Some(item);
                                return Some(InteractEffect::Put);
                            }
                        }
                    }
                }
                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;
                        *other = outputs[1 - ok_rev].map(|kind| Item { kind, active: None });
                        *this = outputs[ok_rev].map(|kind| Item { kind, active: None });
                        *points += pd;
                        return Some(InteractEffect::Produce);
                    }
                }
                _ => (),
            }
        }
    }
    if interactable && this.is_none() {
        if let Some(item) = other.take() {
            *this = Some(item);
            return Some(InteractEffect::Put);
        }
    }
    if other.is_none() {
        if let Some(item) = this.take() {
            *other = Some(item);
            return Some(InteractEffect::Take);
        }
    }
    None
}
pub enum TickEffect {
    Progress(bool),
    Produce,
}
pub fn tick_tile(dt: f32, data: &Gamedata, tile: &mut Tile) -> Option {
    if let Some(item) = &mut tile.item {
        if let Some(a) = &mut item.active {
            let r = &data.recipe(a.recipe);
            if r.supports_tile(tile.kind) {
                a.progress += a.working as f32 * dt / r.duration().unwrap();
            } else if let Some(revert_duration) = r.revert_duration() {
                a.progress -= dt / revert_duration;
            }
            if a.progress >= 1. {
                if let Recipe::Passive { output, .. } = &data.recipe(a.recipe) {
                    tile.item = output.map(|kind| Item { kind, active: None });
                    return Some(TickEffect::Produce);
                };
                a.progress = 1.;
            }
            if a.progress < 0. {
                item.active = None;
            }
            return Some(TickEffect::Progress(r.warn()));
        } else {
            for (ri, recipe) in data.recipes() {
                if recipe.supports_tile(tile.kind) {
                    if let Recipe::Passive { input, .. } = recipe {
                        if *input == item.kind {
                            item.active = Some(Involvement {
                                recipe: ri,
                                progress: 0.,
                                working: 1,
                            });
                            return Some(TickEffect::Progress(recipe.warn()));
                        }
                    }
                }
            }
        }
    }
    None
}