/* w_map.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"


Tcl_HashTable MapCmds;


extern Tk_ConfigSpec TileViewConfigSpecs[];


Ink *NewInk();
void DrawMapInk(SimView *view);
void WireDrawMap(SimView *view);
void DrawMapEditorViews(SimView *view);


int
MapCmdconfigure(VIEW_ARGS)
{
  int result = TCL_OK;

  if (argc == 2) {
    result = Tk_ConfigureInfo(interp, view->tkwin, TileViewConfigSpecs,
			      (char *) view, (char *) NULL, 0);
  } else if (argc == 3) {
    result = Tk_ConfigureInfo(interp, view->tkwin, TileViewConfigSpecs,
			      (char *) view, argv[2], 0);
  } else {
    result = ConfigureTileView(interp, view, argc-2, argv+2,
			       TK_CONFIG_ARGV_ONLY);
  }
  return result;
}

int
MapCmdposition(VIEW_ARGS)
{
  if ((argc != 2) && (argc != 4)) {
    return TCL_ERROR;
  }
  if (argc == 4) {
    if ((Tcl_GetInt(interp, argv[2], &view->w_x) != TCL_OK)
	|| (Tcl_GetInt(interp, argv[3], &view->w_y) != TCL_OK)) {
      return TCL_ERROR;
    }
  }
  sprintf(interp->result, "%d %d", view->w_x, view->w_y);
  return TCL_OK;
}

int
MapCmdsize(VIEW_ARGS)
{
  if ((argc != 2) && (argc != 4)) {
    return TCL_ERROR;
  }
  if (argc == 4) {
    int w, h;

    if (Tcl_GetInt(interp, argv[2], &w) != TCL_OK) {
      return TCL_ERROR;
    }
    if (Tcl_GetInt(interp, argv[3], &h) != TCL_OK) {
      return TCL_ERROR;
    }
    view->w_width = w;
    view->w_height = h;
  }
  sprintf(interp->result, "%d %d", view->w_width, view->w_height);
  return TCL_OK;
}

int
MapCmdMapState(VIEW_ARGS)
{
  int state;

  if ((argc != 2) && (argc != 3)) {
    return TCL_ERROR;
  }

  if (argc == 3) {
    if ((Tcl_GetInt(interp, argv[2], &state) != TCL_OK) ||
	(state < 0) || (state >= NMAPS)) {
      return TCL_ERROR;
    }

    DoSetMapState(view, state); Kick();
  }

  sprintf(interp->result, "%d", view->map_state);
  return TCL_OK;
}

int
MapCmdShowEditors(VIEW_ARGS)
{
  int val;

  if ((argc != 2) && (argc != 3)) {
    return TCL_ERROR;
  }

  if (argc == 3) {
    if (Tcl_GetInt(interp, argv[2], &val) != TCL_OK) {
      return TCL_ERROR;
    }

    view->show_editors = val;
  }

  sprintf(interp->result, "%d", view->show_editors);
  return TCL_OK;
}

int
MapCmdPanStart(VIEW_ARGS)
{
  int x, y, left, right, top, bottom, width, height;
  SimView *ed;

  if (argc != 4) {
    return TCL_ERROR;
  }

  if ((Tcl_GetInt(interp, argv[2], &x) != TCL_OK) ||
      (Tcl_GetInt(interp, argv[3], &y) != TCL_OK)) {
    return TCL_ERROR;
  }

  for (ed = sim->editor; ed != NULL; ed = ed->next) {
    if ((ed->x != view->x) || (ed->show_me == 0))
      continue;

    width = ed->w_width;
    height = ed->w_height;
    left = ed->pan_x - (width / 2);
    top = ed->pan_y - (height / 2);
    right = left + width;
    bottom = top + height;

    left = left * 3 / 16 - 4;
    top = top * 3 / 16 - 4;
    right = right * 3 / 16 + 4;
    bottom = bottom * 3 / 16 + 4;

    if ((x >= left) && (x <= right) &&
	(y >= top) && (y <= bottom)) {
      goto gotit;
    }
  }

gotit:
  view->last_x = x;
  view->last_y = y;
  view->track_info = (char *)ed;
  return TCL_OK;
}

int
MapCmdPanTo(VIEW_ARGS)
{
  int x, y, dx, dy;
  SimView *ed;

  if (argc != 4) {
    return TCL_ERROR;
  }

  if ((Tcl_GetInt(interp, argv[2], &x) != TCL_OK) ||
      (Tcl_GetInt(interp, argv[3], &y) != TCL_OK)) {
    return TCL_ERROR;
  }

  if ((ed = (SimView *)view->track_info) != NULL) {
    dx = x - view->last_x;
    dy = y - view->last_y;
    if (dx || dy) {
      view->last_x = x;
      view->last_y = y;
      dx = dx * 16 / 3;
      dy = dy * 16 / 3;

      ed->skip = 0;
      DoPanBy(ed, dx, dy); Kick();
    }
  }
  return TCL_OK;
}

int
MapCmdVisible(VIEW_ARGS)
{
  int visible;

  if ((argc != 2) && (argc != 3)) {
    return TCL_ERROR;
  }

  if (argc == 3) {
    if ((Tcl_GetInt(interp, argv[2], &visible) != TCL_OK) ||
	(visible < 0) || (visible > 1)) {
      return TCL_ERROR;
    }

    visible = visible && Tk_IsMapped(view->tkwin);
    view->visible = visible;
  }

  sprintf(interp->result, "%d", view->visible);
  return TCL_OK;
}

int
MapCmdViewAt(VIEW_ARGS)
{
  int x, y;

  if (argc != 4) {
    return TCL_ERROR;
  }

  if ((Tcl_GetInt(interp, argv[2], &x) != TCL_OK) ||
      (x < 0) || (x >= WORLD_X) ||
      (Tcl_GetInt(interp, argv[3], &y) != TCL_OK) ||
      (y < 0) || (y >= WORLD_Y)) {
    return TCL_ERROR;
  }

  sprintf(interp->result, "Sorry Not Implemented Yet"); /* XXX */
  return TCL_OK;
}


void
map_command_init(void)
{
  extern int TileViewCmd(CLIENT_ARGS);

  Tcl_CreateCommand(tk_mainInterp, "mapview", TileViewCmd,
		    (ClientData)MainWindow, (void (*)()) NULL);

  Tcl_InitHashTable(&MapCmds, TCL_STRING_KEYS);

#define MAP_CMD(name) HASHED_CMD(Map, name)

  MAP_CMD(configure);
  MAP_CMD(position);
  MAP_CMD(size);
  MAP_CMD(MapState);
  MAP_CMD(ShowEditors);
  MAP_CMD(PanStart);
  MAP_CMD(PanTo);
  MAP_CMD(Visible);
  MAP_CMD(ViewAt);
}


int
DoMapCmd(CLIENT_ARGS)
{
  SimView *view = (SimView *) clientData;
  Tcl_HashEntry *ent;
  int result = TCL_OK;
  int (*cmd)();

  if (argc < 2) {
    return TCL_ERROR;
  }

  if ((ent = Tcl_FindHashEntry(&MapCmds, argv[1]))) {
    cmd = (int (*)())ent->clientData;
    Tk_Preserve((ClientData) view);
    result = cmd(view, interp, argc, argv);
    Tk_Release((ClientData) view);
  } else {
    Tcl_AppendResult(interp, "unknown command name: \"",
		     argv[0], " ", argv[1], "\".", (char *) NULL);
    result = TCL_ERROR;
  }
  return result;
}


/*************************************************************************/

void
DoNewMap(SimView *view)
{
  sim->maps++; view->next = sim->map; sim->map = view;
/* NewMap = 1; */
  view->invalid = 1;
}


int DoUpdateMap(SimView *view)
{
  int dx, dy, i;

  view->updates++;

// fprintf(stderr, "UpdateMaps sim_skips %d skips %d skip %d visible %d\n", sim_skips, view->skips, view->skip, view->visible);

  if (!view->visible) {
    return 0;
  }

  if ((!ShakeNow) &&
      (!view->update) &&
      (sim_skips ||
       view->skips)) {
    if (sim_skips) {
      if (sim_skip > 0) {
	return 0;
      }
    } else {
      if (view->skip > 0) {
	--view->skip;
	return 0;
      } else {
	view->skip = view->skips;
      }
    }
  }

  view->update = 0;
  view->skip = 0;

//  view->invalid = 1;

  if (view->invalid || NewMap || ShakeNow) {

    view->invalid = 0;

    switch (view->type) {

    case X_Mem_View:
      MemDrawMap(view);
      break;

    case X_Wire_View:
      WireDrawMap(view);
      break;
    }

  }

  /* XXX: don't do this stuff if just redrawing overlay */

  for (dx = dy = i = 0; i < ShakeNow; i++) {
    dx += Rand(16) - 8;
    dy += Rand(16) - 8;
  }

  XCopyArea(view->x->dpy, view->pixmap, view->pixmap2, view->x->gc,
	    dx, dy, view->w_width, view->w_height, 0, 0);
  DrawMapInk(view);

  /* XXX: do this if just redrawing overlay */

  XCopyArea(view->x->dpy, view->pixmap2,
	    Tk_WindowId(view->tkwin), view->x->gc,
	    0, 0, view->w_width, view->w_height, 0, 0);

  if (view->show_editors) {
    DrawMapEditorViews(view);
  }

  return 1;
}


void
DrawMapEditorViews(SimView *view)
{
  Pixmap pm = Tk_WindowId(view->tkwin);
  struct SimView *ed;
  int left, right, top, bottom, width, height;

  XSetLineAttributes(view->x->dpy, view->x->gc, 1,
		     LineSolid, CapButt, JoinBevel);

  for (ed = sim->editor; ed != NULL; ed = ed->next) {
    if ((ed->x != view->x) || (ed->show_me == 0))
      continue;

    width = ed->w_width;
    height = ed->w_height;
    left = ed->pan_x - (width / 2);
    top = ed->pan_y - (height / 2);
    right = left + width;
    bottom = top + height;

    left = left * 3 / 16;
    top = top * 3 / 16;
    right = right * 3 / 16;
    bottom = bottom * 3 / 16;
    width = right - left;
    height = bottom - top;

    XSetForeground(view->x->dpy, view->x->gc,
		   view->pixels[COLOR_WHITE]);
    XDrawRectangle(view->x->dpy, pm, view->x->gc,
		   left - 3, top - 3, width + 3, height + 3);

    XSetForeground(view->x->dpy, view->x->gc,
		   view->pixels[COLOR_BLACK]);
    XDrawRectangle(view->x->dpy, pm, view->x->gc,
		   left - 1, top - 1, width + 3, height + 3);

    XSetForeground(view->x->dpy, view->x->gc,
		   view->pixels[COLOR_YELLOW]);
    XDrawRectangle(view->x->dpy, pm, view->x->gc,
		   left - 2, top - 2, width + 3, height + 3);
  }
}


/* 
 * Sending the whole image is 108108 bytes.
 * Sending points is 4.4 bytes per point.
 * One image is as big as 24570 points.
 * But we have to sort these dang things.
 */

#define MAX_PIX 256
int max_pix = MAX_PIX;

struct Pix {
  long color;
  short x, y;
};

struct Pix pix[MAX_PIX];


int
CompareColor(struct Pix *p1, struct Pix *p2)
{
  register char c1 = p1->color, c2 = p2->color;

  if (c1 == c2)
    return (0);
  return ((c1 < c2) ? -1 : 1);
}


void
WireDrawMap(SimView *view)
{
  int different, x, y;
  unsigned char *old, *new;
#if 0
  XPoint *points;
  int i, last, pts;
#endif

  if (!view->x->color) {
    MemDrawMap(view);
    return;
  }

  memcpy(view->other_data, view->data, view->line_bytes * view->m_height); /* XXX: handle depth */
  MemDrawMap(view);

  old = view->other_data; new = view->data; /* XXX: handle depth */
  different = 0;

  /* Scan the pixels that have changed */
  for (y = 0; y < view->m_height; y++) {
    for (x = 0; x < view->m_width; x++) {
      if (old[x] != new[x]) {
	if (different >= max_pix) {
	  /* Wow, lots of the pixels have changed.
	     Maybe we ought to just do it the hard way. */ 
	  XPutImage(view->x->dpy, view->pixmap, view->x->gc, view->image, 
		    0, 0, 0, 0, view->m_width, view->m_height);
	  return;
	}
	pix[different].color = new[x];
	pix[different].x = x;
	pix[different].y = y;
	different++;
      }
    }
    old += view->line_bytes; new += view->line_bytes; /* XXX: handle depth */
  }

  /* Whew, the images are identical! */
  if (different == 0)
    return;

#if 1

  /* Always draw the whole pixmap, for now. */
  XPutImage(view->x->dpy, view->pixmap, view->x->gc, view->image, 
	    0, 0, 0, 0, view->m_width, view->m_height);

#else

  /* TODO: Fix this. I disabled this incremental drawing code for now since it seems to be buggy. */

  /* Sort the changed pixels by their color */
  qsort(pix, different, sizeof (struct Pix), (int (*)())CompareColor);

  /* Draw the points of each color that have changed */
  points = (XPoint *)malloc(sizeof (XPoint) * different);
  last = 0; pts = 0;
  for (i = 0; i <= different; i++) {
    if ((i == different) ||
	(pix[i].color != pix[last].color)) {
      XSetForeground(view->x->dpy, view->x->gc, pix[last].color);
      XDrawPoints(view->x->dpy, view->pixmap, view->x->gc,
		  points, pts, CoordModeOrigin);
      if (i == different)
	break;
      pts = 0;
      last = i;
    }
    points[pts].x = pix[i].x;
    points[pts].y = pix[i].y;
    pts++;
  }
  free(points);

#endif

}


void
DrawMapInk(SimView *view)
{
  Pixmap pm = view->pixmap2;
  Ink *ink, *ink2 = NewInk();
  int i, X, Y, x, y;

  XSetLineAttributes(view->x->dpy, view->x->gc, 0,
		     LineSolid, CapButt, JoinBevel);

  for (ink = sim->overlay; ink != NULL; ink = ink->next) {
    X = ink->x; x = (X * 3) >>4;
    Y = ink->y; y = (Y * 3) >>4;
    
    if (ink->length <= 1) {
      XSetForeground(view->x->dpy, view->x->gc,
		     view->pixels[ink->color]);
      XFillArc(view->x->dpy, pm, view->x->gc,
	       ink->x - 1, ink->y - 1, 1, 1, 0, 360 * 64);
    } else {
      StartInk(ink2, x, y);

      for (i = 1; i < ink->length; i++) {
	X += ink->points[i].x; x = (X * 3) >>4;
	Y += ink->points[i].y; y = (Y * 3) >>4;
	AddInk(ink2, x, y);
      }

      XSetForeground(view->x->dpy, view->x->gc,
		     view->pixels[ink->color]);
      XDrawLines(view->x->dpy, pm, view->x->gc,
		 ink2->points, ink2->length, CoordModePrevious);
    }
  }

  FreeInk(ink2);
}


