package com.brackeen.javagamebook.scripting;

import java.io.IOException;
import java.util.*;
import com.brackeen.javagamebook.game.*;
import bsh.*;

public class ScriptManager {

    /**
        GameObjectEventListener delegujący wywołania do metod skryptowych
        ScriptedListener jest dodawany do każdego obiektu GameObject
        który ma co najmniej jedną metodę skryptową.
    */
    public class ScriptedListener
        implements GameObjectEventListener
    {

        public void notifyVisible(GameObject object,
            boolean visible)
        {
            invoke(object.getName() +
                (visible?"Visible":"NotVisible"));
        }

        public void notifyObjectCollision(GameObject object,
            GameObject otherObject)
        {
            if (otherObject.getName() != null) {
                invoke(object.getName() + "_" +
                    otherObject.getName() + "Collision");
            }

        }

        public void notifyObjectTouch(GameObject object,
            GameObject otherObject)
        {
            if (otherObject.getName() != null) {
                invoke(object.getName() + "_" +
                    otherObject.getName() + "Touch");
            }

        }

        public void notifyObjectRelease(GameObject object,
            GameObject otherObject)
        {
            if (otherObject.getName() != null) {
                invoke(object.getName() + "_" +
                    otherObject.getName() + "Release");
            }

        }

        public void notifyFloorCollision(GameObject object) {
            invoke(object.getName() + "FloorCollision");
        }

        public void notifyCeilingCollision(GameObject object) {
            invoke(object.getName() +"CeilingCollision");
        }

        public void notifyWallCollision(GameObject object) {
            invoke(object.getName() + "WallCollision");
        }
    }

    private static final Class[] NO_ARGS = new Class[0];

    private Interpreter bsh;
    private GameObjectEventListener scriptedListener;

    public ScriptManager() {
         scriptedListener = new ScriptedListener();
    }


    /**
        Konfiguruje ScriptManager dla poziomu. Lista plików
        skryptów do uruchomienia oraz wszystkie obiekty w
        GameObjectManager mające nazwy są dodawane jako zmienne
        nazwane dla skryptów.
        Dodatkowo wywoływana jest metoda initLevel() ze skryptu
        o ile została zdefiniowana.
    */
    public void setupLevel(GameObjectManager gameObjectManager,
        GameTaskManager gameTaskManager, String[] scriptFiles)
    {
        bsh = new Interpreter();
        try {
            // wykonanie pliku źródłowego (i załadowanych metod).
            for (int i=0; i<scriptFiles.length; i++) {
                bsh.source(scriptFiles[i]);
            }

            bsh.set("gameTaskManager", gameTaskManager);

            // Wszystkie nazwane obiekty są traktowane 
            // jako zmienne w beanshell.
            // UWAGA: powoduje to utrzymywanie wszystkich obiektów
            // w pamięci aż do następnego poziomu nawet, jeżeli 
            // zostały w grze zniszczone.
            Iterator i = gameObjectManager.iterator();
            while (i.hasNext()) {
                GameObject object = (GameObject)i.next();
                if (object.getName() != null) {
                    bsh.set(object.getName(), object);

                    // dodanie nasłuchu skryptowego dla obiektu
                    if (hasScripts(object)) {
                        object.addListener(scriptedListener);
                    }
                }
            }

            // inicjowanie kodu poziomu - wywołanie initLevel()
            invoke("initLevel");
        }
        catch (IOException ex) {
            ex.printStackTrace();
        }
        catch (EvalError ex) {
            ex.printStackTrace();
        }
    }

    /**
        Sprawdza, czy określony obiekt gry posiada skrypt
        (czy metoda skryptu zaczyna się od nazwy obiektu).
    */
    public boolean hasScripts(GameObject object) {
        if (object.getName() != null) {
            String[] names = bsh.getNameSpace().getMethodNames();
            for (int i=0; i<names.length; i++) {
                if (names[i].startsWith(object.getName())) {
                    return true;
                }
            }
        }
        // nic nie znaleziono
        return false;
    }

    /**
        Zwraca true jeżeli podana nazwa metody jest istniejącą
        metodą skryptową.
    */
    public boolean isMethod(String methodName) {
        return (bsh.getNameSpace().
            getMethod(methodName, NO_ARGS) != null);
    }

    /**
        Wywołuje określona metodę skryptową.
    */
    public void invoke(String methodName) {
        if (isMethod(methodName)) {
            try {
                bsh.eval(methodName + "()");
            }
            catch (EvalError e) {
                e.printStackTrace();
            }
        }
    }

}
