package org.ggp.base.player.gamer.python;

import org.ggp.base.player.gamer.Gamer;
import org.ggp.base.player.gamer.exception.AbortingException;
import org.ggp.base.player.gamer.exception.GamePreviewException;
import org.ggp.base.player.gamer.exception.MetaGamingException;
import org.ggp.base.player.gamer.exception.MoveSelectionException;
import org.ggp.base.player.gamer.exception.StoppingException;
import org.ggp.base.util.game.Game;
import org.ggp.base.util.gdl.grammar.GdlTerm;
import org.ggp.base.util.logging.GamerLogger;
import org.python.core.PyObject;
import org.python.util.PythonInterpreter;


/**
 * PythonGamer is a superclass that allows you to hook Python gamers into the
 * rest of the Java framework. In order to do this, do the following:
 *
 * 1) Create a subclass of PythonGamer that overrides getPythonGamerName() and
 *    getPythonGamerModule() to indicate where the Python source code file is.
 *    This is the Java stub that refers to the real Python gamer class.
 *
 * 2) Create the Python source code file, in the /src_py/ directory in the root
 *    directory for this project. Make sure that the stub points to this class,
 *    and that the Python class is a valid subclass of Gamer.
 *
 * For examples where this has already been done, see @PythonRandomGamerStub, which
 * is implemented in Python and hooks into the Java framework using the PythonGamer stub.
 *
 * @author Sam
 * @author evancox
 */
public abstract class PythonGamer extends Gamer
{
    Gamer thePythonGamer;

    protected abstract String getPythonGamerName();
    protected abstract String getPythonGamerModule();

    // Gamer stubs are lazily loaded because the Python interface takes
    // time to initialize, so we only want to load it when necessary, and
    // not for light-weight things like returning the player name.
    private void lazilyLoadGamerStub() {
        if (thePythonGamer == null) {
            try {
                // Load in the Python gamer, using a Jython intepreter.
                PythonInterpreter interpreter = new PythonInterpreter();
                interpreter.exec("from " + getPythonGamerModule() + " import " + getPythonGamerName());
                PyObject thePyClass = interpreter.get(getPythonGamerName());
                PyObject PyGamerObject = thePyClass.__call__();
                thePythonGamer = (Gamer)PyGamerObject.__tojava__(Gamer.class);
            } catch(Exception e) {
                GamerLogger.logError("GamePlayer", "Caught exception in Python initialization:");
                GamerLogger.logStackTrace("GamePlayer", e);
            }
        }
    }

    // The following methods are overriden as 'final' because they should not
    // be changed in subclasses of this class. Subclasses of this class should
    // only implement getPythonGamerName() and getPythonGamerModule(), and then
    // implement the real methods in the actual Python gamer. Essentially, any
    // subclass of this class is a Java-implementation stub for the actual real
    // Python implementation.

    @Override
    public final void preview(Game game, long timeout) throws GamePreviewException {
        lazilyLoadGamerStub();
        try {
            thePythonGamer.preview(game, timeout);
        } catch(GamePreviewException e) {
            GamerLogger.logError("GamePlayer", "Caught exception in Python stateMachinePreview:");
            GamerLogger.logStackTrace("GamePlayer", e);
        }
    }

    @Override
    public final void metaGame(long timeout) throws MetaGamingException {
        lazilyLoadGamerStub();
        thePythonGamer.setMatch(getMatch());
        thePythonGamer.setRoleName(getRoleName());
        try {
            thePythonGamer.metaGame(timeout);
        } catch(MetaGamingException e) {
            GamerLogger.logError("GamePlayer", "Caught exception in Python stateMachineMetaGame:");
            GamerLogger.logStackTrace("GamePlayer", e);
        }
    }

    @Override
    public final GdlTerm selectMove(long timeout) throws MoveSelectionException {
        lazilyLoadGamerStub();
        thePythonGamer.setMatch(getMatch());
        thePythonGamer.setRoleName(getRoleName());
        try {
            return thePythonGamer.selectMove(timeout);
        } catch(MoveSelectionException e) {
            GamerLogger.logError("GamePlayer", "Caught exception in Python stateMachineSelectMove:");
            GamerLogger.logStackTrace("GamePlayer", e);
            return null;
        }
    }

    @Override
    public final void stop() {
        lazilyLoadGamerStub();
        thePythonGamer.setMatch(getMatch());
        thePythonGamer.setRoleName(getRoleName());
        try {
            thePythonGamer.stop();
        } catch(StoppingException e) {
            GamerLogger.logError("GamePlayer", "Caught exception in Python stateMachineStop:");
            GamerLogger.logStackTrace("GamePlayer", e);
        }
    }

    @Override
    public final void abort() {
        lazilyLoadGamerStub();
        thePythonGamer.setMatch(getMatch());
        thePythonGamer.setRoleName(getRoleName());
        try {
            thePythonGamer.abort();
        } catch(AbortingException e) {
            GamerLogger.logError("GamePlayer", "Caught exception in Python stateMachineAbort:");
            GamerLogger.logStackTrace("GamePlayer", e);
        }
    }

    @Override
    public final String getName() {
        return getPythonGamerName();
    }
}