/* typehints:start */
import { Component } from "../game/component";
import { GameRoot } from "../game/root";
/* typehints:end */

import { ExplainedResult } from "../core/explained_result";
import { createLogger } from "../core/logging";
// import { BuildingComponent } from "../components/impl/building";
import { gComponentRegistry } from "../core/global_registries";
import { SerializerInternal } from "./serializer_internal";

const logger = createLogger("savegame_serializer");

/**
 * Allows to serialize a savegame
 */
export class SavegameSerializer {
    constructor() {
        this.internal = new SerializerInternal();
    }

    /**
     * Serializes the game root into a dump
     * @param {GameRoot} root
     * @param {boolean=} sanityChecks Whether to check for validity
     * @returns {object}
     */
    generateDumpFromGameRoot(root, sanityChecks = true) {
        // Now store generic savegame payload
        const data = {
            camera: root.camera.serialize(),
            time: root.time.serialize(),
            map: root.map.serialize(),
            entityMgr: root.entityMgr.serialize(),
            hubGoals: root.hubGoals.serialize(),
            pinnedShapes: root.hud.parts.pinnedShapes.serialize(),
            waypoints: root.hud.parts.waypoints.serialize(),
            beltPaths: root.systemMgr.systems.belt.serializePaths(),
        };

        data.entities = this.internal.serializeEntityArray(root.entityMgr.entities);

        if (!G_IS_RELEASE) {
            if (sanityChecks) {
                // Sanity check
                const sanity = this.verifyLogicalErrors(data);
                if (!sanity.result) {
                    logger.error("Created invalid savegame:", sanity.reason, "savegame:", data);
                    return null;
                }
            }
        }
        return data;
    }

    /**
     * Verifies if there are logical errors in the savegame
     * @param {object} savegame
     * @returns {ExplainedResult}
     */
    verifyLogicalErrors(savegame) {
        if (!savegame.entities) {
            return ExplainedResult.bad("Savegame has no entities");
        }

        const seenUids = [];

        // Check for duplicate UIDS
        for (const entityListId in savegame.entities) {
            for (let i = 0; i < savegame.entities[entityListId].length; ++i) {
                const list = savegame.entities[entityListId][i];
                for (let k = 0; k < list.length; ++k) {
                    const entity = list[k];
                    const uid = entity.uid;
                    if (!Number.isInteger(uid)) {
                        return ExplainedResult.bad("Entity has invalid uid: " + uid);
                    }
                    if (seenUids.indexOf(uid) >= 0) {
                        return ExplainedResult.bad("Duplicate uid " + uid);
                    }
                    seenUids.push(uid);

                    // Verify components
                    if (!entity.components) {
                        return ExplainedResult.bad(
                            "Entity is missing key 'components': " + JSON.stringify(entity)
                        );
                    }
                    const components = entity.components;
                    for (const componentId in components) {
                        // Verify component data
                        const componentData = components[componentId];
                        const componentClass = gComponentRegistry.findById(componentId);

                        // Check component id is known
                        if (!componentClass) {
                            return ExplainedResult.bad("Unknown component id: " + componentId);
                        }

                        // Check component data is ok
                        const componentVerifyError = /** @type {typeof Component} */ (componentClass).verify(
                            componentData
                        );
                        if (componentVerifyError) {
                            return ExplainedResult.bad(
                                "Component " + componentId + " has invalid data: " + componentVerifyError
                            );
                        }
                    }
                }
            }
        }

        return ExplainedResult.good();
    }

    /**
     * Tries to load the savegame from a given dump
     * @param {import("./savegame_typedefs").SerializedGame} savegame
     * @param {GameRoot} root
     * @returns {ExplainedResult}
     */
    deserialize(savegame, root) {
        // Sanity
        const verifyResult = this.verifyLogicalErrors(savegame);
        if (!verifyResult.result) {
            return ExplainedResult.bad(verifyResult.reason);
        }
        let errorReason = null;

        errorReason = errorReason || root.entityMgr.deserialize(savegame.entityMgr);
        errorReason = errorReason || root.time.deserialize(savegame.time);
        errorReason = errorReason || root.camera.deserialize(savegame.camera);
        errorReason = errorReason || root.map.deserialize(savegame.map);
        errorReason = errorReason || root.hubGoals.deserialize(savegame.hubGoals);
        errorReason = errorReason || root.hud.parts.pinnedShapes.deserialize(savegame.pinnedShapes);
        errorReason = errorReason || root.hud.parts.waypoints.deserialize(savegame.waypoints);
        errorReason = errorReason || this.internal.deserializeEntityArray(root, savegame.entities);
        errorReason = errorReason || root.systemMgr.systems.belt.deserializePaths(savegame.beltPaths);

        // Check for errors
        if (errorReason) {
            return ExplainedResult.bad(errorReason);
        }

        return ExplainedResult.good();
    }
}
