/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2023 SteamWar.de-Serverteam
 *
 *   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, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   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 <https://www.gnu.org/licenses/>.
 */

import { readonly, writable } from "svelte/store";

import type { Readable, Subscriber, Unsubscriber } from "svelte/store";

export interface Cached<T> extends Readable<T> {
    reload: () => void;
    future: Promise<T>;
}

export function cached<T>(normal: T, init: () => Promise<T>): Cached<T> {
    const store = writable<T>(normal);
    const future = new Promise<T>((resolve) => {
        let f = false;
        store.subscribe((value) => {
            if (f) {
                resolve(value);
            } else {
                f = true;
            }
        });
    });
    let first = true;

    const reload = () => {
        init().then((data) => {
            store.set(data);
        });
    };

    return {
        ...readonly(store),
        subscribe: (run: Subscriber<T>, invalidate?: (value?: T) => void): Unsubscriber => {
            if (first) {
                first = false;
                reload();
            }
            return store.subscribe(run, invalidate);
        },
        reload,
        future,
    };
}

export function cachedFamily<T, K>(normal: K, init: (arg0: T) => Promise<K>): (arg: T) => Cached<K> {
    const stores: Map<T, Cached<K>> = new Map();
    return (arg: T) => {
        if (stores.has(arg)) {
            return stores.get(arg)!;
        } else {
            const store = writable<K>(normal);
            let first = true;

            const reload = () => {
                init(arg).then((data) => {
                    store.set(data);
                });
            };

            const cachedStore = {
                ...readonly(store),
                subscribe: (run: Subscriber<K>, invalidate?: (value?: K) => void): Unsubscriber => {
                    if (first) {
                        first = false;
                        reload();
                    }
                    return store.subscribe(run, invalidate);
                },
                reload,
            } as Cached<K>;

            stores.set(arg, cachedStore);
            return cachedStore;
        }
    };
}
