/*
 * The MIT License (MIT)
 *
 * Copyright (c) Despector <https://despector.voxelgenesis.com>
 * Copyright (c) contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.thevoxelbox.script;

import com.thevoxelbox.fmoka.ast.SourceSet;
import com.thevoxelbox.fmoka.compiler.BytecodeCompiler;
import com.thevoxelbox.fmoka.compiler.data.CompiledSet;
import com.thevoxelbox.fmoka.parser.Parser;

import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.format.TextColor;
import org.spongepowered.api.text.format.TextColors;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Path;

public class Script {

    private final String id;
    private final Path path;
    private Status status = Status.UNLOADED;
    private long last_time;

    private SourceSet src;
    private CompiledSet compiled;

    public Script(String id, Path p) {
        this.id = id;
        this.path = p;
    }

    public String getId() {
        return this.id;
    }

    public Path getPath() {
        return this.path;
    }

    public boolean isLoaded() {
        return this.status == Status.LOADED || this.status == Status.UPDATED;
    }

    public Status getStatus() {
        return this.status;
    }

    public void setStatus(Status s) {
        this.status = s;
    }

    public long getLastTime() {
        return this.last_time;
    }

    public void setLastTime(long time) {
        this.last_time = time;
    }

    public void load(CommandSource sender) {
        if (this.status != Status.UNLOADED && this.status != Status.ERRORED) {
            return;
        }
        this.src = new SourceSet(ScriptManager.getLibrarySource());
        Parser parser = new Parser(this.src, this.path);
        try {
            if (!parser.parse()) {
                this.status = Status.ERRORED;
                sender.sendMessage(Text.of(TextColors.DARK_RED, "Script " + this.id + " errors while parsing"));
                return;
            }
        } catch (IOException e) {
            e.printStackTrace();
            this.status = Status.ERRORED;
            sender.sendMessage(Text.of(TextColors.DARK_RED, "Script " + this.id + " failed to load"));
            return;
        }

        this.compiled = new CompiledSet(ScriptManager.getLibraryCompiled());
        BytecodeCompiler compiler = new BytecodeCompiler();
        if (!compiler.compile(this.src, this.compiled)) {
            this.status = Status.ERRORED;
            sender.sendMessage(Text.of(TextColors.DARK_RED, "Script " + this.id + " errors while compiling"));
            return;
        }

        this.status = Status.LOADED;
        sender.sendMessage(Text.of(TextColors.GREEN, "Script " + this.id + " successfully loaded"));

        Method onLoad = this.compiled.getMethod(this.id, "onLoad", "()V");
        if (onLoad != null) {
            try {
                onLoad.invoke(null, (Object[]) null);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                VoxelScript.getLogger().error("Error calling " + this.id + "#onLoad()V");
                e.printStackTrace();
                this.status = Status.ERRORED;
            }
        }
    }

    public void unload(CommandSource sender) {
        if (this.status != Status.LOADED && this.status != Status.UPDATED) {
            return;
        }

        Method onUnload = this.compiled.getMethod(this.id, "onUnload", "()V");
        if (onUnload != null) {
            try {
                onUnload.invoke(null, (Object[]) null);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                VoxelScript.getLogger().error("Error calling " + this.id + "#onUnload()V");
                e.printStackTrace();
            }
        }
        this.src = null;
        this.compiled = null;
        this.status = Status.UNLOADED;
        sender.sendMessage(Text.of(TextColors.GREEN, "Script " + this.id + " unloaded"));
    }

    public void reload(CommandSource sender) {
        if (this.status != Status.LOADED && this.status != Status.UPDATED) {
            return;
        }
        this.compiled = null;
        this.src = new SourceSet(ScriptManager.getLibrarySource());
        Parser parser = new Parser(this.src, this.path);
        try {
            if (!parser.parse()) {
                this.status = Status.ERRORED;
                sender.sendMessage(Text.of(TextColors.DARK_RED, "Script " + this.id + " errors while parsing"));
                return;
            }
        } catch (IOException e) {
            e.printStackTrace();
            this.status = Status.ERRORED;
            sender.sendMessage(Text.of(TextColors.DARK_RED, "Script " + this.id + " failed to load"));
            return;
        }

        this.compiled = new CompiledSet(ScriptManager.getLibraryCompiled());
        BytecodeCompiler compiler = new BytecodeCompiler();
        if (!compiler.compile(this.src, this.compiled)) {
            this.status = Status.ERRORED;
            sender.sendMessage(Text.of(TextColors.DARK_RED, "Script " + this.id + " errors while compiling"));
            return;
        }
        Method onReload = this.compiled.getMethod(this.id, "onReload", "()V");
        if (onReload != null) {
            try {
                onReload.invoke(null, (Object[]) null);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                VoxelScript.getLogger().error("Error calling " + this.id + "#onReload()V");
                e.printStackTrace();
                unload(sender);
                return;
            }
        }

        this.status = Status.LOADED;
        sender.sendMessage(Text.of(TextColors.GREEN, "Script " + this.id + " successfully reloaded"));
    }

    public static enum Status {
        UNLOADED(TextColors.RED),
        ERRORED(TextColors.DARK_RED),
        LOADED(TextColors.GREEN),
        UPDATED(TextColors.DARK_AQUA);

        private TextColor color;

        Status(TextColor col) {
            this.color = col;
        }

        public TextColor getTextColor() {
            return this.color;
        }
    }
}