/*
 *     The Peacock Project - a HITMAN server replacement.
 *     Copyright (C) 2021-2023 The Peacock Project Team
 *
 *     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 { Controller } from "./controller"
import { existsSync, readFileSync } from "fs"
import { getFlag } from "./flags"
import { log, LogLevel } from "./loggingInterop"
import { MissionManifest, SMFLastDeploy } from "./types/types"
import { basename, join } from "path"
import { readFile } from "fs/promises"
import { menuSystemDatabase } from "./menus/menuSystem"
import { parse } from "json5"

type LastServerSideData = SMFLastDeploy["lastServerSideStates"]

export class SMFSupport {
    public readonly lastDeploy: SMFLastDeploy | null

    constructor(private readonly controller: Controller) {
        const dataPath = SMFSupport.modFrameworkDataPath

        if (dataPath && existsSync(dataPath)) {
            this.lastDeploy = parse(readFileSync(dataPath).toString())
            return
        }

        this.lastDeploy = null
    }

    static get modFrameworkDataPath() {
        return (
            (process.env.LOCALAPPDATA &&
                join(
                    process.env.LOCALAPPDATA,
                    "Simple Mod Framework",
                    "lastDeploy.json",
                )) ||
            false
        )
    }

    private async executePlugin(plugin: string) {
        if (!existsSync(plugin)) return

        await this.controller.executePlugin(
            basename(plugin),
            (await readFile(plugin)).toString(),
            plugin,
        )
    }

    private handleBlobs(lastServerSideData: LastServerSideData) {
        if (!lastServerSideData?.blobs) return

        menuSystemDatabase.hooks.getConfig.tap(
            "SMFBlobs",
            (name: string, gameVersion: string) => {
                if (
                    !(
                        gameVersion === "h3" &&
                        (lastServerSideData.blobs?.[name] ||
                            lastServerSideData.blobs?.[name.slice(1)])
                    )
                ) {
                    return
                }

                if (!process.env.LOCALAPPDATA) return

                return parse(
                    readFileSync(
                        join(
                            process.env.LOCALAPPDATA as string,
                            "Simple Mod Framework",
                            "blobs",
                            lastServerSideData.blobs[name] ||
                                lastServerSideData.blobs[name.slice(1)],
                        ),
                    ).toString(),
                )
            },
        )
    }

    private handleContracts(lastServerSideData: LastServerSideData) {
        if (!lastServerSideData?.contracts) return

        for (const contractData of Object.values(
            lastServerSideData.contracts,
        )) {
            this.controller.addMission(contractData)

            if (contractData.SMF?.destinations?.addToDestinations) {
                if (
                    contractData.SMF.destinations.peacockIntegration !== false
                ) {
                    this.handleDestination(contractData)
                }
            }
        }
    }

    private handleDestination(contractData: MissionManifest) {
        const location = contractData.Metadata.Location
        const id = contractData.Metadata.Id
        const placeBefore = contractData.SMF?.destinations.placeBefore
        const placeAfter = contractData.SMF?.destinations.placeAfter
        const inLocation = this.controller.missionsInLocations[
            location
        ] as string[]

        if (placeBefore) {
            const index = inLocation.indexOf(placeBefore)
            inLocation.splice(index, 0, id)
        } else if (placeAfter) {
            const index = inLocation.indexOf(placeAfter) + 1
            inLocation.splice(index, 0, id)
        } else {
            inLocation.push(id)
        }
    }

    private handleUnlockables(lastServerSideData: LastServerSideData) {
        if (lastServerSideData?.unlockables) {
            this.controller.configManager.configs["allunlockables"] =
                lastServerSideData.unlockables.slice(1)
        }
    }

    public async initSMFSupport(modFrameworkDataPath: string) {
        if (!(modFrameworkDataPath && existsSync(modFrameworkDataPath))) {
            return
        }

        log(
            LogLevel.INFO,
            "Simple Mod Framework installed - using the data it outputs.",
            "boot",
        )

        const lastServerSideData = this.lastDeploy?.lastServerSideStates

        this.handleUnlockables(lastServerSideData)
        this.handleContracts(lastServerSideData)
        this.handleBlobs(lastServerSideData)

        if (lastServerSideData?.peacockPlugins) {
            for (const plugin of lastServerSideData.peacockPlugins) {
                await this.executePlugin(plugin)
            }
        }
    }

    /**
     * Returns whether a mod is available and installed.
     *
     * @param modId The mod's ID.
     * @returns If the mod is available (or the `overrideFrameworkChecks` flag is set). You should probably abort initialisation if false is returned.
     */
    public modIsInstalled(modId: string): boolean {
        return (
            this.lastDeploy?.loadOrder.includes(modId) ||
            getFlag("overrideFrameworkChecks") === true
        )
    }
}