/*
    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 => JSON.stringify(e!.toString())).join(",")}]\n`
        s += `  outputs: [${r.outputs.map(e => JSON.stringify(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"
            })
        }
    }
}
function auto_burn() {
    for (const i of [...all_items]) {
        if (i instanceof Container) continue
        if (i.container || i.name == "burned") continue
        else {
            out({
                action: "passive",
                inputs: [i],
                outputs: [new Item("burned")],
                duration: 1.5,
                revert_duration: 1.5,
                warn: true,
                tile: "stove"
            })
        }
    }
}
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 == container) return o
        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.container ? this.container + ":" : "") + this.name
    }
}
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 PAN = new Container("pan")
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 sear(s: Item, duration = 15): Item {
    s = s.tr(PAN)
    const o = new Item(`seared-${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", PAN)], 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 {
    if (items.length == 1) return items[0].tr(c)
    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 steak = crate("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")
const cheese = crate("cheese")
const lettuce = crate("lettuce")
// Buns
const dough = process(flour.tr(FP)).as("dough").tr()
const bun = bake(dough).as("bun")
edible(bun.tr(PL))
// Steak
const seared_steak = sear(steak)
edible(
    combine(PL, seared_steak, bun),
    combine(PL, seared_steak),
)
// Salad
edible(
    combine(PL, cut(tomato), cut(lettuce)),
    combine(PL, cut(lettuce)),
)
// Burger
const b_patty = sear(cut(steak).as("patty"))
edible(
    combine(PL, cut(bun), b_patty, cut(cheese)),
    combine(PL, cut(bun), b_patty, cut(cheese), cut(lettuce)),
    combine(PL, cut(bun), b_patty, cut(tomato), cut(lettuce)),
    combine(PL, cut(bun), b_patty, cut(cheese), cut(tomato)),
    combine(PL, cut(bun), cut(cheese), cut(lettuce), cut(tomato)),
    combine(PL, cut(bun), cut(lettuce), cut(tomato)),
)
// 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
edible(freeze(strawberry_shake).as("strawberry-icecream").tr(PL))
// 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()
auto_burn()
finish()