/* w_tk.c
 *
 * Micropolis, Unix Version.  This game was released for the Unix platform
 * in or about 1990 and has been modified for inclusion in the One Laptop
 * Per Child program.  Copyright (C) 1989 - 2007 Electronic Arts Inc.  If
 * you need assistance with this program, you may contact:
 *   http://wiki.laptop.org/go/Micropolis  or email  micropolis@laptop.org.
 * 
 * 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 3 of the License, or (at
 * your option) any later version.
 * 
 * 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, see <http://www.gnu.org/licenses/>.
 * 
 *             ADDITIONAL TERMS per GNU GPL Section 7
 * 
 * No trademark or publicity rights are granted.  This license does NOT
 * give you any right, title or interest in the trademark SimCity or any
 * other Electronic Arts trademark.  You may not distribute any
 * modification of this program using the trademark SimCity or claim any
 * affliation or association with Electronic Arts Inc. or its employees.
 * 
 * Any propagation or conveyance of this program must include this
 * copyright notice and these terms.
 * 
 * If you convey this program (or any modifications of it) and assume
 * contractual liability for the program to recipients of it, you agree
 * to indemnify Electronic Arts for any liability that those contractual
 * assumptions impose on Electronic Arts.
 * 
 * You may not misrepresent the origins of this program; modified
 * versions of the program must be marked as such and not identified as
 * the original program.
 * 
 * This disclaimer supplements the one included in the General Public
 * License.  TO THE FULLEST EXTENT PERMISSIBLE UNDER APPLICABLE LAW, THIS
 * PROGRAM IS PROVIDED TO YOU "AS IS," WITH ALL FAULTS, WITHOUT WARRANTY
 * OF ANY KIND, AND YOUR USE IS AT YOUR SOLE RISK.  THE ENTIRE RISK OF
 * SATISFACTORY QUALITY AND PERFORMANCE RESIDES WITH YOU.  ELECTRONIC ARTS
 * DISCLAIMS ANY AND ALL EXPRESS, IMPLIED OR STATUTORY WARRANTIES,
 * INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY, SATISFACTORY QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT OF THIRD PARTY
 * RIGHTS, AND WARRANTIES (IF ANY) ARISING FROM A COURSE OF DEALING,
 * USAGE, OR TRADE PRACTICE.  ELECTRONIC ARTS DOES NOT WARRANT AGAINST
 * INTERFERENCE WITH YOUR ENJOYMENT OF THE PROGRAM; THAT THE PROGRAM WILL
 * MEET YOUR REQUIREMENTS; THAT OPERATION OF THE PROGRAM WILL BE
 * UNINTERRUPTED OR ERROR-FREE, OR THAT THE PROGRAM WILL BE COMPATIBLE
 * WITH THIRD PARTY SOFTWARE OR THAT ANY ERRORS IN THE PROGRAM WILL BE
 * CORRECTED.  NO ORAL OR WRITTEN ADVICE PROVIDED BY ELECTRONIC ARTS OR
 * ANY AUTHORIZED REPRESENTATIVE SHALL CREATE A WARRANTY.  SOME
 * JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF OR LIMITATIONS ON IMPLIED
 * WARRANTIES OR THE LIMITATIONS ON THE APPLICABLE STATUTORY RIGHTS OF A
 * CONSUMER, SO SOME OR ALL OF THE ABOVE EXCLUSIONS AND LIMITATIONS MAY
 * NOT APPLY TO YOU.
 */
#include "sim.h"

#ifdef MSDOS
#define filename2UNIX(name)	\
    {   char *p; for (p = name; *p; p++) if (*p == '\\') *p = '/'; }
#else
#define filename2UNIX(name)	/**/
#endif


Tcl_Interp *tk_mainInterp = NULL;
Tcl_CmdBuf buffer = NULL;
Tk_TimerToken sim_timer_token = 0;
int sim_timer_idle = 0;
int sim_timer_set = 0;
Tk_Window MainWindow;
int UpdateDelayed = 0;
int AutoScrollEdge = 16;
int AutoScrollStep = 16;
int AutoScrollDelay = 10;
Tk_TimerToken earthquake_timer_token;
int earthquake_timer_set = 0;
int earthquake_delay = 3000;
int PerformanceTiming;
double FlushTime;
int NeedRest = 0;


#define DEF_VIEW_FONT	"-Adobe-Helvetica-Bold-R-Normal-*-140-*"

Tk_ConfigSpec TileViewConfigSpecs[] = {
    {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL,
	DEF_VIEW_FONT, Tk_Offset(SimView, fontPtr), 0},
    {TK_CONFIG_STRING, "-messagevar", (char *) NULL, (char *) NULL,
	NULL, Tk_Offset(SimView, message_var), 0},
    {TK_CONFIG_PIXELS, "-width", "width", "Width",
	0, Tk_Offset(SimView, width), 0},
    {TK_CONFIG_PIXELS, "-height", "height", "Height",
	0, Tk_Offset(SimView, height), 0},
    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
	(char *) NULL, 0, 0}
};


int TileViewCmd(CLIENT_ARGS);
static void TileViewEventProc(ClientData clientData, XEvent *eventPtr);
static void DestroyTileView(ClientData clientData);
static void MicropolisTimerProc(ClientData clientData);

int SimCmd(CLIENT_ARGS);
int DoEditorCmd(CLIENT_ARGS);
int DoMapCmd(CLIENT_ARGS);
int GraphViewCmd(CLIENT_ARGS);
int DoGraphCmd(CLIENT_ARGS);
int SpriteCmd(CLIENT_ARGS);
extern int Tk_PieMenuCmd(CLIENT_ARGS);
extern int Tk_IntervalCmd(CLIENT_ARGS);


int
TileViewCmd(CLIENT_ARGS)
{
  Tk_Window tkwin = (Tk_Window) clientData;
  SimView *view;
  int viewclass;

  if (argc < 2) {
    Tcl_AppendResult(interp, "wrong # args:  should be \"",
		     argv[0], " pathName ?options?\"", (char *) NULL);
    return TCL_ERROR;
  }

  if (strcmp(argv[0], "editorview") == 0)
    viewclass = Editor_Class;
  else if (strcmp(argv[0], "mapview") == 0)
    viewclass = Map_Class;
  else {
    return TCL_ERROR;
  }

  tkwin = Tk_CreateWindowFromPath(interp, tkwin,
				  argv[1], (char *) NULL);
  if (tkwin == NULL) {
    return TCL_ERROR;
  }

  view = (SimView *)ckalloc(sizeof (SimView));

  view->tkwin = tkwin;
  view->interp = interp;
  view->flags = 0;

  if (viewclass == Editor_Class) {
    Tk_SetClass(view->tkwin, "EditorView");

    Tk_CreateEventHandler(view->tkwin,
			  VisibilityChangeMask |
			  ExposureMask |
			  StructureNotifyMask |
			  EnterWindowMask |
			  LeaveWindowMask |
			  PointerMotionMask,
			  TileViewEventProc, (ClientData) view);
    Tcl_CreateCommand(interp, Tk_PathName(view->tkwin),
		      DoEditorCmd, (ClientData) view, (void (*)(int *)) NULL);
  } else {
    Tk_SetClass(view->tkwin, "MapView");

    Tk_CreateEventHandler(view->tkwin,
			  VisibilityChangeMask |
			  ExposureMask |
			  StructureNotifyMask /* |
			  EnterWindowMask |
			  LeaveWindowMask |
			  PointerMotionMask */ ,
			  TileViewEventProc, (ClientData) view);
    Tcl_CreateCommand(interp, Tk_PathName(view->tkwin),
		      DoMapCmd, (ClientData) view, (void (*)(int *)) NULL);
  }

  Tk_MakeWindowExist(view->tkwin);

  if (getenv("XSYNCHRONIZE") != NULL) {
    XSynchronize(Tk_Display(tkwin), 1);
  }

  if (viewclass == Editor_Class) {
    InitNewView(view, "MicropolisEditor", Editor_Class, EDITOR_W, EDITOR_H);
    DoNewEditor(view);
  } else {
    InitNewView(view, "MicropolisMap", Map_Class, MAP_W, MAP_H);
    DoNewMap(view);
  }

  if (ConfigureTileView(interp, view, argc-2, argv+2, 0) != TCL_OK) {
    /* XXX: destroy view */
    Tk_DestroyWindow(view->tkwin);
    return TCL_ERROR;
  }

  switch (view->class) {
  case Editor_Class:
    break;
  case Map_Class:
    view->invalid = 1;
    view->update = 1;
    DoUpdateMap(view);
    break;
  }

  interp->result = Tk_PathName(view->tkwin);
  return TCL_OK;
}


int
ConfigureTileView(Tcl_Interp *interp, SimView *view,
	     int argc, char **argv, int flags)
{
  if (Tk_ConfigureWidget(interp, view->tkwin, TileViewConfigSpecs,
			 argc, argv, (char *) view, flags) != TCL_OK) {
    return TCL_ERROR;
  }

  if (view->class == Map_Class) {
    Tk_GeometryRequest(view->tkwin, MAP_W, MAP_H);
  } else {
    if (view->width || view->height) {
      Tk_GeometryRequest(view->tkwin, view->width, view->height);
    }
  }
  EventuallyRedrawView(view);
  return TCL_OK;
}


void
InvalidateMaps(void)
{
  SimView *view;

//fprintf(stderr, "InvalidateMaps\n");
  for (view = sim->map; view != NULL; view = view->next) {
    view->invalid = 1;
    view->skip = 0;
    EventuallyRedrawView(view);
  }
  sim_skip = 0;
}


void
InvalidateEditors(void)
{
  SimView *view;

//fprintf(stderr, "InvalidateEditors\n");
  for (view = sim->editor; view != NULL; view = view->next) {
    view->invalid = 1;
    view->skip = 0;
    EventuallyRedrawView(view);
  }
  sim_skip = 0;
}


void
RedrawMaps(void)
{
  SimView *view;

//fprintf(stderr, "RedrawMaps\n");

  for (view = sim->map; view != NULL; view = view->next) {
    view->skip = 0;
    EventuallyRedrawView(view);
  }
  sim_skip = 0;
}


void
RedrawEditors(void)
{
  SimView *view;

//fprintf(stderr, "RedrawEditors\n");

  for (view = sim->editor; view != NULL; view = view->next) {
    view->skip = 0;
    EventuallyRedrawView(view);
  }
  sim_skip = 0;
}


static void
DisplayTileView(ClientData clientData)
{
  SimView *view = (SimView *) clientData;
  Tk_Window tkwin = view->tkwin;

  view->flags &= ~VIEW_REDRAW_PENDING;
  if (view->visible && (tkwin != NULL) && Tk_IsMapped(tkwin)) {
    switch (view->class) {
    case Editor_Class:
      view->skip = 0;
      view->update = 1;
      DoUpdateEditor(view);
      break;
    case Map_Class:
//fprintf(stderr, "DisplayTileView\n");
      view->skip = 0;
      view->update = 1;
      DoUpdateMap(view);
      break;
    }
  }
}


/* comefrom:
    ConfigureTileView
    TileViewEventProc expose configure motion
    InvalidateMaps
    EraserTo
    DoSetMapState
    AddInk
    EraserTo
 */

void
EventuallyRedrawView(SimView *view)
{
  if (!(view->flags & VIEW_REDRAW_PENDING)) {
    Tk_DoWhenIdle(DisplayTileView, (ClientData) view);
    view->flags |= VIEW_REDRAW_PENDING;
  }

}


void
CancelRedrawView(SimView *view)
{
  if (view->flags & VIEW_REDRAW_PENDING) {
    Tk_CancelIdleCall(DisplayTileView, (ClientData) view);
  }
  view->flags &= ~VIEW_REDRAW_PENDING;
}


static void
TileAutoScrollProc(ClientData clientData)
{
  SimView *view = (SimView *)clientData;
  char buf[256];

  if (view->tool_mode != 0) {
    int dx = 0, dy = 0;
    int root_x, root_y, x, y;
    unsigned int key_buttons;
    Window root, child;

    XQueryPointer(Tk_Display(view->tkwin), Tk_WindowId(view->tkwin),
		  &root, &child, &root_x, &root_y, &x, &y, &key_buttons);

    if (x < AutoScrollEdge)
      dx = -AutoScrollStep;
    else if (x > (view->w_width - AutoScrollEdge))
      dx = AutoScrollStep;
    if (y < AutoScrollEdge)
      dy = -AutoScrollStep;
    else if (y > (view->w_height - AutoScrollEdge))
      dy = AutoScrollStep;

    if (dx || dy) {
      int px = view->pan_x, py = view->pan_y;

      if (view->tool_mode == -1) {
	dx = -dx; dy = -dy;
      }

      DoPanBy(view, dx, dy);
      view->tool_x += view->pan_x - px;
      view->tool_y += view->pan_y - py;
      view->auto_scroll_token =
	Tk_CreateTimerHandler(AutoScrollDelay, TileAutoScrollProc,
			      (ClientData) view);

      sprintf(buf, "UIDidPan %s %d %d", Tk_PathName(view->tkwin), x, y);
      Eval(buf);
    }
  }
}


static void
TileViewEventProc(ClientData clientData, XEvent *eventPtr)
{
  SimView *view = (SimView *) clientData;

  if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
    view->visible = 1;
    EventuallyRedrawView(view);
  } else if (eventPtr->type == MapNotify) {
    view->visible = 1;
  } else if (eventPtr->type == UnmapNotify) {
    view->visible = 0;
  } else if (eventPtr->type == VisibilityNotify) {
    if (eventPtr->xvisibility.state == VisibilityFullyObscured)
      view->visible = 0;
    else
      view->visible = 1;
  } else if (eventPtr->type == ConfigureNotify) {
    if (view->class == Editor_Class)
      DoResizeView(view,
		   eventPtr->xconfigure.width,
		   eventPtr->xconfigure.height);
    EventuallyRedrawView(view);
  } else if (eventPtr->type == DestroyNotify) {
    Tcl_DeleteCommand(view->interp, Tk_PathName(view->tkwin));
    view->tkwin = NULL;
    CancelRedrawView(view);
    Tk_EventuallyFree((ClientData) view, DestroyTileView);
  } else if ((view->class == Editor_Class) &&
	     (view->show_me != 0) &&
	     ((eventPtr->type == EnterNotify) ||
	      (eventPtr->type == LeaveNotify) ||
	      (eventPtr->type == MotionNotify))) {
    int last_x = view->tool_x, last_y = view->tool_y,
        last_showing = view->tool_showing;
    int x, y, showing;

    if (eventPtr->type == EnterNotify) {
      showing = 1;
      x = eventPtr->xcrossing.x; y = eventPtr->xcrossing.y;
    } else if (eventPtr->type == LeaveNotify) {
      showing = 0;
      x = eventPtr->xcrossing.x; y = eventPtr->xcrossing.y;
    } else {
      showing = 1;
      x = eventPtr->xmotion.x; y = eventPtr->xmotion.y;
    }

    if (view->tool_mode != 0) {

      if ((x < AutoScrollEdge) ||
	  (x > (view->w_width - AutoScrollEdge)) ||
	  (y < AutoScrollEdge) ||
	  (y > (view->w_height - AutoScrollEdge))) {
	if (!view->auto_scroll_token) {
	  view->auto_scroll_token =
	    Tk_CreateTimerHandler(AutoScrollDelay, TileAutoScrollProc,
				  (ClientData) view);
	}
      } else {
	if (view->auto_scroll_token) {
	  Tk_DeleteTimerHandler(view->auto_scroll_token);
	  view->auto_scroll_token = 0;
	}
      }
    }

    ViewToPixelCoords(view, x, y, &x, &y);
    view->tool_showing = showing;

    if (view->tool_mode != -1) {
      view->tool_x = x; view->tool_y = y;
    }

/* XXX: redraw all views showing cursor */
/* XXX: also, make sure switching tools works w/out moving */
    if (((view->tool_showing != last_showing) ||
        ((view->tool_x >> 4) != (last_x >> 4)) ||
        ((view->tool_y >> 4)  != (last_y >> 4)))) {
#if 1
      EventuallyRedrawView(view);
#else
      RedrawEditors();
#endif
    }
  }
}


static void
DestroyTileView(ClientData clientData)
{
  SimView *view = (SimView *) clientData;

  DestroyView(view);
}


void
StdinProc(ClientData clientData, int mask)
{
  char line[200];
  static int gotPartial = 0;
  char *cmd;
  int result;
  
  if (mask & TK_READABLE) {
    if (fgets(line, 200, stdin) == NULL) {
      if (!gotPartial) {
	if (sim_tty) {
	  sim_exit(0); // Just sets tkMustExit and ExitReturn
	  return;
	} else {
	  Tk_DeleteFileHandler(0);
	}
	return;
      } else {
	line[0] = 0;
      }
    }
    cmd = Tcl_AssembleCmd(buffer, line);
    if (cmd == NULL) {
      gotPartial = 1;
      return;
    }
    gotPartial = 0;
    result = Tcl_RecordAndEval(tk_mainInterp, cmd, 0);
    if (*tk_mainInterp->result != 0) {
      if ((result != TCL_OK) || sim_tty) {
	printf("%s\n", tk_mainInterp->result);
      }
    }
    if (sim_tty) {
      printf("sim:\n");
      fflush(stdout);
    }
  }
}


static void
StructureProc(ClientData clientData, XEvent *eventPtr)
{
  if (eventPtr->type == DestroyNotify) {
    MainWindow = NULL;
  }
}


#if 0
static void
DelayedMap(ClientData clientData)
{
  while (Tk_DoOneEvent(TK_IDLE_EVENTS) != 0) {
    /* Empty loop body. */
  }
  if (MainWindow == NULL) {
    return;
  }
  Tk_MapWindow(MainWindow);
}
#endif


void
DidStopPan(SimView *view)
{
  char buf[256];
  sprintf(buf, "UIDidStopPan %s", Tk_PathName(view->tkwin));
	
  Eval(buf);
}


static void
MicropolisTimerProc(ClientData clientData)
{
  sim_timer_token = NULL;
  sim_timer_set = 0;

  if (NeedRest > 0) {
    NeedRest--;
  }

  if (SimSpeed) {
    sim_loop(1);
    StartMicropolisTimer();
  } else {
    StopMicropolisTimer();
  }
}


void
ReallyStartMicropolisTimer(ClientData clientData)
{
  int delay = sim_delay;
  XDisplay *xd = XDisplays;

  StopMicropolisTimer();

  while (xd != NULL) {
    if ((NeedRest > 0) ||
	ShakeNow ||
	(xd->tkDisplay->buttonWinPtr != NULL) ||
	(xd->tkDisplay->grabWinPtr != NULL)) {
      if (ShakeNow || NeedRest) {
	if (delay < 50000) delay = 50000;
      } else {
      }
      break;
    }
    xd = xd->next;
  }

  sim_timer_token =
    Tk_CreateMicroTimerHandler(
      0,
      delay,
      MicropolisTimerProc,
      (ClientData)0);

  sim_timer_set = 1;
}


void
StartMicropolisTimer(void)
{
  if (sim_timer_idle == 0) {
    sim_timer_idle = 1;
    Tk_DoWhenIdle(
      ReallyStartMicropolisTimer,
      NULL);
  }
}


void
StopMicropolisTimer(void)
{
  if (sim_timer_idle != 0) {
    sim_timer_idle = 0;
    Tk_CancelIdleCall(
      ReallyStartMicropolisTimer,
      NULL);
  }

  if (sim_timer_set) {
    if (sim_timer_token != 0) {
      Tk_DeleteTimerHandler(sim_timer_token);
      sim_timer_token = 0;
    }
    sim_timer_set = 0;
  }
}


void
FixMicropolisTimer()
{
  if (sim_timer_set) {
    StartMicropolisTimer();
  }
}


static void
DelayedUpdate(ClientData clientData)
{
//fprintf(stderr, "DelayedUpdate\n");
  UpdateDelayed = 0;
  sim_skip = 0;
  sim_update();
}


void
Kick(void)
{
  if (!UpdateDelayed) {
    UpdateDelayed = 1;
    Tk_DoWhenIdle(DelayedUpdate, (ClientData) NULL);
  }
}


void
StopEarthquake(void)
{
  ShakeNow = 0;
  if (earthquake_timer_set) {
    Tk_DeleteTimerHandler(earthquake_timer_token);
  }
  earthquake_timer_set = 0;
}


void
DoEarthQuake(void)
{
  MakeSound("city", "Explosion-Low");
  Eval("UIEarthQuake");
  ShakeNow++;
  if (earthquake_timer_set) {
    Tk_DeleteTimerHandler(earthquake_timer_token);
  }
  Tk_CreateTimerHandler(earthquake_delay, (void (*)(int *))StopEarthquake, (ClientData) 0);
  earthquake_timer_set = 1;
}


void
StopToolkit(void)
{
  if (tk_mainInterp != NULL) {
    Eval("catch {DoStopMicropolis}");
  }
}


int
Eval(char *buf)
{
  int result = Tcl_Eval(tk_mainInterp, buf, 0, (char **) NULL);
  if (result != TCL_OK) {
    char *errorinfo = Tcl_GetVar(tk_mainInterp, "errorInfo",
				 TCL_GLOBAL_ONLY);
    if (errorinfo == NULL) errorinfo = "<no backtrace>";
    fprintf(stderr, "Micropolis: error in TCL code: %s\n%s\n",
	    tk_mainInterp->result, errorinfo);
  }
  return (result);
}


void tk_main(void)
{
  char initCmd[256];
  Tk_3DBorder border;

  tk_mainInterp = Tcl_CreateExtendedInterp();

#if 0
  /* XXX: Figure out Extended TCL */
  tclAppName     = "Wish";
  tclAppLongname = "Wish - Tk Shell";
  tclAppVersion  = TK_VERSION;
  Tcl_ShellEnvInit (interp, TCLSH_ABORT_STARTUP_ERR,
                    name,
                    0, NULL,           /* argv var already set  */
                    fileName == NULL,  /* interactive?          */
                    NULL);             /* Standard default file */
#endif

  MainWindow = Tk_CreateMainWindow(tk_mainInterp, FirstDisplay, "Micropolis");
  if (MainWindow == NULL) {
    fprintf(stderr, "%s\n", tk_mainInterp->result);
    sim_really_exit(1); // Just sets tkMustExit and ExitReturn
  }
  Tk_SetClass(MainWindow, "Tk");
  Tk_CreateEventHandler(MainWindow, StructureNotifyMask,
			StructureProc, (ClientData) NULL);
/*  Tk_DoWhenIdle(DelayedMap, (ClientData) NULL); */

  Tk_GeometryRequest(MainWindow, 256, 256);
  border = Tk_Get3DBorder(tk_mainInterp, MainWindow, None, "gray75");
  if (border == NULL) {
    Tcl_SetResult(tk_mainInterp, (char *) NULL, TCL_STATIC);
    Tk_SetWindowBackground(MainWindow,
			   WhitePixelOfScreen(Tk_Screen(MainWindow)));
  } else {
    Tk_SetBackgroundFromBorder(MainWindow, border);
  }
  XSetForeground(Tk_Display(MainWindow),
		 DefaultGCOfScreen(Tk_Screen(MainWindow)),
		 BlackPixelOfScreen(Tk_Screen(MainWindow)));

  sim_command_init();
  map_command_init();
  editor_command_init();
  graph_command_init();
  date_command_init();
  sprite_command_init();
  sound_command_init();

#ifdef CAM
  cam_command_init();
#endif

  Tcl_CreateCommand(tk_mainInterp, "piemenu", Tk_PieMenuCmd,
		    (ClientData)MainWindow, (void (*)(int *)) NULL);
  Tcl_CreateCommand(tk_mainInterp, "interval", Tk_IntervalCmd,
		    (ClientData)MainWindow, (void (*)(int *)) NULL);

  sim = MakeNewSim();

  sprintf(initCmd, "source %s/micropolis.tcl", ResourceDir);
  filename2UNIX(initCmd);
  if (Eval(initCmd)) {
    sim_exit(1); // Just sets tkMustExit and ExitReturn
    goto bail;
  }

  sim_init();

  buffer = Tcl_CreateCmdBuf();

  if (sim_tty) {
    Tk_CreateFileHandler(0, TK_READABLE, StdinProc, (ClientData) 0);
  }

  { char buf[1024];

    sprintf(buf, "UIStartMicropolis {%s} {%s} {%s}",
	    HomeDir, ResourceDir, HostName);
    filename2UNIX(buf);
    if (Eval(buf) != TCL_OK) {
      sim_exit(1); // Just sets tkMustExit and ExitReturn
      goto bail;
    }
  }

  if (sim_tty) {
    printf("sim:\n");
  }
  fflush(stdout);

  Tk_MainLoop();

  sim_really_exit(0);

 bail:

  if (buffer != NULL) {
    Tcl_DeleteCmdBuf(buffer);
  }

  Tcl_DeleteInterp(tk_mainInterp);
}

