#include "McRFPy_API.h"
#include "platform.h"
#include "GameEngine.h"
#include "Grid.h"
#include "UI.h"

// static class members...?
std::map<std::string, UIMenu*> McRFPy_API::menus;
std::map<std::string, Grid*> McRFPy_API::grids;
std::map<std::string, PyObject*> McRFPy_API::callbacks;
std::list<Animation*> McRFPy_API::animations;
std::vector<sf::SoundBuffer> McRFPy_API::soundbuffers;
sf::Music McRFPy_API::music;
sf::Sound McRFPy_API::sfx;
std::string McRFPy_API::input_mode;
int McRFPy_API::turn_number;
std::string McRFPy_API::active_grid;
bool McRFPy_API::do_camfollow;

EntityManager McRFPy_API::entities;

static PyMethodDef mcrfpyMethods[] = {

    {"createMenu", McRFPy_API::_createMenu, METH_VARARGS,
        "Create a new uimenu (name_str, x, y, w, h)"},

    {"listMenus", McRFPy_API::_listMenus, METH_VARARGS,
        "return a list of existing menus"},

    {"modMenu", McRFPy_API::_modMenu, METH_VARARGS,
        "call with a UIMenu object to update all fields"},

    {"createCaption", McRFPy_API::_createCaption, METH_VARARGS,
        "Create a new text caption (menu_str, text_str, fontsize, r, g, b)"},

    {"createButton", McRFPy_API::_createButton, METH_VARARGS,
        "Create a new button (menu_str, x, y, w, h, (bg r, g, b), (text r, g, b), caption, action_code)"},

    {"createSprite", McRFPy_API::_createSprite, METH_VARARGS,
        "Create a new sprite (menu_str, texture_index, sprite_index, x, y, scale)"},

    {"createTexture", McRFPy_API::_createTexture, METH_VARARGS,
        "Create a new texture (filename_str, grid_size, width, height) - grid_size is in pixels (only square sprites for now), width and height are in tiles"},

    {"registerPyAction", McRFPy_API::_registerPyAction, METH_VARARGS,
        "Register a callable Python object to correspond to an action string. (actionstr, callable)"},
        
    {"registerInputAction", McRFPy_API::_registerInputAction, METH_VARARGS,
        "Register a SFML input code to correspond to an action string. (input_code, actionstr)"},

    {"createGrid", McRFPy_API::_createGrid, METH_VARARGS,
        "create a new grid (title, grid_x, grid_y, grid_size, x, y, w, h). grid_x and grid_y are the width and height in squares. grid_size is the pixel w/h of sprites on the grid. x,y are the grid's screen position. w,h are the grid's screen size" },

    {"listGrids", McRFPy_API::_listGrids, METH_VARARGS,
        "return grid objects and all points" },

    {"modGrid", McRFPy_API::_modGrid, METH_VARARGS,
        "call with a Grid object to update all fields"},

    {"createAnimation", McRFPy_API::_createAnimation, METH_VARARGS,
        "Create a new animation:\n"
        "createAnimation(duration:float, parent:string, target_type:string, target_id:string or int, field:string, callback:function, loop:bool, frames:list)\n"
        "duration: total animation time in seconds\n"
        "parent: the name of a UI menu or grid\n"
        "target_type: 'caption', 'button', 'sprite', or 'entity'\n"
        "target_id: integer index of the caption or button, or string ID of entity\n"
        "field: what to animate. 'position', 'size', 'bgcolor', 'textcolor' or 'sprite'\n"
        "callback: called when the animation completes\n"
        "loop: if True, animation repeats; if False, animation is deleted\n"
        "frames: if animating a sprite, list the frames. For other data types, the value will change in discrete steps at a rate of duration/len(frames).\n"},

/*
    static PyObject* _createSoundBuffer(PyObject*, PyObject*);
    static PyObject* _loadMusic(PyObject*, PyObject*);
    static PyObject* _setMusicVolume(PyObject*, PyObject*);
    static PyObject* _setSoundVolume(PyObject*, PyObject*);
    static PyObject* _playSound(PyObject*, PyObject*);
    static PyObject* _getMusicVolume(PyObject*, PyObject*);
    static PyObject* _getSoundVolume(PyObject*, PyObject*);
*/
	{"createSoundBuffer", McRFPy_API::_createSoundBuffer, METH_VARARGS, "(filename)"},
	{"loadMusic", McRFPy_API::_loadMusic, METH_VARARGS, "(filename)"},
	{"setMusicVolume", McRFPy_API::_setMusicVolume, METH_VARARGS, "(int)"},
	{"setSoundVolume", McRFPy_API::_setSoundVolume, METH_VARARGS, "(int)"},
	{"playSound", McRFPy_API::_playSound, METH_VARARGS, "(int)"},
	{"getMusicVolume", McRFPy_API::_getMusicVolume, METH_VARARGS, ""},
	{"getSoundVolume", McRFPy_API::_getSoundVolume, METH_VARARGS, ""},
	
	{"unlockPlayerInput", McRFPy_API::_unlockPlayerInput, METH_VARARGS, ""},
	{"lockPlayerInput", McRFPy_API::_lockPlayerInput, METH_VARARGS, ""},
	{"requestGridTarget", McRFPy_API::_requestGridTarget, METH_VARARGS, ""},
	{"activeGrid", McRFPy_API::_activeGrid, METH_VARARGS, ""},
	{"setActiveGrid", McRFPy_API::_setActiveGrid, METH_VARARGS, ""},
	{"inputMode", McRFPy_API::_inputMode, METH_VARARGS, ""},
	{"turnNumber", McRFPy_API::_turnNumber, METH_VARARGS, ""},
	{"createEntity", McRFPy_API::_createEntity, METH_VARARGS, ""},
	//{"listEntities", McRFPy_API::_listEntities, METH_VARARGS, ""},
	{"refreshFov", McRFPy_API::_refreshFov, METH_VARARGS, ""},
	
	{"camFollow", McRFPy_API::_camFollow, METH_VARARGS, ""},

    {NULL, NULL, 0, NULL}
};

static PyModuleDef mcrfpyModule = {
    PyModuleDef_HEAD_INIT, "mcrfpy", NULL, -1, mcrfpyMethods,
    NULL, NULL, NULL, NULL
};

// Module initializer fn, passed to PyImport_AppendInittab
PyObject* PyInit_mcrfpy()
{
    PyObject* m = PyModule_Create(&mcrfpyModule);
    
    if (m == NULL) 
    {
        return NULL;
    }
    
    // This code runs, but Python segfaults when accessing the UIFrame type.
    std::cout << "Adding UIFrame object to module\n";
    PyModule_AddType(m, &mcrfpydef::PyColorType);
    if (PyModule_AddType(m, &mcrfpydef::PyUIFrameType) < 0)
    {
        std::cout << "Error adding UIFrame type to module; aborting" << std::endl;
        Py_DECREF(&mcrfpydef::PyUIFrameType);
        return NULL;
    }

    return m;
}

// init_python - configure interpreter details here
PyStatus init_python(const char *program_name)
{
    PyStatus status;

	//**preconfig to establish locale**
    PyPreConfig preconfig;
    PyPreConfig_InitIsolatedConfig(&preconfig);
    preconfig.utf8_mode = 1;
    
    status = Py_PreInitialize(&preconfig);
    if (PyStatus_Exception(status)) {
    	Py_ExitStatusException(status);
    }

    PyConfig config;
    PyConfig_InitIsolatedConfig(&config);
	config.dev_mode = 0;

	PyConfig_SetBytesString(&config, &config.home, 
        narrow_string(executable_path() + L"/Python311").c_str());

    status = PyConfig_SetBytesString(&config, &config.program_name,
                                     program_name);

    // under Windows, the search paths are correct; under Linux, they need manual insertion
#if __PLATFORM_SET_PYTHON_SEARCH_PATHS == 1
    config.module_search_paths_set = 1;
	
	// search paths for python libs/modules/scripts
    const wchar_t* str_arr[] = {
        L"/scripts",
        L"/Python311/lib.linux-x86_64-3.11",
	    L"/Python311",
        L"/Python311/Lib",
        L"/venv/lib/python3.11/site-packages"
    };
    

    for(auto s : str_arr) {
        status = PyWideStringList_Append(&config.module_search_paths, (executable_path() + s).c_str());
        if (PyStatus_Exception(status)) {
            continue;
        }
    }
#endif

    status = Py_InitializeFromConfig(&config);
    return status;
}

void McRFPy_API::setSpriteTexture(int ti)
{
    int tx = ti % texture_width, ty = ti / texture_width;
    sprite.setTextureRect(sf::IntRect(
                tx * texture_size,
                ty * texture_size,
                texture_size, texture_size));
}

// functionality
//void McRFPy_API::

void McRFPy_API::api_init() {

    // build API exposure before python initialization
    PyImport_AppendInittab("mcrfpy", &PyInit_mcrfpy);
    // use full path version of argv[0] from OS to init python
    init_python(narrow_string(executable_filename()).c_str());

/*
    // Create Python translations of types
    PyTypeObject * gridpoint_pytype = new PyTypeObject;
    gridpoint_pytype->tp_name = "GridPoint";
    gridpoint_pytype->tp_basicsize = sizeof(GridPoint);
    gridpoint_pytype->tp_dealloc = [](PyObject* obj) {
        delete ((GridPoint*) obj);
    };
    gridpoint_pytype->tp_flags = Py_TPFLAGS_DEFAULT;
    gridpoint_pytype->tp_doc = "GridPoint";
    gridpoint_pytype->tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) {
        return (PyObject*) new GridPoint();
    };
    PyType_Ready(gridpoint_pytype);
    PyModule_AddObject(
            PyImport_AddModule("__main__"), "GridPoint", (PyObject*) gridpoint_pytype);
*/



    texture.loadFromFile("./assets/kenney_tinydungeon.png");
    //texture_size = 16, texture_width = 12, texture_height= 11;
    //texture_sprite_count = texture_width * texture_height;
    texture.setSmooth(false);

    sprite.setTexture(texture);
    sprite.setScale(sf::Vector2f(4.0f, 4.0f));
    setSpriteTexture(0);
}

void McRFPy_API::executeScript(std::string filename)
{
    FILE* PScriptFile = fopen(filename.c_str(), "r");
    if(PScriptFile) {
        PyRun_SimpleFile(PScriptFile, filename.c_str());
        fclose(PScriptFile);
    }
}

void McRFPy_API::api_shutdown()
{
    Py_Finalize();
}


void McRFPy_API::executePyString(std::string pycode)
{
    PyRun_SimpleString(pycode.c_str());
}

void McRFPy_API::REPL()
{
    PyRun_InteractiveLoop(stdin, "<stdin>");
}

void McRFPy_API::REPL_device(FILE * fp, const char *filename)
{
    PyRun_InteractiveLoop(fp, filename);
}

PyObject* McRFPy_API::_createMenu(PyObject *self, PyObject *args) {
    const char* title_cstr;
    int posx, posy, sizex, sizey;
    if (!PyArg_ParseTuple(args, "siiii", &title_cstr, &posx, &posy, &sizex, &sizey)) return NULL;
    std::string title = title_cstr;
    //TODO (Bug 2) check for and free existing key before overwriting ptr
    menus[title] = createMenu(posx, posy, sizex, sizey);
    Py_INCREF(Py_None);
    return Py_None;
}

PyObject* McRFPy_API::_listMenus(PyObject*, PyObject*) {
    // todo - get the (Py) classes UIMenu, Button, Caption, Sprite
    // and call BuildValue (tuples) -> their constructors
    PyObject* uimodule = PyImport_AddModule("UIMenu"); //already imported
    PyObject* uimenu_type = PyObject_GetAttrString(uimodule, "UIMenu");
    PyObject* btn_type = PyObject_GetAttrString(uimodule, "Button");
    PyObject* cap_type = PyObject_GetAttrString(uimodule, "Caption");
    PyObject* spr_type = PyObject_GetAttrString(uimodule, "Sprite");

    PyObject* menulist = PyList_New(menus.size());
    std::map<std::string, UIMenu*>::iterator it = menus.begin();
    //for (int i = 0; i < menus.size(); i++) {
    int i = 0;
    for (auto it = menus.begin(); it != menus.end(); it++) {
        std::string title = it->first;
        auto menu = it->second;
        auto p = menu->box.getPosition();
        auto s = menu->box.getSize();
        auto g = menu->box.getFillColor();
        PyObject* menu_args = Py_BuildValue("(siiii(iii)O)",
            title.c_str(),
            (int)p.x, (int)p.y, (int)s.x, (int)s.y,
            (int)g.r, (int)g.g, (int)g.b, 
            menu->visible ? Py_True: Py_False);
        menu->visible ? Py_INCREF(Py_True) : Py_INCREF(Py_False);
        PyObject* menuobj = PyObject_CallObject((PyObject*) uimenu_type, menu_args);

        // Loop: Convert Button objects to Python Objects
        PyObject* button_list = PyObject_GetAttrString(menuobj, "buttons");
        for(auto& b : menu->buttons) {
            auto bp = b.rect.getPosition();
            auto bs = b.rect.getSize();
            auto bg = b.rect.getFillColor();
            auto bf = b.caption.getFillColor();
            PyObject* btn_args = Py_BuildValue("(iiii(iii)(iii)ss)",
                (int)bp.x, (int)bp.y, (int)bs.x, (int)bs.y,
                (int)bg.r, (int)bg.g, (int)bg.b,
                (int)bf.r, (int)bf.g, (int)bf.b,
                b.caption.getString().toAnsiString().c_str(),
                b.action.c_str());
            PyObject* buttonobj = PyObject_CallObject((PyObject*) btn_type, btn_args);
            PyList_Append(button_list, buttonobj);
        }

        // Loop: Convert Caption objects to Python Objects
        PyObject* caption_list = PyObject_GetAttrString(menuobj, "captions");
        for (auto& c : menu->captions) {
            auto cc = c.getFillColor();
            PyObject* cap_args = Py_BuildValue("si(iii)",
               c.getString().toAnsiString().c_str(),
               c.getCharacterSize(),
               cc.r, cc.g, cc.b);
            PyObject* capobj = PyObject_CallObject((PyObject*) cap_type, cap_args);
            PyList_Append(caption_list, capobj);
        }

        // Loop: Convert Sprite objects to Python Objects
        PyObject* sprite_list = PyObject_GetAttrString(menuobj, "sprites");
        for (auto& s : menu->sprites) {
            PyObject* spr_args = Py_BuildValue("(iiff)",
                s.texture_index, s.sprite_index, s.x, s.y);

            PyObject* sprobj = PyObject_CallObject((PyObject*) spr_type, spr_args);
            PyList_Append(sprite_list, sprobj);
        }

        PyList_SET_ITEM(menulist, i, menuobj);
        i++; // count iterator steps
    }
    return menulist;
}

PyObject* McRFPy_API::_modMenu(PyObject* self, PyObject* args) {
    PyObject* o;
    if (!PyArg_ParseTuple(args, "O", &o)) return NULL;
    std::string title = PyUnicode_AsUTF8(PyObject_GetAttrString(o, "title"));
    int x = PyLong_AsLong(PyObject_GetAttrString(o, "x"));
    int y = PyLong_AsLong(PyObject_GetAttrString(o, "y"));
    int w = PyLong_AsLong(PyObject_GetAttrString(o, "w"));
    int h = PyLong_AsLong(PyObject_GetAttrString(o, "h"));
    PyObject* bgtuple = PyObject_GetAttrString(o, "bgcolor");
    auto bgcolor = sf::Color(
        PyLong_AsLong(PyTuple_GetItem(bgtuple, 0)),
        PyLong_AsLong(PyTuple_GetItem(bgtuple, 1)),
        PyLong_AsLong(PyTuple_GetItem(bgtuple, 2))
        );
    bool visible = PyObject_IsTrue(PyObject_GetAttrString(o, "visible"));

    auto menu = menus[title];
    if (menu == NULL) return NULL;
    menu->box.setPosition(sf::Vector2f(x, y));
    menu->box.setSize(sf::Vector2f(w, h));
    menu->box.setFillColor(bgcolor);
    menu->visible = visible;

    // jank, or dank? iterate over .captions, .buttons, .sprites to modify them
    // captions
    PyObject* captionlist = PyObject_GetAttrString(o, "captions");
    //std::cout << PyUnicode_AsUTF8(PyObject_Repr(captionlist)) << std::endl;
    for (int i = 0; i < menu->captions.size(); i++) {
        PyObject* captionobj = PyList_GetItem(captionlist, i);
        menu->captions[i].setString(
            PyUnicode_AsUTF8(PyObject_GetAttrString(captionobj, "text")));
        //menu->captions[i].setCharacterSize(
        //    PyLong_AsLong(PyObject_GetAttrString(captionobj, "textsize")));
        PyObject* fgtuple = PyObject_GetAttrString(captionobj, "color");
        menu->captions[i].setFillColor(
            sf::Color(
                PyLong_AsLong(PyTuple_GetItem(fgtuple, 0)),
                PyLong_AsLong(PyTuple_GetItem(fgtuple, 1)),
                PyLong_AsLong(PyTuple_GetItem(fgtuple, 2))
                ));
    }

    // buttons
    PyObject* buttonlist = PyObject_GetAttrString(o, "buttons");
    //std::cout << PyUnicode_AsUTF8(PyObject_Repr(buttonlist)) << std::endl;
    for (int i = 0; i < menu->buttons.size(); i++) {
        PyObject* buttonobj = PyList_GetItem(buttonlist, i);
        menu->buttons[i].setPosition(sf::Vector2f(
            PyLong_AsLong(PyObject_GetAttrString(buttonobj, "x")),
            PyLong_AsLong(PyObject_GetAttrString(buttonobj, "y"))
            ));
        auto sizevec = sf::Vector2f(
            PyLong_AsLong(PyObject_GetAttrString(buttonobj, "w")),
            PyLong_AsLong(PyObject_GetAttrString(buttonobj, "h"))
            );
        menu->buttons[i].setSize(sizevec);
        PyObject* btncolor = PyObject_GetAttrString(buttonobj, "bgcolor");
        //std::cout << PyUnicode_AsUTF8(PyObject_Repr(btncolor)) << std::endl;
        menu->buttons[i].setBackground(
            sf::Color(
                PyLong_AsLong(PyTuple_GetItem(btncolor, 0)),
                PyLong_AsLong(PyTuple_GetItem(btncolor, 1)),
                PyLong_AsLong(PyTuple_GetItem(btncolor, 2))
                ));
        PyObject* btxtcolor = PyObject_GetAttrString(buttonobj, "textcolor");
        //std::cout << PyUnicode_AsUTF8(PyObject_Repr(btxtcolor)) << std::endl;
        menu->buttons[i].setTextColor(
            sf::Color(
                PyLong_AsLong(PyTuple_GetItem(btxtcolor, 0)),
                PyLong_AsLong(PyTuple_GetItem(btxtcolor, 1)),
                PyLong_AsLong(PyTuple_GetItem(btxtcolor, 2))
                ));
        //std::cout << PyObject_Repr(PyObject_GetAttrString(buttonobj, "text")) << std::endl;
        menu->buttons[i].caption.setString(
        PyUnicode_AsUTF8(PyObject_GetAttrString(buttonobj, "text")));
        //std::cout << PyObject_Repr(PyObject_GetAttrString(buttonobj, "actioncode")) << std::endl;
        menu->buttons[i].action = 
        PyUnicode_AsUTF8(PyObject_GetAttrString(buttonobj, "actioncode"));
    }

    // sprites
    PyObject* spriteslist = PyObject_GetAttrString(o, "sprites");
    //std::cout << PyUnicode_AsUTF8(PyObject_Repr(spriteslist)) << std::endl;
    for (int i = 0; i < menu->sprites.size(); i++) {
        PyObject* spriteobj = PyList_GetItem(spriteslist, i);
        menu->sprites[i].texture_index = 
            PyLong_AsLong(PyObject_GetAttrString(spriteobj, "tex_index"));
        menu->sprites[i].sprite_index = 
            PyLong_AsLong(PyObject_GetAttrString(spriteobj, "sprite_index"));
        menu->sprites[i].x = 
            PyFloat_AsDouble(PyObject_GetAttrString(spriteobj, "x"));
        menu->sprites[i].y = 
            PyFloat_AsDouble(PyObject_GetAttrString(spriteobj, "y"));
    }

    Py_INCREF(Py_None);
    return Py_None;
}

PyObject* McRFPy_API::_createCaption(PyObject* self, PyObject* args) {
    const char* menukey_cstr, *text_cstr;
    int fontsize, cr, cg, cb;
    if (!PyArg_ParseTuple(args, "ssi(iii)", 
        &menukey_cstr, &text_cstr,
        &fontsize, &cr, &cg, &cb)) return NULL;
    createCaption(std::string(menukey_cstr), std::string(text_cstr), fontsize, sf::Color(cr, cg, cb));
    Py_INCREF(Py_None);
    return Py_None;
}

PyObject* McRFPy_API::_createButton(PyObject* self, PyObject* args) {
    const char *menukey_cstr, *caption_cstr, *action_cstr;
    int x, y, w, h, bgr, bgg, bgb, fgr, fgg, fgb;
    if (!PyArg_ParseTuple(args, "siiii(iii)(iii)ss",
        &menukey_cstr, &x, &y, &w, &h, 
        &bgr, &bgg, &bgb, &fgr, &fgg, &fgb,
        &caption_cstr, &action_cstr
        )) return NULL;
    createButton(std::string(menukey_cstr), x, y, w, h, sf::Color(bgr, bgg, bgb), sf::Color(fgr, fgg, fgb), std::string(caption_cstr), std::string(action_cstr));
    Py_INCREF(Py_None);
    return Py_None;
}

PyObject* McRFPy_API::_createTexture(PyObject* self, PyObject* args) {
    const char *fn_cstr;
    int gs, gw, gh;
    if (!PyArg_ParseTuple(args, "siii", &fn_cstr, &gs, &gw, &gh)) return NULL;
    createTexture(std::string(fn_cstr), gs, gw, gh);
    Py_INCREF(Py_None);
    return Py_None;
}

PyObject* McRFPy_API::_listTextures(PyObject*, PyObject*) {
    Py_INCREF(Py_None);
    return Py_None;
}

PyObject* McRFPy_API::_createSprite(PyObject* self, PyObject* args) {
    const char * menu_cstr;
    int ti, si;
    float x, y;
    float s;
    if (!PyArg_ParseTuple(args, "siifff", &menu_cstr, &ti, &si, &x, &y, &s)) return NULL;
    //std::cout << "Creating uisprite " << ti << " " << si << " " << x << " " << y << " " << s << " " << std::endl;
    createSprite(std::string(menu_cstr), ti, si, x, y, s);
    Py_INCREF(Py_None);
    return Py_None;
}

UIMenu *McRFPy_API::createMenu(int posx, int posy, int sizex, int sizey) {
    auto m = new UIMenu(game->getFont());
    m->box.setPosition(sf::Vector2f(posx, posy));
    m->box.setSize(sf::Vector2f(sizex, sizey));
    return m;
}

void McRFPy_API::createCaption(std::string menukey, std::string text, int fontsize, sf::Color textcolor) {
    auto menu = menus[menukey];
    menu->add_caption(text.c_str(), fontsize, textcolor);
}

void McRFPy_API::createButton(std::string menukey, int x, int y, int w, int h, sf::Color bgcolor, sf::Color textcolor, std::string caption, std::string action) {
    auto menu = menus[menukey];
    auto b = Button(x, y, w, h, bgcolor, textcolor, caption.c_str(), game->getFont(), action.c_str());
    menu->add_button(b);
}

void McRFPy_API::createSprite(std::string menukey, int ti, int si, float x, float y, float scale) {
    auto menu = menus[menukey];
    auto s = IndexSprite(ti, si, x, y, scale);
    menu->add_sprite(s);
    //std::cout << "indexsprite just created has values x,y " << s.x << ", " << s.y << std::endl;
}

int McRFPy_API::createTexture(std::string filename, int grid_size, int grid_width, int grid_height) {
    sf::Texture t;
    t.loadFromFile(filename.c_str());
    t.setSmooth(false);
    auto indextex = IndexTexture(t, grid_size, grid_width, grid_height);
    game->textures.push_back(indextex);

    return game->textures.size() - 1;
}

// python connection
PyObject* McRFPy_API::_registerPyAction(PyObject *self, PyObject *args)
{
    PyObject* callable;
    const char * actionstr;
    if (!PyArg_ParseTuple(args, "sO", &actionstr, &callable)) return NULL;
    //TODO: if the string already exists in the callbacks map, 
    //  decrease our reference count so it can potentially be garbage collected
    callbacks[std::string(actionstr)] = callable;
    Py_INCREF(callable);

    // return None correctly
    Py_INCREF(Py_None);
    return Py_None;
}

PyObject* McRFPy_API::_registerInputAction(PyObject *self, PyObject *args)
{
    int action_code;
    const char * actionstr;
    if (!PyArg_ParseTuple(args, "iz", &action_code, &actionstr)) return NULL;
    
    bool success;
    
    if (actionstr == NULL) { // Action provided is None, i.e. unregister
        std::cout << "Unregistering\n";
        success = game->currentScene()->unregisterActionInjected(action_code, std::string(actionstr) + "_py");
    } else {
        std::cout << "Registering" << actionstr << "_py to " << action_code << "\n";
        success = game->currentScene()->registerActionInjected(action_code, std::string(actionstr) + "_py");
    }
    
    success ? Py_INCREF(Py_True) : Py_INCREF(Py_False);
    return success ? Py_True : Py_False;
    
}

void McRFPy_API::doAction(std::string actionstr) {
    // hard coded actions that require no registration
    //std::cout << "Calling Python Action: " << actionstr;
    if (!actionstr.compare("startrepl")) return McRFPy_API::REPL();
    if (callbacks.find(actionstr) == callbacks.end()) 
    {
        //std::cout << " (not found)" << std::endl;
        return;
    }
    //std::cout << " (" << PyUnicode_AsUTF8(PyObject_Repr(callbacks[actionstr])) << ")" << std::endl;
    PyObject_Call(callbacks[actionstr], PyTuple_New(0), NULL);
}

PyObject* McRFPy_API::_createGrid(PyObject *self, PyObject *args) {
    const char* title_cstr;
    int gx, gy, gs, x, y, w, h;
    if (!PyArg_ParseTuple(args, "siiiiiii", &title_cstr, &gx, &gy, &gs, &x, &y, &w, &h)) return NULL;
    std::string title = title_cstr;
    //TODO - (Bug 2) check for key existing, and free if overwriting
    grids[title] = new Grid(gx, gy, gs, x, y, w, h);
    Py_INCREF(Py_None);
    return Py_None;
}

PyObject* McRFPy_API::_listGrids(PyObject*, PyObject*) {
    PyObject* gridmodule = PyImport_AddModule("Grid"); //already imported
    PyObject* grid_type = PyObject_GetAttrString(gridmodule, "Grid");
    PyObject* gridpoint_type = PyObject_GetAttrString(gridmodule, "GridPoint");
    PyObject* entity_type = PyObject_GetAttrString(gridmodule, "Entity");

    //std::cout << PyUnicode_AsUTF8(PyObject_Repr(gridmodule)) << std::endl;
    //std::cout << PyUnicode_AsUTF8(PyObject_Repr(grid_type)) << std::endl;
    //std::cout << PyUnicode_AsUTF8(PyObject_Repr(gridpoint_type)) << std::endl;
    //std::cout << PyUnicode_AsUTF8(PyObject_Repr(entity_type)) << std::endl;
        
    PyObject* gridlist = PyList_New(grids.size());
    std::map<std::string, Grid*>::iterator it = grids.begin();
    int i = 0;
    for (auto it = grids.begin(); it != grids.end(); it++) {
        std::string title = it->first;
        auto grid = it->second;
        auto p = grid->box.getPosition();
        auto s = grid->box.getSize();
        PyObject* grid_args = Py_BuildValue("(siiiiiiiO)",
            title.c_str(),
            (int)grid->grid_x, (int)grid->grid_y, (int)grid->grid_size,
			(int)p.x, (int)p.y, (int)s.x, (int)s.y, 
			grid->visible? Py_True: Py_False);

		grid->visible ? Py_INCREF(Py_True) : Py_INCREF(Py_False);
        //std::cout << PyUnicode_AsUTF8(PyObject_Repr(grid_args)) << std::endl;

        PyObject* gridobj = PyObject_CallObject((PyObject*) grid_type, grid_args);
        //std::cout << (long)gridobj << std::flush <<std::endl;

        //std::cout << PyUnicode_AsUTF8(PyObject_Repr(gridobj)) << std::endl;
        // Loop: Convert GridPoint objects to Python Objects
        PyObject* gridp_list = PyObject_GetAttrString(gridobj, "points");
        for(auto& p : grid->points) {
            PyObject* gridp_args = Py_BuildValue("((iii)OiOOO(iii)ii)",
                (int)p.color.r, (int)p.color.g, (int)p.color.b,
				p.walkable ? Py_True: Py_False,
        		p.tilesprite,
				p.transparent ? Py_True: Py_False,
				p.visible ? Py_True: Py_False,
				p.discovered ? Py_True: Py_False,
                (int)p.color_overlay.r, (int)p.color_overlay.g, (int)p.color_overlay.b,
                p.tile_overlay,
				p.uisprite);
			p.walkable ? Py_INCREF(Py_True) : Py_INCREF(Py_False);
			p.transparent ? Py_INCREF(Py_True) : Py_INCREF(Py_False);
            p.visible ? Py_INCREF(Py_True) : Py_INCREF(Py_False);
			p.discovered ? Py_INCREF(Py_True) : Py_INCREF(Py_False);
            PyObject* gridpobj = PyObject_CallObject((PyObject*) gridpoint_type, gridp_args);
            PyList_Append(gridp_list, gridpobj);
		}
		
		PyObject* ent_list = PyObject_GetAttrString(gridobj, "entities");
		for (auto e : grid->entities) {
			//def __init__(self, parent, tex_index, sprite_index, x, y, visible=True):
			PyObject* ent_args = Py_BuildValue("siiiiO",
				title.c_str(),
				e->cGrid->indexsprite.texture_index,
				e->cGrid->indexsprite.sprite_index,
				e->cGrid->x,
				e->cGrid->y,
				e->cGrid->visible ? Py_True: Py_False);
			//std::cout << PyUnicode_AsUTF8(PyObject_Repr(ent_args)) << std::endl;
			e->cGrid->visible ? Py_INCREF(Py_True) : Py_INCREF(Py_False);
			PyObject* entobj = PyObject_CallObject((PyObject*) entity_type, ent_args);
			PyList_Append(ent_list, entobj);
			//std::cout << PyUnicode_AsUTF8(PyObject_Repr(ent_list)) << std::endl;
		}
		
		PyList_SET_ITEM(gridlist, i, gridobj);
        i++; // count iterator steps
    }
    return gridlist;
}

PyObject* McRFPy_API::_modGrid(PyObject* self, PyObject* args) {
    PyObject* o;
    PyObject* bool_is_entityonly = Py_False;
    if (!PyArg_ParseTuple(args, "O|O", &o, &bool_is_entityonly)) return NULL;
    //std::cout << "EntOnly Flag: " << PyUnicode_AsUTF8(PyObject_Repr(bool_is_entityonly)) << std::endl;
    std::string title = PyUnicode_AsUTF8(PyObject_GetAttrString(o, "title"));
    int grid_x = PyLong_AsLong(PyObject_GetAttrString(o, "grid_x"));
    int grid_y = PyLong_AsLong(PyObject_GetAttrString(o, "grid_y"));
    int grid_size = PyLong_AsLong(PyObject_GetAttrString(o, "grid_size"));
    int x = PyLong_AsLong(PyObject_GetAttrString(o, "x"));
    int y = PyLong_AsLong(PyObject_GetAttrString(o, "y"));
    int w = PyLong_AsLong(PyObject_GetAttrString(o, "w"));
    int h = PyLong_AsLong(PyObject_GetAttrString(o, "h"));
    bool visible = PyObject_IsTrue(PyObject_GetAttrString(o, "visible"));

    auto grid = grids[title];
    if (grid == NULL) return NULL;
    grid->box.setPosition(sf::Vector2f(x, y));
    grid->box.setSize(sf::Vector2f(w, h));
    grid->visible = visible;

    //iterate over gridpoints
    if (!PyObject_IsTrue(bool_is_entityonly)) {
		PyObject* gpointlist = PyObject_GetAttrString(o, "points");
		//std::cout << PyUnicode_AsUTF8(PyObject_Repr(gpointlist)) << std::endl;
		for (int i = 0; i < grid->points.size(); i++) {
			PyObject* gpointobj = PyList_GetItem(gpointlist, i);
			PyObject* colortuple = PyObject_GetAttrString(gpointobj, "color");
			grid->points[i].color =
				sf::Color(
					PyLong_AsLong(PyTuple_GetItem(colortuple, 0)),
					PyLong_AsLong(PyTuple_GetItem(colortuple, 1)),
					PyLong_AsLong(PyTuple_GetItem(colortuple, 2))
					);
			grid->points[i].walkable = PyObject_IsTrue(PyObject_GetAttrString(gpointobj, "walkable"));
			grid->points[i].tilesprite = PyLong_AsLong(PyObject_GetAttrString(gpointobj, "tilesprite"));
			grid->points[i].transparent = PyObject_IsTrue(PyObject_GetAttrString(gpointobj, "transparent"));
			grid->points[i].visible = PyObject_IsTrue(PyObject_GetAttrString(gpointobj, "visible"));
			grid->points[i].discovered = PyObject_IsTrue(PyObject_GetAttrString(gpointobj, "discovered"));
			PyObject* overlaycolortuple = PyObject_GetAttrString(gpointobj, "color_overlay");
			grid->points[i].color_overlay =
				sf::Color(
					PyLong_AsLong(PyTuple_GetItem(overlaycolortuple, 0)),
					PyLong_AsLong(PyTuple_GetItem(overlaycolortuple, 1)),
					PyLong_AsLong(PyTuple_GetItem(overlaycolortuple, 2))
					);
			grid->points[i].tile_overlay = PyLong_AsLong(PyObject_GetAttrString(gpointobj, "tile_overlay"));
			grid->points[i].uisprite = PyLong_AsLong(PyObject_GetAttrString(gpointobj, "uisprite"));
		}
		
		// update grid pathfinding & visibility
		grid->refreshTCODmap();
		for (auto e : McRFPy_API::entities.getEntities("player")) {
			grid->refreshTCODsight(e->cGrid->x, e->cGrid->y);
		}
	}
    PyObject* entlist = PyObject_GetAttrString(o, "entities");
    //std::cout << PyUnicode_AsUTF8(PyObject_Repr(entlist)) << std::endl;
    for (int i = 0; i < grid->entities.size(); i++) {
		PyObject* entobj = PyList_GetItem(entlist, i);
		//std::cout << PyUnicode_AsUTF8(PyObject_Repr(entobj)) << std::endl;
		grid->entities[i]->cGrid->x = PyLong_AsLong(PyObject_GetAttrString(entobj, "x"));
		grid->entities[i]->cGrid->y = PyLong_AsLong(PyObject_GetAttrString(entobj, "y"));
		grid->entities[i]->cGrid->indexsprite.texture_index = PyLong_AsLong(PyObject_GetAttrString(entobj, "tex_index"));
		grid->entities[i]->cGrid->indexsprite.sprite_index = PyLong_AsLong(PyObject_GetAttrString(entobj, "sprite_index"));
	}

    Py_INCREF(Py_None);
    return Py_None;
}

PyObject* McRFPy_API::_refreshFov(PyObject* self, PyObject* args) {
	for (auto e : McRFPy_API::entities.getEntities("player")) {
		e->cGrid->grid->refreshTCODsight(e->cGrid->x, e->cGrid->y);
	}
	Py_INCREF(Py_None);
    return Py_None;
}

PyObject* _test_createAnimation(PyObject *self, PyObject *args) {
    //LerpAnimation<T>::LerpAnimation(float _d, T _ev, T* _t, std::function<void()> _cb, std::function<void(T)> _w, bool _l)
    std::string menu_key = "demobox1";
    McRFPy_API::animations.push_back(
        new LerpAnimation<sf::Vector2f>(
         3.0, 
         sf::Vector2f(100, 100), 
         McRFPy_API::menus[menu_key]->box.getPosition(),
         [](){McRFPy_API::executePyString("print('animation callback')");},
         [=](sf::Vector2f v) {
			 //std::cout << "write lambda!" << std::endl; 
			 McRFPy_API::menus[menu_key]->box.setPosition(v); 
			 //std::cout << "Position set to" << McRFPy_API::menus[menu_key]->box.getPosition().x 
			 //<< ", " << McRFPy_API::menus[menu_key]->box.getPosition().y << std::endl;
			 }, 
         false)
    );

    Py_INCREF(Py_None);
    return Py_None;
}

#define CEQ(A, B) (std::string(A).compare(B) == 0)

PyObject* McRFPy_API::_createAnimation(PyObject *self, PyObject *args) {
	//std::cout << "Creating animation called..." << std::endl;
    float duration;
	const char* parent;
	const char* target_type;
	PyObject* target_id_obj;
	const char* field;
	PyObject* callback;
	PyObject* loop_obj;
	PyObject* values_obj;
	PyObject* evdata; // for decoding values_obj
	//std::cout << PyUnicode_AsUTF8(PyObject_Repr(args)) << std::endl;
    if (!PyArg_ParseTuple(args, "fssOsOOO", &duration, &parent, &target_type, &target_id_obj, &field, &callback, &loop_obj, &values_obj)) { return NULL; }
    bool loop = PyObject_IsTrue(loop_obj);
    int target_id = PyLong_AsLong(target_id_obj);
    Py_INCREF(callback);
    /*
    std::cout << "Animation fields received:" <<
		"\nduration: " << duration <<
		"\nparent: " << parent <<
		"\ntarget type: " << target_type <<
		"\ntarget id: " << PyUnicode_AsUTF8(PyObject_Repr(target_id_obj)) <<
		"\nfield: " << field <<
		"\ncallback: " << PyUnicode_AsUTF8(PyObject_Repr(callback)) <<
		"\nloop: " << loop <<
		"\nvalues: " << PyUnicode_AsUTF8(PyObject_Repr(values_obj)) << std::endl;
	*/
	/* Jank alert:
	 * The following block is meant to raise an exception when index is missing from object animations that require one,
	 * but accept the target_id_obj error (and accept None as an index) for menus/grids.
	 * Instead, I get a "latent" exception, not properly raised, when the index is None.
	 * That not-really-raised exception causes other scripts to silently fail to execute
	 * until I go into the REPL and run any code, and get a bizarre, botched traceback.
	 * So, Grid/Menu can just take an index of 0 in the scripts until this is dejankified
	 */
	if (!CEQ(target_type, "menu") && !CEQ(target_type, "grid") && target_id == -1) {
		PyErr_SetObject(PyExc_TypeError, target_id_obj);
		PyErr_SetString(PyExc_TypeError, "target_id (integer, index value) is required for targets other than 'menu' or 'grid'");
		return NULL;
	}
	// at this point, `values` needs to be decoded based on the `field` provided
	
//            3.0, # duration, seconds
//            "demobox1", # parent: a UIMenu or Grid key
//            "menu", # target type: 'menu', 'grid', 'caption', 'button', 'sprite', or 'entity'
//            None, # target id: integer index for menu or grid objs; None for grid/menu
//            "position", # field: 'position', 'size', 'bgcolor', 'textcolor', or 'sprite'
//            lambda: self.animation_done("demobox1"), #callback: callable once animation is complete
//            False, #loop: repeat indefinitely
//            [100, 100] # values: iterable of frames for 'sprite', lerp target for others

//LerpAnimation<T>::LerpAnimation(float _d, T _ev, T _sv, std::function<void()> _cb, std::function<void(T)> _w, bool _l)
	if (CEQ(target_type, "menu")) {
		auto obj = menus[std::string(parent)];
		if (CEQ(field, "position")) {
			if (PyList_Check(values_obj)) evdata = PyList_AsTuple(values_obj); else evdata = values_obj;
			auto end_value = sf::Vector2f(PyFloat_AsDouble(PyTuple_GetItem(evdata, 0)),
										  PyFloat_AsDouble(PyTuple_GetItem(evdata, 1)));
			McRFPy_API::animations.push_back(new LerpAnimation<sf::Vector2f>(
				duration, end_value,
				obj->box.getPosition(),
				[=](){PyObject_Call(callback, PyTuple_New(0), NULL);},
				[=](sf::Vector2f v){obj->box.setPosition(v);},
				loop)
			);	
		}
		else if (CEQ(field, "size")) {
			if (PyList_Check(values_obj)) evdata = PyList_AsTuple(values_obj); else evdata = values_obj;
			auto end_value = sf::Vector2f(PyFloat_AsDouble(PyTuple_GetItem(evdata, 0)),
										  PyFloat_AsDouble(PyTuple_GetItem(evdata, 1)));
			McRFPy_API::animations.push_back(new LerpAnimation<sf::Vector2f>(
				duration, end_value,
				obj->box.getSize(),
				[=](){PyObject_Call(callback, PyTuple_New(0), NULL);},
				[=](sf::Vector2f v){obj->box.setSize(v);},
				loop)
			);
		}
		// else if (CEQ(field, "bgcolor")) { )
	}
	else if (CEQ(target_type, "sprite")) {
		if (CEQ(field, "position")) {
			auto obj = menus[std::string(parent)]->sprites[target_id];
			PyObject* evdata;
			if (PyList_Check(values_obj)) evdata = PyList_AsTuple(values_obj); else evdata = values_obj;
			auto end_value = sf::Vector2f(PyFloat_AsDouble(PyTuple_GetItem(evdata, 0)),
										  PyFloat_AsDouble(PyTuple_GetItem(evdata, 1)));
			McRFPy_API::animations.push_back(new LerpAnimation<sf::Vector2f>(duration, end_value,
				sf::Vector2f(obj.x, obj.y),
				[=](){PyObject_Call(callback, PyTuple_New(0), NULL);},
				[&](sf::Vector2f v){obj.x = v.x; obj.y = v.y;},
				loop)
			);			
		}
		else if (CEQ(field, "sprite")) {
			auto obj = menus[std::string(parent)];
			PyObject* evdata;
			if (PyList_Check(values_obj)) evdata = PyList_AsTuple(values_obj); else evdata = values_obj;
			std::vector<int> frames;
			for (int i = 0; i < PyTuple_Size(evdata); i++) {
				frames.push_back(PyLong_AsLong(PyTuple_GetItem(evdata, i)));
			}
//DiscreteAnimation(float _d, std::vector<T> _v, std::function<void()> _cb, std::function<void(T)> _w, bool _l)
			McRFPy_API::animations.push_back(new DiscreteAnimation<int>(
				duration,
				frames,
				[=](){PyObject_Call(callback, PyTuple_New(0), NULL);},
				[=](int s){obj->sprites[target_id].sprite_index = s;},
				loop)
			);
			//std::cout << "Frame animation constructed, there are now " <<McRFPy_API::animations.size() << std::endl;
		}
	}
	else if (CEQ(target_type, "entity")) {
		if (CEQ(field, "position")) {
			auto obj = grids[std::string(parent)]->entities[target_id];
			PyObject* evdata;
			if (PyList_Check(values_obj)) evdata = PyList_AsTuple(values_obj); else evdata = values_obj;
			auto end_value = sf::Vector2f(PyFloat_AsDouble(PyTuple_GetItem(evdata, 0)),
										  PyFloat_AsDouble(PyTuple_GetItem(evdata, 1)));
			McRFPy_API::animations.push_back(new LerpAnimation<sf::Vector2f>(duration, end_value,
				sf::Vector2f(obj->cGrid->indexsprite.x, obj->cGrid->indexsprite.y),
				[=](){PyObject_Call(callback, PyTuple_New(0), NULL);},
				[=](sf::Vector2f v){obj->cGrid->indexsprite.x = v.x; obj->cGrid->indexsprite.y = v.y;},
				loop)
			);			
		}
		else if (CEQ(field, "sprite")) {
			auto obj = grids[std::string(parent)];
			PyObject* evdata;
			if (PyList_Check(values_obj)) evdata = PyList_AsTuple(values_obj); else evdata = values_obj;
			std::vector<int> frames;
			for (int i = 0; i < PyTuple_Size(evdata); i++) {
				frames.push_back(PyLong_AsLong(PyTuple_GetItem(evdata, i)));
			}
//DiscreteAnimation(float _d, std::vector<T> _v, std::function<void()> _cb, std::function<void(T)> _w, bool _l)
			McRFPy_API::animations.push_back(new DiscreteAnimation<int>(
				duration,
				frames,
				[=](){PyObject_Call(callback, PyTuple_New(0), NULL);},
				[=](int s){obj->entities[target_id]->cGrid->indexsprite.sprite_index = s;},
				loop)
			);
		}
	}

    Py_INCREF(Py_None);
    return Py_None;	
}

PyObject* McRFPy_API::_createSoundBuffer(PyObject* self, PyObject* args) {
	const char *fn_cstr;
	if (!PyArg_ParseTuple(args, "s", &fn_cstr)) return NULL;
	auto b = sf::SoundBuffer();
	b.loadFromFile(fn_cstr);
	McRFPy_API::soundbuffers.push_back(b);
    Py_INCREF(Py_None);
    return Py_None;
}

PyObject* McRFPy_API::_loadMusic(PyObject* self, PyObject* args) {
	const char *fn_cstr;
	PyObject* loop_obj;
	if (!PyArg_ParseTuple(args, "s|O", &fn_cstr, &loop_obj)) return NULL;
	McRFPy_API::music.stop();
	// get params for sf::Music initialization
	//sf::InputSoundFile file;
	//file.openFromFile(fn_cstr);
	McRFPy_API::music.openFromFile(fn_cstr);
	McRFPy_API::music.setLoop(PyObject_IsTrue(loop_obj));
	//McRFPy_API::music.initialize(file.getChannelCount(), file.getSampleRate());
	McRFPy_API::music.play();
    Py_INCREF(Py_None);
    return Py_None;
}

PyObject* McRFPy_API::_setMusicVolume(PyObject* self, PyObject* args) {
	int vol;
	if (!PyArg_ParseTuple(args, "i", &vol)) return NULL;
	McRFPy_API::music.setVolume(vol);
    Py_INCREF(Py_None);
    return Py_None;
}

PyObject* McRFPy_API::_setSoundVolume(PyObject* self, PyObject* args) {
	float vol;
	if (!PyArg_ParseTuple(args, "f", &vol)) return NULL;
	McRFPy_API::sfx.setVolume(vol);
    Py_INCREF(Py_None);
    return Py_None;
}

PyObject* McRFPy_API::_playSound(PyObject* self, PyObject* args) {
	float index;
	if (!PyArg_ParseTuple(args, "f", &index)) return NULL;
	if (index >= McRFPy_API::soundbuffers.size()) return NULL;
	McRFPy_API::sfx.stop();
	McRFPy_API::sfx.setBuffer(McRFPy_API::soundbuffers[index]);
	McRFPy_API::sfx.play();
    Py_INCREF(Py_None);
    return Py_None;
}

PyObject* McRFPy_API::_getMusicVolume(PyObject* self, PyObject* args) {
	return Py_BuildValue("f", McRFPy_API::music.getVolume());
}

PyObject* McRFPy_API::_getSoundVolume(PyObject* self, PyObject* args) {
	return Py_BuildValue("f", McRFPy_API::sfx.getVolume());
}

PyObject* McRFPy_API::_unlockPlayerInput(PyObject* self, PyObject* args) {
	McRFPy_API::input_mode = "playerturn";
    Py_INCREF(Py_None);
    return Py_None;	
}
PyObject* McRFPy_API::_lockPlayerInput(PyObject* self, PyObject* args) {
	McRFPy_API::input_mode = "computerturnwait";
    Py_INCREF(Py_None);
    return Py_None;		
}
PyObject* McRFPy_API::_requestGridTarget(PyObject* self, PyObject* args) {
	const char* requestmode;
	if (!PyArg_ParseTuple(args, "s", &requestmode)) return NULL;
	McRFPy_API::input_mode = requestmode;
    Py_INCREF(Py_None);
    return Py_None;	
}
PyObject* McRFPy_API::_activeGrid(PyObject* self, PyObject* args) {
	return Py_BuildValue("s", McRFPy_API::active_grid.c_str());
}
PyObject* McRFPy_API::_setActiveGrid(PyObject* self, PyObject* args) {
	const char* newactivegrid;
	if (!PyArg_ParseTuple(args, "s", &newactivegrid)) return NULL;
	McRFPy_API::active_grid = newactivegrid;
    Py_INCREF(Py_None);
    return Py_None;		
}
PyObject* McRFPy_API::_inputMode(PyObject* self, PyObject* args) {
	return Py_BuildValue("s", McRFPy_API::input_mode.c_str());
}
PyObject* McRFPy_API::_turnNumber(PyObject* self, PyObject* args) {
	return Py_BuildValue("i", McRFPy_API::turn_number);
}
PyObject* McRFPy_API::_createEntity(PyObject* self, PyObject* args) {
	const char * grid_cstr, *entity_tag;
    int ti, si, x, y;
    PyObject* behavior_obj;
    if (!PyArg_ParseTuple(args, "ssiiii|O", &grid_cstr, &entity_tag, &ti, &si, &x, &y, &behavior_obj)) return NULL;
    auto e = entities.addEntity(std::string(entity_tag));
    Grid* grid_ptr = grids[grid_cstr];
	grid_ptr->entities.push_back(e);
    e->cGrid = std::make_shared<CGrid>(grid_ptr, ti, si, x, y, true);
    e->cBehavior = std::make_shared<CBehavior>(behavior_obj);
    Py_INCREF(behavior_obj);
	Py_INCREF(Py_None);
    return Py_None;
}

/*
PyObject* McRFPy_API::_listEntities(PyObject* self, PyObject* args) {
	Py_INCREF(Py_None);
    return Py_None;	
}
*/

void McRFPy_API::player_input(int dx, int dy) {
	//std::cout << "# entities tagged 'player': " << McRFPy_API::entities.getEntities("player").size() << std::endl;
	auto player_entity = McRFPy_API::entities.getEntities("player")[0];
	auto grid = player_entity->cGrid->grid;
	//std::cout << "Grid pointed to: " << (long)player_entity->cGrid->grid << std::endl;
	if (McRFPy_API::input_mode.compare("playerturn") != 0) {
		// no input accepted while computer moving
		//std::cout << "Can't move while it's not player's turn." << std::endl;
		return;
	}
	// TODO: selection cursor via keyboard
	// else if (!input_mode.compare("selectpoint") {}
	// else if (!input_mode.compare("selectentity") {}
	
	// grid bounds check
	if (player_entity->cGrid->x + dx < 0 ||
	    player_entity->cGrid->y + dy < 0 ||
	    player_entity->cGrid->x + dx > grid->grid_x - 1 ||
	    player_entity->cGrid->y + dy > grid->grid_y - 1) {
	    //std::cout << "(" << player_entity->cGrid->x << ", " << player_entity->cGrid->y <<
	    //  ") + (" << dx << ", " << dy << ") is OOB." << std::endl;
	    return;
	}
	//std::cout << PyUnicode_AsUTF8(PyObject_Repr(player_entity->cBehavior->object)) << std::endl;
	PyObject* move_fn = PyObject_GetAttrString(player_entity->cBehavior->object, "move");
	//std::cout << PyUnicode_AsUTF8(PyObject_Repr(move_fn)) << std::endl;
	if (move_fn) {
		//std::cout << "Calling `move`" << std::endl;
		PyObject* move_args = Py_BuildValue("(ii)", dx, dy);
		PyObject_CallObject((PyObject*) move_fn, move_args);
	} else {
		//std::cout << "player_input called on entity with no `move` method" << std::endl;
	}
}

void McRFPy_API::computerTurn() {
	McRFPy_API::input_mode = "computerturnrunning";
	for (auto e : McRFPy_API::grids[McRFPy_API::active_grid]->entities) {
		if (e->cBehavior) {
			PyObject_Call(PyObject_GetAttrString(e->cBehavior->object, "ai_act"), PyTuple_New(0), NULL);
		}
	}
}

void McRFPy_API::playerTurn() {
	McRFPy_API::input_mode = "playerturn";
	for (auto e : McRFPy_API::entities.getEntities("player")) {
		if (e->cBehavior) {
			PyObject_Call(PyObject_GetAttrString(e->cBehavior->object, "player_act"), PyTuple_New(0), NULL);
		}
	}
}

void McRFPy_API::camFollow() {
	if (!McRFPy_API::do_camfollow) return;
	auto& ag = McRFPy_API::grids[McRFPy_API::active_grid];
	for (auto e : McRFPy_API::entities.getEntities("player")) {
		//std::cout << "grid center: " << ag->center_x << ", " << ag->center_y << std::endl <<
		//             "player grid pos: " << e->cGrid->x << ", " << e->cGrid->y << std::endl <<
		//             "player sprite pos: " << e->cGrid->indexsprite.x << ", " << e->cGrid->indexsprite.y << std::endl;
		ag->center_x = e->cGrid->indexsprite.x * ag->grid_size + ag->grid_size * 0.5;
		ag->center_y = e->cGrid->indexsprite.y * ag->grid_size + ag->grid_size * 0.5;
	}
}

PyObject* McRFPy_API::_camFollow(PyObject* self, PyObject* args) {
	PyObject* set_camfollow = NULL;
	//std::cout << "camFollow Parse Args" << std::endl;
	if (!PyArg_ParseTuple(args, "|O", &set_camfollow)) return NULL;
	//std::cout << "Parsed" << std::endl;
	if (set_camfollow == NULL) {
		// return value
		//std::cout << "null; Returning value " << McRFPy_API::do_camfollow << std::endl;
		Py_INCREF(McRFPy_API::do_camfollow ? Py_True : Py_False);
		return McRFPy_API::do_camfollow ? Py_True : Py_False;
	}
	//std::cout << "non-null; setting value" << std::endl;
	
	McRFPy_API::do_camfollow = PyObject_IsTrue(set_camfollow);
	Py_INCREF(Py_None);
    return Py_None;
}
