/// <reference path="World/Classes/Save/StoredVariable.ts" />
/// <reference path="World/TurnSequence.ts" />
/// <reference path="World/Classes/Rule.ts" />
/// <reference path="Controls/Classes/StoredMemory.ts" />
interface SavedThing {
    Name : string;
    Unique : boolean;
    Changes : {[id : string] : any}
}

// The player is always a humanoid
interface SavedPlayer {
    Parts : Array<SavedThing>;
    PlayerChanges : {[id : string] : any};
    Name : string; // Are we going to do names? I Mean it'd be fun
}

interface SaveStructure {
    Variables : {[id : string] : any};
    Wielded : Array<SavedThing>;
    Worn : Array<SavedThing>;
    Carried : Array<SavedThing>;
    Player : SavedPlayer;
    Rounds : number;
    Date : string;
    UniqueThings : Array<SavedThing>;
}

module SaveHandler {
    let saveName = "Obelisk_Save"
    let saveExtension = "obsav";
    let storagePrefix = "save_";
    let saveSlot = 0;
    let errors : Array<string> = [];
    let erasing = false;
    let virgin = new StoredMemory("First time saving", true);

    export let AfterLoad = new Rulebook<SaveStructure>("After loading");

    export async function readFile () : Promise<string> {
        let element = <HTMLInputElement> document.createElement("input");
        element.type = "file";
        element.accept = "." + saveExtension;

        let promise : Promise<string> = new Promise((resolve, reject) => {
            element.onchange = () => {
                if (element.files.length == 0) {
                    resolve(undefined);
                } else {
                    var fr = new FileReader();

                    fr.onload = (ev) => {
                        resolve(ev.target['result']);
                    }

                    fr.readAsText(element.files[0]);
                }
            }
        });

        element.click();
        return promise;
    }

    function download(filename, text) {
        var element = document.createElement('a');
        element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
        element.setAttribute('download', filename);

        element.style.display = 'none';
        document.body.appendChild(element);

        element.click();

        document.body.removeChild(element);
    }

    export function loadSave () {
        let input = <HTMLInputElement> document.createElement("input");
        input.type = "file";
        input.accept = "." + saveExtension;

        document.body.appendChild(input);
        input.addEventListener("change", () => {
            let reader = new FileReader();
            reader.onload = () => {
                let text = reader.result;
                // TODO: Check if this is actually working. Typescript 3 suggested it wouldn't.
                SaveHandler.loadGame(<SaveStructure> JSON.parse(<string> text));
            };
            reader.readAsText(input.files[0]);
        });
        input.click();
        document.body.removeChild(input);
    }

    function getItem (thing : SavedThing) : Thing {
        let item : Thing;
        if (thing.Unique) {
            item = Thing.getUnique(thing.Name);
        } else {
            let items = Thing.getNonUnique(thing.Name);
            if (items.length > 0) {
                item = items[0];
            }
            if (item == undefined) {
                let error = thing.Name + " no longer exists.";
                console.error("[SaveHandler] " + error);
                errors.push(error)
                return undefined;
            } else {
                item = item.clone(true);
            }
        }
        item.setChanges(thing.Changes);
        return item;
    }

    export function loadGame (saveStruc : SaveStructure) {
        // interface SaveStructure {
        //     Variables : {[id : string] : any};
        //     Wielded : Array<SavedThing>;
        //     Worn : Array<SavedThing>;
        //     Carried : Array<SavedThing>;
        //     Player : SavedPlayer;
        //     Rounds : number;
        // }
        let player = WorldState.player;
        StoredVariable.updateFromObject(saveStruc.Variables);

        WorldState.setCurrentTurn(saveStruc.Rounds);

        Thing.WearRelation.getRight(player).forEach((thing : Thing) => {
            Thing.WearRelation.unsetRight(thing);
        });

        Thing.WieldRelation.getRight(player).forEach((thing : Thing) => {
            Thing.WieldRelation.unsetRight(thing);
        });

        Thing.CarryRelation.getRight(player).forEach((thing : Thing) => {
            Thing.CarryRelation.unsetRight(thing);
        });

        saveStruc.Wielded.forEach((thing : SavedThing) => {
            let item = getItem(thing);
            if (item != undefined) WorldState.player.setWielded(item);
        });

        saveStruc.Worn.forEach((thing : SavedThing) => {
            let item = getItem(thing);
            if (item != undefined) WorldState.player.setWorn(item);
        });

        saveStruc.Carried.forEach((thing : SavedThing) => {
            let item = getItem(thing);
            if (item != undefined) WorldState.player.setCarried(item);
        });

        let savedPlayer = saveStruc.Player;
        player.setName(savedPlayer.Name);
        player.setChanges(savedPlayer.PlayerChanges); // this adds the right gendered parts
        savedPlayer.Parts.forEach((part : SavedThing) => {
            let bpList = <Array<Bodypart>> player.getPartsByName(part.Name);
            if (bpList != undefined) {
                bpList[0].setChanges(part.Changes);
            }
        });

        saveStruc.UniqueThings.forEach((savedThing : SavedThing) => {
            let thing = Thing.getUnique(savedThing.Name);
            if (thing != undefined) {
                thing.setChanges(savedThing.Changes);
            }
        });
    }

    function exportPlayer () : SavedPlayer {
        return {
            Name : WorldState.player.getName(),
            PlayerChanges : WorldState.player.getChanges(),
            Parts : exportThings(WorldState.player.getParts())
        }
    }

    export function exportThings (arr : Array<Thing>, changedOnly? : boolean) : Array<SavedThing> {
        let obj = [];
        for (let i = 0; i < arr.length; i++) {
            let thing = <Thing> arr[i];
            let savedThing = {
                Unique : thing.isUnique(),
                Name : thing.getName(),
                Changes : thing.getChanges()
            };
            if (!changedOnly || Object.keys(savedThing.Changes).length > 0) {
                obj.push(savedThing);
            }
        }
        return obj;
    }

    export function getSaveStructure () : SaveStructure {
        let variables = StoredVariable.getVariables();
        let savedVariables = {};
        for (let i = 0; i < variables.length; i++) {
            savedVariables[variables[i].id] = variables[i].exportAsObject();
        }

        let wielded = Thing.WieldRelation.getRight(WorldState.player);
        let worn = Thing.WearRelation.getRight(WorldState.player);
        let carried = Thing.CarryRelation.getRight(WorldState.player);

        let saveStruc : SaveStructure = {
            Variables : savedVariables,
            UniqueThings : exportThings(Thing.getUniques()),
            Wielded : exportThings(wielded),
            Worn : exportThings(worn),
            Carried : exportThings(carried),
            Player : exportPlayer(),
            Rounds : WorldState.getCurrentTurn(),
            Date : new Date().toLocaleString()
        };

        console.debug("[SaveHandler] Created Save Structure", saveStruc);

        return saveStruc;
    }

    export function setSlot (slotN : number) {
        saveSlot = slotN;
    }

    export function saveToStorage () {
        Controls.Memory.setValue(storagePrefix + saveSlot, getSaveStructure());
    }

    export async function loadFromStorage () {
        if (!erasing) {
            let saveStruct = (Controls.Memory.getValue(storagePrefix + saveSlot, undefined));
            if (saveStruct != undefined) {
                loadGame(saveStruct);
                return await AfterLoad.execute({noun : saveStruct});
            }
        }
        // this is a new game!
        await CharacterCreation.rulebook.execute({});
    }

    export async function loadFromFile () {
        PlayBegins.LOAD_FAILED = false;
        let promise = readFile();

        let finishedAny;
        let realPromise = new Promise((resolve) => {
            finishedAny = resolve;
        });

        let say = new Say("No save file was loaded.", Say.PARAGRAPH_BREAK, Say.CENTERED, new SayBold("Press any key to return."));
        let sayElements = await Elements.CurrentTurnHandler.getSayElementsAsContent(say);
        Elements.CurrentTurnHandler.print(...sayElements);

        let nextKey = Controls.KeyHandler.getNextKey();

        promise.then((file) => {
            Controls.KeyHandler.stopGivingNextKey(nextKey);
            Elements.CurrentTurnHandler.unprint(...sayElements);
            loadGame(getFromFile(file)); // If no file was chosen then change isn't triggered, so it's never undefined...
            finishedAny();
        });

        nextKey.then((keyCode : KeyCode) => {
            PlayBegins.LOAD_FAILED = true;
            finishedAny();
        });

        await realPromise;
    }

    export function getSayForSlot (slotNumber : number) {
        let saveStruct : SaveStructure = (Controls.Memory.getValue(storagePrefix + slotNumber, undefined));
        if (saveStruct == undefined) {
            return new Say("New Game");
        } else {
            let erasingText = erasing ? "(ERASE) - " : "";
            return new Say(erasingText, saveStruct.Player.Name + " - Turns: " + saveStruct.Rounds + " - Last Played: " + saveStruct.Date);
        }
    }

    export function getFromFile (saveText) : SaveStructure {
        return JSON.parse(decodeURIComponent(atob(saveText)));
    }

    export function saveToFile () {
        // It's okay if you want to cheat, but if you tamper with the save file, please be mindful when reporting "bugs".
        download(saveName + "." + saveExtension, btoa(unescape(encodeURIComponent((JSON.stringify(getSaveStructure()))))));
    }

    export function isErasing () {
        return erasing;
    }

    export function toggleErasing () {
        erasing = !erasing;
    }

    export function isVirgin () {
        let was = virgin.getValue();
        virgin.storeValue(false);
        return was;
    }
}

// document.getElementById("SaveGameButton").addEventListener("click", () => {
//     SaveHandler.saveToFile();
// });

TurnSequence.rulebook.createAndAddRule({
    name : "Save game to Storage",
    priority : Rule.PRIORITY_LOWEST,
    firstPriority : Rule.PRIORITY_LOWEST,
    code : () => {
        SaveHandler.saveToStorage();
    }
})