#ifdef KIT_INCLUDES_TK
#  include <tk.h>
#else
#  include <tcl.h>
#endif /* KIT_INCLUDES_TK */

#ifdef _WIN32
#  define WIN32_LEAN_AND_MEAN
#  include <windows.h>
#  undef WIN32_LEAN_AND_MEAN
#endif /* _WIN32 */

#ifdef HAVE_STRING_H
#  include <string.h>
#endif
#ifdef HAVE_STRINGS_H
#  include <strings.h>
#endif
#ifdef HAVE_DLFCN_H
#  include <dlfcn.h>
#endif

#include "tclInt.h"

#if defined(HAVE_TCL_GETENCODINGNAMEFROMENVIRONMENT) && defined(HAVE_TCL_SETSYSTEMENCODING)
#  define TCLKIT_CAN_SET_ENCODING 1
#endif
#if 10 * TCL_MAJOR_VERSION + TCL_MINOR_VERSION < 85
#  define TCLKIT_REQUIRE_TCLEXECUTABLENAME 1
#endif
#if 10 * TCL_MAJOR_VERSION + TCL_MINOR_VERSION < 85
#  define KIT_INCLUDES_PWB 1
#endif

Tcl_AppInitProc Vfs_Init, Rechan_Init;
Tcl_AppInitProc Vfs_kitdll_data_tcl_Init;
#ifdef KIT_INCLUDES_MK4TCL
Tcl_AppInitProc Mk4tcl_Init;
#endif
#ifdef KIT_INCLUDES_PWB
Tcl_AppInitProc Pwb_Init;
#endif
#ifdef TCL_THREADS
Tcl_AppInitProc Thread_Init;
#endif
#ifdef _WIN32
Tcl_AppInitProc Dde_Init, Registry_Init;
#endif

/*
 * This Tcl code is invoked whenever Tcl_Init() is called on an
 * interpreter.  It should mount up the VFS and make everything ready for
 * that interpreter to do its job.
 */
static char *preInitCmd =
"proc tclKitInit {} {\n"
	"rename tclKitInit {}\n"
#ifdef KIT_INCLUDES_MK4TCL
	"catch { load {} Mk4tcl }\n"
#endif
	"load {} tclkit::init\n"
	"load {} rechan\n"
	"load {} vfs\n"
	"load {} vfs_kitdll_data_tcl\n"
#include "vfs_kitdll.tcl.h"
	"if {![file exists \"/.KITDLL_TCL/boot.tcl\"]} {\n"
		"vfs::kitdll::Mount tcl /.KITDLL_TCL\n"
		"set ::initVFS 1\n"
	"}\n"
	"set f [open \"/.KITDLL_TCL/boot.tcl\"]\n"
	"set s [read $f]\n"
	"close $f\n"
	"::tclkit::init::initInterp\n"
	"rename ::tclkit::init::initInterp {}\n"
	"uplevel #0 $s\n"
#if defined(KIT_INCLUDES_TK) && defined(KIT_TK_VERSION)
	"package ifneeded Tk " KIT_TK_VERSION " {\n"
		"load {} Tk\n"
	"}\n"
#endif
#ifdef _WIN32
	"catch {load {} dde}\n"
	"catch {load {} registry}\n"
#endif /* _WIN32 */
"}\n"
"tclKitInit";

#ifdef HAVE_ACCEPTABLE_DLADDR
/* Symbol to resolve against dladdr() */
static void _tclkit_dummy_func(void) {
	return;
}

int main(int argc, char **argv);
#endif /* HAVE_ACCEPTABLE_DLADDR */

/*
 * This function will return a pathname we can open() to treat as a VFS,
 * hopefully
 */
static char *find_tclkit_dll_path(void) {
#ifdef HAVE_ACCEPTABLE_DLADDR
	Dl_info syminfo;
	int dladdr_ret;
#endif /* HAVE_ACCEPTABLE_DLADDR */
#ifdef _WIN32
	TCHAR modulename[8192];
	DWORD gmfn_ret;
#endif /* _WIN32 */

#ifdef HAVE_ACCEPTABLE_DLADDR
	dladdr_ret = dladdr(&_tclkit_dummy_func, &syminfo);
	if (dladdr_ret != 0) {
		if (syminfo.dli_fname && syminfo.dli_fname[0] != '\0') {
			return(strdup(syminfo.dli_fname));
		}
	}
#endif /* HAVE_ACCEPTABLE_DLADDR */

#ifdef _WIN32
	gmfn_ret = GetModuleFileName(TclWinGetTclInstance(), modulename, sizeof(modulename) / sizeof(modulename[0]) - 1);

	if (gmfn_ret != 0) {
		return(strdup(modulename));
	}
#endif /* _WIN32 */

	return(NULL);
}

/* SetExecName --
	
   Hack to get around Tcl bug 1224888.
*/
static void SetExecName(Tcl_Interp *interp, const char *path) {
#ifdef TCLKIT_REQUIRE_TCLEXECUTABLENAME
	tclExecutableName = strdup(path);
#endif  
	Tcl_FindExecutable(path);

	return;
}
	
static void FindAndSetExecName(Tcl_Interp *interp) {
	int len = 0;
	Tcl_Obj *execNameObj;
	Tcl_Obj *lobjv[1];
#ifdef HAVE_READLINK
	ssize_t readlink_ret;
	char exe_buf[4096];
#endif /* HAVE_READLINK */
#ifdef HAVE_ACCEPTABLE_DLADDR
	Dl_info syminfo;
	int dladdr_ret;
#endif /* HAVE_ACCEPTABLE_DLADDR */

#ifdef HAVE_READLINK
	if (Tcl_GetNameOfExecutable() == NULL) {
		readlink_ret = readlink("/proc/self/exe", exe_buf, sizeof(exe_buf) - 1);
		
		if (readlink_ret > 0 && readlink_ret < (sizeof(exe_buf) - 1)) {
			exe_buf[readlink_ret] = '\0';
			
			SetExecName(interp, exe_buf);
			
			return;
		}
	}
	
	if (Tcl_GetNameOfExecutable() == NULL) {
		readlink_ret = readlink("/proc/curproc/file", exe_buf, sizeof(exe_buf) - 1);												 

		if (readlink_ret > 0 && readlink_ret < (sizeof(exe_buf) - 1)) {
			exe_buf[readlink_ret] = '\0';

			if (strcmp(exe_buf, "unknown") != 0) {
				SetExecName(interp, exe_buf);

				return;
			}
		}
	}
#endif /* HAVE_READLINK */

#ifdef HAVE_ACCEPTABLE_DLADDR
	if (Tcl_GetNameOfExecutable() == NULL) {
		dladdr_ret = dladdr(&main, &syminfo);
		if (dladdr_ret != 0) {
			SetExecName(interp, syminfo.dli_fname);
		}
	}
#endif /* HAVE_ACCEPTABLE_DLADDR */

	if (Tcl_GetNameOfExecutable() == NULL) {
		lobjv[0] = Tcl_GetVar2Ex(interp, "argv0", NULL, TCL_GLOBAL_ONLY);
		execNameObj = Tcl_FSJoinToPath(Tcl_FSGetCwd(interp), 1, lobjv);

		SetExecName(interp, Tcl_GetStringFromObj(execNameObj, &len));

		return;
	}

	return;
}


/*
 * This function exists to allow C code to initialize a particular
 * interpreter.
 */
static int tclkit_init_initinterp(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	char *kitdll_path;
#ifdef TCLKIT_CAN_SET_ENCODING
	Tcl_DString encodingName;
#endif /* TCLKIT_CAN_SET_ENCODING */


#ifdef _WIN32
	Tcl_SetVar(interp, "tcl_rcFileName", "~/tclkitrc.tcl", TCL_GLOBAL_ONLY);
#else   
	Tcl_SetVar(interp, "tcl_rcFileName", "~/.tclkitrc", TCL_GLOBAL_ONLY);
#endif

	kitdll_path = find_tclkit_dll_path();
	if (kitdll_path != NULL) {
		Tcl_SetVar(interp, "tclKitFilename", kitdll_path, TCL_GLOBAL_ONLY);

		free(kitdll_path);
	}

	FindAndSetExecName(interp);

#ifdef TCLKIT_CAN_SET_ENCODING
	/* Set the encoding from the Environment */
	Tcl_GetEncodingNameFromEnvironment(&encodingName);
	Tcl_SetSystemEncoding(NULL, Tcl_DStringValue(&encodingName));
	Tcl_SetVar(interp, "tclkit_system_encoding", Tcl_DStringValue(&encodingName), TCL_GLOBAL_ONLY);
	Tcl_DStringFree(&encodingName);
#endif  

	return(TCL_OK);
}

/*
 * Create a package for initializing a particular interpreter.  This is
 * our hook to have Tcl invoke C commands when creating an interpreter.
 * The preInitCmd will load the package in the new interpreter and invoke
 * this function.
 */
int Tclkit_init_Init(Tcl_Interp *interp) {
	Tcl_Command tclCreatComm_ret;
	int tclPkgProv_ret;

	tclCreatComm_ret = Tcl_CreateObjCommand(interp, "::tclkit::init::initInterp", tclkit_init_initinterp, NULL, NULL);
	if (!tclCreatComm_ret) {
		return(TCL_ERROR);
	}

	tclPkgProv_ret = Tcl_PkgProvide(interp, "tclkit::init", "1.0");

	return(tclPkgProv_ret);
}

/*
 * Initialize the Tcl system when we are loaded, that way Tcl functions
 * are ready to be used when invoked.
 */
void __attribute__((constructor)) _Tclkit_Init(void) {
	Tcl_StaticPackage(0, "tclkit::init", Tclkit_init_Init, NULL);
	Tcl_StaticPackage(0, "rechan", Rechan_Init, NULL);
	Tcl_StaticPackage(0, "vfs", Vfs_Init, NULL);
	Tcl_StaticPackage(0, "vfs_kitdll_data_tcl", Vfs_kitdll_data_tcl_Init, NULL);
#ifdef KIT_INCLUDES_MK4TCL
	Tcl_StaticPackage(0, "Mk4tcl", Mk4tcl_Init, NULL);
#endif
#ifdef KIT_INCLUDES_PWB
	Tcl_StaticPackage(0, "pwb", Pwb_Init, NULL);
#endif
#ifdef TCL_THREADS
	Tcl_StaticPackage(0, "Thread", Thread_Init, NULL);
#endif
#ifdef _WIN32
	Tcl_StaticPackage(0, "dde", Dde_Init, NULL);
	Tcl_StaticPackage(0, "registry", Registry_Init, NULL);
#endif
#ifdef KIT_INCLUDES_TK
	Tcl_StaticPackage(0, "Tk", Tk_Init, Tk_SafeInit);
#endif  

	TclSetPreInitScript(preInitCmd);

	return;
}
