/* 
 *
 * ***** BEGIN GPL/BL DUAL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version. The Blender
 * Foundation also sells licenses for use in proprietary software under
 * the Blender License.  See http://www.blender.org/BL/ for information
 * about this.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 *
 * This is a new part of Blender.
 *
 * Contributor(s): Michel Selten, Willian P. Germano, Stephen Swaney
 *
 * ***** END GPL/BL DUAL LICENSE BLOCK *****
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <Python.h>
#include "compile.h" /* for the PyCodeObject */
#include "eval.h" /* for PyEval_EvalCode */

#include <stdio.h>

#include <MEM_guardedalloc.h>

#include <BLI_blenlib.h> /* for BLI_last_slash() */

#include <BKE_global.h>
#include <BKE_main.h>
#include <BKE_text.h>
#include <DNA_camera_types.h>
#include <DNA_ID.h>
#include <DNA_lamp_types.h>
#include <DNA_material_types.h>
#include <DNA_object_types.h>
#include <DNA_scene_types.h>
#include <DNA_scriptlink_types.h>
#include <DNA_space_types.h>
#include <DNA_text_types.h>
#include <DNA_world_types.h>

#include <DNA_userdef_types.h> /* for U.pythondir */

#include "BPY_extern.h"
#include "api2_2x/EXPP_interface.h"

/* bpy_registryDict is declared in api2_2x/Registry.h and defined
 * here.  This Python dictionary will be used to store data that scripts
 * choose to preserve after they are executed, so user changes can be
 * restored next time the script is used.  Check the Blender.Registry module. */
extern PyObject *bpy_registryDict;

/*****************************************************************************/
/* Structure definitions                                                     */
/*****************************************************************************/
#define FILENAME_LENGTH 24
typedef struct _ScriptError {
  char filename[FILENAME_LENGTH];
  int lineno;
} ScriptError;

/*****************************************************************************/
/* Global variables                                                          */
/*****************************************************************************/
ScriptError g_script_error;
short EXPP_releaseGlobalDict = 1;

/*****************************************************************************/
/* Function prototypes                                                       */
/*****************************************************************************/
PyObject *RunPython(Text *text, PyObject *globaldict);
char     *GetName(Text *text);
PyObject *CreateGlobalDictionary (void);
void      ReleaseGlobalDictionary (PyObject * dict);
void      DoAllScriptsFromList (ListBase * list, short event);
PyObject *importText(char *name);
void init_ourImport(void);
PyObject *blender_import(PyObject *self, PyObject *args);

/*****************************************************************************/
/* Description: This function will initialise Python and all the implemented */
/*              api variations.                                              */
/* Notes:       Currently only the api for 2.2x will be initialised.         */
/*****************************************************************************/
void BPY_start_python(void)
{
  bpy_registryDict = PyDict_New(); /* check comment at start of this file */

  if (!bpy_registryDict)
    printf("Error: Couldn't create the Registry Python Dictionary!");

/* TODO: Shouldn't "blender" be replaced by PACKAGE ?? (config.h) */
  Py_SetProgramName("blender");

  Py_Initialize ();

  init_ourImport ();

  initBlenderApi2_2x ();

  init_syspath();

  return;
}

/*****************************************************************************/
/* Description: This function will terminate the Python interpreter          */
/*****************************************************************************/
void BPY_end_python(void)
{
  if (bpy_registryDict) {
    Py_DECREF (bpy_registryDict);
    bpy_registryDict = NULL;
  }

  Py_Finalize();
  return;
}

void syspath_append(PyObject *dir)
{
  PyObject *mod_sys, *dict, *path;

  PyErr_Clear();

  mod_sys = PyImport_ImportModule("sys"); /* new ref */
  dict = PyModule_GetDict(mod_sys);       /* borrowed ref */
  path = PyDict_GetItemString(dict, "path"); /* borrowed ref */

  if (!PyList_Check(path)) return;

  PyList_Append(path, dir);

  if (PyErr_Occurred()) Py_FatalError("could not build sys.path");

  Py_DECREF(mod_sys);
}

void init_syspath(void)
{
  PyObject *path;
  PyObject *mod, *d;
  PyObject *p;
  char *c, *progname;
  char execdir[FILE_MAXDIR + FILE_MAXFILE];/*defines from DNA_space_types.h*/

  int n;

  path = Py_BuildValue("s", bprogname);

  mod = PyImport_ImportModule("Blender.sys");

  if (mod) {
    d = PyModule_GetDict(mod);
    PyDict_SetItemString(d, "progname", path);
    Py_DECREF(mod);
  }
  else
    printf("Warning: could not set Blender.sys.progname\n");

  progname = BLI_last_slash(bprogname); /* looks for the last dir separator */

  c = Py_GetPath(); /* get python system path */
  PySys_SetPath(c); /* initialize */

  n = progname - bprogname;
  if (n > 0) {
    strncpy(execdir, bprogname, n);
    if (execdir[n-1] == '.') n--; /*fix for when run as ./blender */
    execdir[n] = '\0';

    p = Py_BuildValue("s", execdir);
    syspath_append(p);  /* append to module search path */

    /* set Blender.sys.progname */
  }
  else
    printf ("Warning: could not determine argv[0] path\n");

  if (U.pythondir) {
    p = Py_BuildValue("s", U.pythondir);
    syspath_append(p);  /* append to module search path */
  }

  /* 
   * bring in the site module so we can add 
   * site-package dirs to sys.path 
   */

  mod = PyImport_ImportModule("site"); /* new ref */

  if (mod) {
    PyObject* item;
    int size = 0;
    int index;

    /* get the value of 'sitedirs' from the module */

    /* the ref man says GetDict() never fails!!! */
    d = PyModule_GetDict (mod); /* borrowed ref */
    p = PyDict_GetItemString (d, "sitedirs");  /* borrowed ref */

    if( p ) {  /* we got our string */
      /* append each item in sitedirs list to path */
      size = PyList_Size (p);

      for (index = 0; index < size; index++) {
	item  = PySequence_GetItem (p, index);  /* new ref */
	if( item )
	  syspath_append (item);
      }
    }
    Py_DECREF(mod);
  }
  else {  /* import 'site' failed */
    PyErr_Clear();
    printf("sys_init:warning - no sitedirs added from site module.\n");
  }

  /* 
   * initialize the sys module
   * set sys.executable to the Blender exe 
   * set argv[0] to the Blender exe
   */

  mod = PyImport_ImportModule("sys"); /* new ref */

  if (mod) {
    d = PyModule_GetDict(mod); /* borrowed ref */
    PyDict_SetItemString(d, "executable", Py_BuildValue("s", bprogname));
    /* in the future this can be extended to have more argv's if needed: */
    PyDict_SetItemString(d, "argv", Py_BuildValue("[s]", bprogname));
    Py_DECREF(mod);
  }
}

/*****************************************************************************/
/* Description: This function adds the user defined folder for Python        */
/*              scripts to sys.path.  This is done in init_syspath, too, but */
/*              when Blender's main() runs BPY_start_python(), U.pythondir   */
/*              isn't set yet, so we provide this function to be executed    */
/*              after U.pythondir is defined.                                */
/*****************************************************************************/
void BPY_syspath_append_pythondir(void)
{
  syspath_append(Py_BuildValue("s", U.pythondir));
}

/*****************************************************************************/
/* Description: This function will return the linenumber on which an error   */
/*              has occurred in the Python script.                           */
/*****************************************************************************/
int BPY_Err_getLinenumber(void)
{
  return g_script_error.lineno;
}

/*****************************************************************************/
/* Description: This function will return the filename of the python script. */
/*****************************************************************************/
const char *BPY_Err_getFilename(void)
{
  return g_script_error.filename;
}

/*****************************************************************************/
/* Description: Return PyString filename from a traceback object             */
/*****************************************************************************/
PyObject *traceback_getFilename(PyObject *tb)
{
  PyObject *v;

/* co_filename is in f_code, which is in tb_frame, which is in tb */

  v = PyObject_GetAttrString(tb, "tb_frame"); Py_XDECREF(v);
  v = PyObject_GetAttrString(v, "f_code"); Py_XDECREF(v);
  v = PyObject_GetAttrString(v, "co_filename");

  return v;
}

/*****************************************************************************/
/* Description: Blender Python error handler. This catches the error and     */
/* stores filename and line number in a global                               */
/*****************************************************************************/
void BPY_Err_Handle(Text *text)
{
  PyObject *exception, *err, *tb, *v;

  PyErr_Fetch(&exception, &err, &tb);

  if (!exception && !tb) {
    printf("FATAL: spurious exception\n");
    return;
  }

  strcpy(g_script_error.filename, GetName(text));

  if (exception && PyErr_GivenExceptionMatches(exception, PyExc_SyntaxError)) {
    /* no traceback available when SyntaxError */
    PyErr_Restore(exception, err, tb); /* takes away reference! */
    PyErr_Print();
    v = PyObject_GetAttrString(err, "lineno");
    g_script_error.lineno = PyInt_AsLong(v);
    Py_XDECREF(v);
    /* this avoids an abort in Python 2.3's garbage collecting: */
    PyErr_Clear(); 
    return;
  } else {
    PyErr_NormalizeException(&exception, &err, &tb);
    PyErr_Restore(exception, err, tb); // takes away reference!
    PyErr_Print();
    tb = PySys_GetObject("last_traceback");

    if (!tb) {
      printf("\nCan't get traceback\n");
      return;
    }

    Py_INCREF(tb);

/* From old bpython BPY_main.c:
 * 'check traceback objects and look for last traceback in the
 *  same text file. This is used to jump to the line of where the
 *  error occured. "If the error occured in another text file or module,
 *  the last frame in the current file is adressed."' */

    while (1) { 
      v = PyObject_GetAttrString(tb, "tb_next");

      if (v == Py_None || strcmp(PyString_AsString(traceback_getFilename(v)),
                              GetName(text))) {
        break;
      }

      Py_DECREF(tb);
      tb = v;
    }

    v = PyObject_GetAttrString(tb, "tb_lineno");
    g_script_error.lineno = PyInt_AsLong(v);
    Py_XDECREF(v);
    v = traceback_getFilename(tb);
    strncpy(g_script_error.filename, PyString_AsString(v), FILENAME_LENGTH);
    Py_XDECREF(v);
    Py_DECREF(tb);
  }

  return;
}

/*****************************************************************************/
/* Description: This function executes the script passed by st.              */
/* Notes:       It is called by blender/src/drawtext.c when a Blender user   */
/*              presses ALT+PKEY in the script's text window.                */
/*****************************************************************************/
struct _object *BPY_txt_do_python(struct SpaceText* st)
{
  PyObject *dict, *ret;
  PyObject *main_dict = PyModule_GetDict(PyImport_AddModule("__main__"));

  if (!st->text) return NULL;

/* The EXPP_releaseGlobalDict global variable controls whether we should run
 * the script with a clean global dictionary or should keep the current one,
 * possibly already "polluted" by other calls to the Python Interpreter.
 * The default is to use a clean one.  To change this the script writer must
 * call Blender.ReleaseGlobalDict(bool), with bool == 0, in the script. */

  if (EXPP_releaseGlobalDict) {
    printf("Using a clean Global Dictionary.\n");
    st->flags |= ST_CLEAR_NAMESPACE;
    dict = CreateGlobalDictionary();
  }
  else
    dict = main_dict; /* must be careful not to free the main_dict */

  clearScriptLinks ();

  ret = RunPython (st->text, dict); /* Run the script */

  if (!ret) { /* Failed execution of the script */

    if (EXPP_releaseGlobalDict && (dict != main_dict))
      ReleaseGlobalDictionary(dict);

    BPY_Err_Handle(st->text);
    BPY_end_python();
    BPY_start_python();

    return NULL;
  }

  else Py_DECREF (ret);

/* Scripts that use the GUI rely on the persistent global namespace, so
 * they need a workaround: The namespace is released when the GUI is exit.'
 * See api2_2x/Draw.c: Method_Register() */

/* Block below: The global dict should only be released if:
 * - a script didn't defined it to be persistent and
 * - Draw.Register() is not in use (no GUI) and
 * - it is not the real __main__ dict (if it is, restart to clean it) */

  if (EXPP_releaseGlobalDict) {
    if (st->flags & ST_CLEAR_NAMESPACE) { /* False if the GUI is in use */

      if (dict != main_dict) ReleaseGlobalDictionary(dict);

      else {
        BPY_end_python(); /* restart to get a fresh __main__ dict */
        BPY_start_python();
      }

    }
  }
  else if (dict != main_dict) PyDict_Update (main_dict, dict);

/* Line above: if it should be kept and it's not already the __main__ dict,
 * merge it into the __main__ one.  This happens when to release is the
 * current behavior and the script changes that with
 * Blender.ReleaseGlobalDict(0). */

/* Edited from old BPY_main.c:
 * 'The return value is the global namespace dictionary of the script
 *  context.  This may be stored in the SpaceText instance to give control
 *  over namespace persistence.  Remember that the same script may be
 *  executed in several windows ...  Namespace persistence is desired for
 *  scripts that use the GUI and store callbacks to the current script.' */

  return dict;
}

/*****************************************************************************/
/* Description:                                                              */
/* Notes:                                                                    */
/*****************************************************************************/
void BPY_free_compiled_text(struct Text* text)
{
  if (!text->compiled) return;
  Py_DECREF((PyObject*) text->compiled);
  text->compiled = NULL;

  return;
}

/*****************************************************************************/
/* ScriptLinks                                                               */
/*****************************************************************************/

/*****************************************************************************/
/* Description:                                                              */
/* Notes:       Not implemented yet                                          */
/*****************************************************************************/
void BPY_clear_bad_scriptlinks(struct Text *byebye)
{
/*
  BPY_clear_bad_scriptlist(getObjectList(), byebye);
  BPY_clear_bad_scriptlist(getLampList(), byebye);
  BPY_clear_bad_scriptlist(getCameraList(), byebye);
  BPY_clear_bad_scriptlist(getMaterialList(), byebye);
  BPY_clear_bad_scriptlist(getWorldList(),  byebye);
  BPY_clear_bad_scriptlink(&scene_getCurrent()->id, byebye);

  allqueue(REDRAWBUTSSCRIPT, 0);
*/
  return;
}

/*****************************************************************************/
/* Description: Loop through all scripts of a list of object types, and      */
/*              execute these scripts.                                       */
/*              For the scene, only the current active scene the scripts are */
/*              executed (if any).                                           */
/*****************************************************************************/
void BPY_do_all_scripts(short event)
{
  DoAllScriptsFromList (&(G.main->object), event);
  DoAllScriptsFromList (&(G.main->lamp), event);
  DoAllScriptsFromList (&(G.main->camera), event);
  DoAllScriptsFromList (&(G.main->mat), event);
  DoAllScriptsFromList (&(G.main->world), event);

  BPY_do_pyscript (&(G.scene->id), event);

  return;
}

/*****************************************************************************/
/* Description: Execute a Python script when an event occurs. The following  */
/*              events are possible: frame changed, load script and redraw.  */
/*              Only events happening to one of the following object types   */
/*              are handled: Object, Lamp, Camera, Material, World and       */
/*              Scene.                                                       */
/*****************************************************************************/
void BPY_do_pyscript(struct ID *id, short event)
{
  ScriptLink  * scriptlink;
  int           index;
  PyObject    * dict;
  PyObject    * ret;

  scriptlink = setScriptLinks (id, event);

  if (scriptlink == NULL) return;

  for (index = 0; index < scriptlink->totscript; index++)
  {
    if ((scriptlink->flag[index] == event) &&
        (scriptlink->scripts[index] != NULL))
    {
      dict = CreateGlobalDictionary();
      ret = RunPython ((Text*) scriptlink->scripts[index], dict);
      ReleaseGlobalDictionary (dict);
      if (!ret)
      {
          /* Failed execution of the script */
          BPY_Err_Handle ((Text*) scriptlink->scripts[index]);
          BPY_end_python ();
          BPY_start_python ();
      }
      else
      {
          Py_DECREF (ret);
      }
    }
  }

  return;
}

/*****************************************************************************/
/* Description:                                                              */
/* Notes:                                                                    */
/*****************************************************************************/
void BPY_free_scriptlink(struct ScriptLink *slink)
{
  if (slink->totscript) {
    if(slink->flag) MEM_freeN(slink->flag);
    if(slink->scripts) MEM_freeN(slink->scripts); 
  }

  return;
}

/*****************************************************************************/
/* Description:                                                              */
/* Notes:                                                                    */
/*****************************************************************************/
void BPY_copy_scriptlink(struct ScriptLink *scriptlink)
{
  void *tmp;

  if (scriptlink->totscript) {

    tmp = scriptlink->scripts;
    scriptlink->scripts =
      MEM_mallocN(sizeof(ID*)*scriptlink->totscript, "scriptlistL");
    memcpy(scriptlink->scripts, tmp, sizeof(ID*)*scriptlink->totscript);

    tmp = scriptlink->flag;
    scriptlink->flag =
      MEM_mallocN(sizeof(short)*scriptlink->totscript, "scriptlistF");
    memcpy(scriptlink->flag, tmp, sizeof(short)*scriptlink->totscript);
  }

  return;
}

/*****************************************************************************/
/* Description:                                                              */
/* Notes:       Not implemented yet                                          */
/*****************************************************************************/
int BPY_call_importloader(char *name)
{ /* XXX Should this function go away from Blender? */
  printf ("In BPY_call_importloader(name=%s)\n",name);
  return (0);
}

/*****************************************************************************/
/* Private functions                                                         */
/*****************************************************************************/

/*****************************************************************************/
/* Description: This function executes the python script passed by text.     */
/*              The Python dictionary containing global variables needs to   */
/*              be passed in globaldict.                                     */
/*****************************************************************************/
PyObject * RunPython(Text *text, PyObject *globaldict)
{
  char *buf = NULL;

/* The script text is compiled to Python bytecode and saved at text->compiled
 * to speed-up execution if the user executes the script multiple times */

  if (!text->compiled) { /* if it wasn't already compiled, do it now */
    buf = txt_to_buf(text);

    text->compiled = Py_CompileString(buf, GetName(text), Py_file_input);

    MEM_freeN(buf);

    if (PyErr_Occurred()) {
      BPY_free_compiled_text(text);
      return NULL;
    }

  }

  return PyEval_EvalCode(text->compiled, globaldict, globaldict);
}

/*****************************************************************************/
/* Description: This function returns the value of the name field of the     */
/*              given Text struct.                                           */
/*****************************************************************************/
char * GetName(Text *text)
{
  return (text->id.name+2);
}

/*****************************************************************************/
/* Description: This function creates a new Python dictionary object.        */
/*****************************************************************************/
PyObject * CreateGlobalDictionary (void)
{
  PyObject *dict = PyDict_New();

  PyDict_SetItemString (dict, "__builtins__", PyEval_GetBuiltins());
  PyDict_SetItemString (dict, "__name__", PyString_FromString("__main__"));

  return dict;
}

/*****************************************************************************/
/* Description: This function deletes a given Python dictionary object.      */
/*****************************************************************************/
void ReleaseGlobalDictionary (PyObject * dict)
{
  PyDict_Clear (dict);
  Py_DECREF (dict);   /* Release dictionary. */

  return;
}

/*****************************************************************************/
/* Description: This function runs all scripts (if any) present in the       */
/*              list argument. The event by which the function has been      */
/*              called, is passed in the event argument.                     */
/*****************************************************************************/
void DoAllScriptsFromList (ListBase *list, short event)
{
  ID *id;

  id = list->first;

  while (id != NULL) {
    BPY_do_pyscript (id, event);
    id = id->next;
  }

  return;
}

PyObject *importText(char *name)
{
  Text *text;
  char *txtname;
  char *buf = NULL;
  int namelen = strlen(name);

  txtname = malloc(namelen+3+1);
  if (!txtname) return NULL;

  memcpy(txtname, name, namelen);
  memcpy(&txtname[namelen], ".py", 4);

  text = (Text*) &(G.main->text.first);

  while(text) {
    if (!strcmp (txtname, GetName(text)))
      break;
    text = text->id.next;
  }

  if (!text) {
    free(txtname);
    return NULL;
  }

  if (!text->compiled) {
    buf = txt_to_buf(text);
    text->compiled = Py_CompileString(buf, GetName(text), Py_file_input);
    MEM_freeN(buf);

    if (PyErr_Occurred()) {
      PyErr_Print();
      BPY_free_compiled_text(text);
      free(txtname);
      return NULL;
    }
  }

  free(txtname);
  return PyImport_ExecCodeModule(name, text->compiled);
}

static PyMethodDef bimport[] = {
  { "blimport", blender_import, METH_VARARGS, "our own import"}
};

PyObject *blender_import(PyObject *self, PyObject *args)
{
  PyObject *exception, *err, *tb;
  char *name;
  PyObject *globals = NULL, *locals = NULL, *fromlist = NULL;
  PyObject *m;

  if (!PyArg_ParseTuple(args, "s|OOO:bimport",
          &name, &globals, &locals, &fromlist))
      return NULL;

  m = PyImport_ImportModuleEx(name, globals, locals, fromlist);

  if (m) 
    return m;
  else
    PyErr_Fetch(&exception, &err, &tb); /*restore for probable later use*/
  
  m = importText(name);
  if (m) { /* found module, ignore above exception*/
    PyErr_Clear();
    Py_XDECREF(exception); Py_XDECREF(err); Py_XDECREF(tb);
    printf("imported from text buffer...\n");
  } else {
    PyErr_Restore(exception, err, tb);
  }
  return m;
}

void init_ourImport(void)
{
  PyObject *m, *d;
  PyObject *import = PyCFunction_New(bimport, NULL);

  m = PyImport_AddModule("__builtin__");
  d = PyModule_GetDict(m);
  PyDict_SetItemString(d, "__import__", import);
}
