/*
    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 .
*/
//? Is this a good idea? Probably not.
export interface Recipe {
    tile?: string,
    inputs: (Item | null | undefined)[],
    outputs: (Item | null | undefined)[],
    action: "instant" | "passive" | "active" | "demand" | "demand"
    duration?: number
    revert_duration?: number,
    warn?: boolean,
    points?: number,
}
const all_items = new Set- ()
const all_recipes = new Set()
export function out(r: Recipe) {
    r.inputs.forEach(i => i ? all_items.add(i) : void 0)
    r.outputs.forEach(i => i ? all_items.add(i) : void 0)
    r.inputs = r.inputs.filter(e => e)
    r.outputs = r.outputs.filter(e => e)
    all_recipes.add(r);
}
export function finish() {
    const k = new Set()
    for (const r of all_recipes) {
        let s = `- action: !${r.action}\n`
        s += `  inputs: [${r.inputs.map(e => e!.toString()).join(",")}]\n`
        s += `  outputs: [${r.outputs.map(e => e!.toString()).join(",")}]\n`
        if (r.warn) s += `  warn: true\n`
        if (r.duration) s += `  duration: ${r.duration}\n`
        if (r.revert_duration) s += `  revert_duration: ${r.revert_duration}\n`
        if (r.points) s += `  points: ${r.points}\n`
        if (r.tile) s += `  tile: ${r.tile}\n`
        k.add(s)
    }
    for (const r of k)
        console.log(r);
}
function auto_trash() {
    for (const i of all_items) {
        if (i instanceof Container) continue
        if (!i.container) out({ action: "instant", inputs: [i], outputs: [], tile: "trash" })
        else {
            out({
                action: "instant",
                inputs: [i],
                outputs: [i.container.dispose ?? i.container],
                tile: i.container.dispose_tile ?? "trash"
            })
        }
    }
}
class Item {
    constructor(
        public name: string,
        public container?: Container
    ) { }
    as(s: string) { this.name = s; return this }
    tr(container?: Container) {
        const o = new Item(this.name, container)
        if (this.container) out({ action: "instant", inputs: [this, container], outputs: [this.container, o] })
        else out({ action: "instant", inputs: [container, this], outputs: [o] })
        return o
    }
    toString() {
        return this.name + (this.container ? "-" + this.container : "")
    }
}
class Container extends Item { constructor(name: string, public dispose?: Item, public dispose_tile?: string) { super(name) } }
const FP = new Container("foodprocessor")
const POT = new Container("pot")
const PL = new Container("plate", new Container("dirty-plate"))
const GL = new Container("glass", undefined, "sink")
function crate(s: string): Item {
    const item = new Item(s);
    out({ action: "instant", inputs: [], outputs: [item], tile: `${s}-crate`, points: -1 })
    out({ action: "instant", inputs: [item], outputs: [], tile: `${s}-crate`, points: 1 })
    return item
}
function cut(s: Item, two?: boolean): Item {
    const o = new Item(`sliced-${s.name}`, s.container)
    out({ action: "active", inputs: [s], outputs: [o, two ? o : null], tile: "cuttingboard", duration: 2 })
    return o
}
function cook(s: Item, duration = 20): Item {
    const o = new Item(`cooked-${s.name}`, s.container)
    out({ action: "passive", duration, tile: "stove", inputs: [s], outputs: [o] })
    out({ action: "passive", duration: duration / 3, revert_duration: 20, tile: "stove", inputs: [o], outputs: [new Item("burned", POT)], warn: true })
    return o
}
function bake(s: Item, duration = 25): Item {
    const o = new Item(`sliced-${s.name}`, s.container)
    out({ action: "passive", duration, tile: "oven", inputs: [s], outputs: [o] })
    out({ action: "passive", duration: duration / 2, revert_duration: 20, tile: "oven", inputs: [o], outputs: [new Item("burned")], warn: true })
    return o
}
function freeze(s: Item): Item {
    const o = new Item(`frozen-${s.name}`, s.container)
    out({ action: "passive", duration: 25, tile: "freezer", inputs: [s], outputs: [o] })
    return o
}
function process(s: Item): Item {
    const o = new Item(`processed-${s.name}`, s.container)
    out({ action: "passive", duration: 5, inputs: [s], outputs: [o] })
    return o
}
function container_add(base: Item, add: Item): Item {
    const o = new Item("!!!", base.container)
    out({ action: "instant", inputs: [base, add], outputs: [o, add.container] })
    return o
}
function combine(c: Container, ...items: Item[]): Item {
    const open = items.map(i => (i.tr(c), [i.name]))
    const seen = new Set()
    let result
    while (1) {
        const cur = open.pop()
        if (!cur) break;
        for (const new_item of items) {
            if (cur.includes(new_item.name)) continue
            const rkey = cur.join("-") + "#" + new_item
            if (seen.has(rkey)) continue
            seen.add(rkey)
            const parts = [...cur, new_item.name]
            parts.sort()
            open.push(parts)
            const i = new Item(cur.join("-"), c)
            const o = new Item(parts.join("-"), c)
            if (parts.length == items.length) result = o
            out({
                action: "instant",
                inputs: [i, new_item],
                outputs: [o, new_item.container]
            })
        }
    }
    return result!
}
function edible(...items: Item[]) {
    for (const i of items) {
        out({ action: "demand", inputs: [i], outputs: [i.container?.dispose ?? i.container], duration: 10 })
    }
}
function either(a: Item, b: Item) {
    if (a.name != b.name) throw new Error("either options are named differently");
    if (a.container != b.container) throw new Error("either options are contained differently");
    return a
}
function sink_fill(c: Container) {
    const o = new Item("water", c)
    out({ action: "active", inputs: [c], outputs: [o], tile: "sink", duration: 1 })
    return o
}
out({ action: "active", duration: 2, tile: "sink", inputs: [new Container("dirty-plate")], outputs: [PL] })
const tomato = crate("tomato")
const raw_steak = crate("raw-steak")
const flour = crate("flour")
const leek = crate("leek")
const rice = crate("rice")
const fish = crate("fish")
const coconut = crate("coconut")
const strawberry = crate("strawberry")
// Bread
const dough = process(flour.tr(FP)).as("dough").tr()
const bread = bake(dough).as("bread")
const bread_slice = cut(bread, true).as("bread-slice")
edible(bread)
// Burger
const steak_pot = cook(raw_steak.tr(POT)).as("steak")
const sliced_tomato = cut(tomato).as("sliced-tomato")
edible(
    combine(PL, steak_pot, sliced_tomato, bread_slice),
    combine(PL, sliced_tomato, bread_slice),
    combine(PL, steak_pot, bread_slice),
    combine(PL, sliced_tomato, steak_pot),
    sliced_tomato.tr(PL),
)
// Soup
const tomato_juice = process(tomato.tr(FP)).as("tomato-juice")
const leek_tj_pot = combine(POT, leek, tomato_juice)
const tomato_soup_plate = cook(leek_tj_pot).as("tomato-soup").tr(PL)
edible(tomato_soup_plate)
// Rice and nigiri
const nigiri = container_add(cut(fish), cook(rice.tr(POT))).as("nigiri").tr(PL)
edible(nigiri)
// coconut milk and strawberry puree
const strawberry_puree = process(strawberry.tr(FP)).as("strawberry-puree")
const milk = process(coconut.tr(FP)).as("milk")
const strawberry_shake = either(
    process(container_add(milk, strawberry).as("milk-strawberry")).as("strawberry-shake"),
    process(container_add(strawberry_puree, coconut).as("coconut-strawberry-puree")).as("strawberry-shake")
)
// Icecream
const strawberry_icecream = freeze(strawberry_shake).as("strawberry-icecream").tr(PL)
edible(strawberry_icecream)
// Mochi
const rice_flour = process(rice.tr(FP)).as("rice-flour")
const mochi_dough = cook(rice_flour.tr(POT), 5).as("mochi-dough")
const strawberry_mochi = container_add(strawberry, mochi_dough).as("strawberry-mochi")
edible(strawberry_mochi)
// Drinks
edible(
    strawberry_shake.tr(GL),
    tomato_juice.tr(GL),
    sink_fill(GL)
)
// Curry
const curry_with_rice = combine(PL, cook(rice.tr(POT)), cook(combine(POT, milk, tomato, leek)).as("curry"))
edible(curry_with_rice)
auto_trash()
finish()