/*
    avwm 0.4 / Amiga Virtual Workbench Manager
    5/2/93 Bernhard Fastenrath
    Public Domain
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <dos.h>
#include <exec/types.h>
#include <devices/timer.h>
#include <graphics/rastport.h>
#include <intuition/intuitionbase.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/graphics.h>
#include <proto/intuition.h>

#include "callbacks.h"
#include "avwm.h"
#include "wbwm.h"

const char		*Version = VERSION;

struct IntuitionBase	*IntuitionBase = NULL;
struct timerequest	*TimerIO = NULL;
struct MsgPort		*TimerPort = NULL;
struct Window		*Win = NULL,
			*LastActiveWindow = NULL;
struct RastPort		*Rp;
short			Screenwidth, Screenheight,
			VisWidth, VisHeight;
struct List		VWinA[2];
int			Options = OPT_NO_SETF | OPT_MOVE_SCREEN,
			VWIndex = 0, Dx, Dy,
			Action = ACT_NONE, Area = 0,
			Seconds = 2, MSeconds = 0;
			Offset = 11;
ULONG			WMSigBit = -1, WMSig;
struct vWindow		*Cvw = NULL;
char			*PubScreenName = "Workbench";

int
main (int argc, char *argv[])
{
  struct Screen *Lock;
  struct NewWindow NewWin;
  int argn = 1;
  char optc;

  while (argn < argc)
    if (argopt (argc, argv, "OcS", &argn, &optc))
      switch (optc) {
	case 'O':
	  Offset = atoi (argv[argn-1] + (argv[argn-1][0] == '-' ? 2 : 0));
	  break;
        case 'h':
	  printf ("Amiga Virtual Workbench Manager %d.%d\n", VER_NUM, REV_NUM);
	  printf ("Options: -t don't open timer device.\n");
	  printf ("         -c <n> check windows every n seconds (default: 2s).\n");
	  printf ("         -s use SetFunction () (by default disabled now).\n");
	  printf ("         -m use MoveWindow (expects double v. screen size) instead of\n");
	  printf ("            MoveScreen (expects triple h. and double v. screen size).\n");
	  printf ("         -S Name of public screen (default: Workbench).\n");
	  exit (0);
	case 'c':
          Seconds = atoi (argv[argn-1] + (argv[argn-1][0] == '-' ? 2 : 0));
	  if (Seconds == 0) MSeconds = 250;
	  break;
        case 't':
	  Options |= OPT_NO_TIMER;
	  break;
        case 's':
	  Options &= ~OPT_NO_SETF;
	  break;
	case 'm':
	  Options &= ~OPT_MOVE_SCREEN;
	  break;
	case 'S':
          PubScreenName = argv[argn-1] + (argv[argn-1][0] == '-' ? 2 : 0);
	  break;
        default:
	  printf ("Invalid Option: \"-%c\" (try '%s -h')\n", optc, argv[0]);
	  exit (20);
      }
    else
      argn++;

  InitListHeader ((struct MinList *) &VWinA[0]);
  InitListHeader ((struct MinList *) &VWinA[1]);

  if (!(IntuitionBase = (struct IntuitionBase *) OpenLibrary ("intuition.library", 37L))) {
    printf ("%s: Can't open intuition library 37.x.\n", Version);
    exit_avwm (1);
  }
  if ((Lock = LockPubScreen (PubScreenName)) == NULL) {
    printf ("%s: Can't lock public screen '%s'.\n", Version, PubScreenName);
    exit_avwm (1);
  }
  Screenwidth = Lock -> Width;
  Screenheight = Lock -> Height;

  if (Options & OPT_MOVE_SCREEN) {
    VisWidth = Screenwidth / 3;
    VisHeight = Screenheight / 2;
  }
  else {
    VisWidth = Screenwidth;
    VisHeight = Screenheight / 2;
  }
  NewWin.LeftEdge = VisWidth - AVWM_WIDTH;
  NewWin.TopEdge = 12;
  NewWin.Width = AVWM_WIDTH;
  NewWin.Height = AVWM_HEIGHT;
  NewWin.DetailPen = 0;
  NewWin.BlockPen = 1;
  NewWin.Title = "Virtual Desktop";
  NewWin.Flags = WINDOWDRAG | WINDOWDEPTH | WINDOWCLOSE | SMART_REFRESH |
		 GIMMEZEROZERO | ACTIVATE | RMBTRAP | REPORTMOUSE;
  NewWin.IDCMPFlags = MOUSEBUTTONS | CLOSEWINDOW | REFRESHWINDOW | MOUSEMOVE;
  NewWin.Type = PUBLICSCREEN;
  NewWin.FirstGadget = NULL;
  NewWin.CheckMark = NULL;
  NewWin.Screen = Lock;
  NewWin.BitMap = NULL;
  NewWin.MinWidth = 0;
  NewWin.MinHeight = 0;
  NewWin.MaxWidth = 0;
  NewWin.MaxHeight = 0;

  if (!(Win = OpenWindow (&NewWin))) {
    UnlockPubScreen (NULL, Lock);
    printf ("%s: Can't open window.\n", Version);
    exit_avwm (20);
  }
  UnlockPubScreen (NULL, Lock);
  Rp = Win -> RPort;

  AddCallback (Win, close_window, CLOSEWINDOW);
  AddCallback (Win, mouse_event, MOUSEBUTTONS);
  AddCallback (Win, refresh_window, REFRESHWINDOW);
  AddCallback (Win, mouse_move, MOUSEMOVE);

  if ((Options & OPT_NO_TIMER) == 0)
  {
    init_timer ();
    AddPortCallback (TimerPort, (callback) timer_event, 0);
  }

  if ((Options & OPT_NO_SETF) == 0)
  {
    if ((WMSigBit = AllocSignal (-1)) == -1)
    {
      printf ("%s: Can't allocate Signal.\n", Version);
      exit_avwm (20);
    }
    start_window_monitor ();
    WMSig = 1L << WMSigBit;
    SetSignalCallback (WMSig, (callback) workbench_monitor_event);
  }
  refresh_graphic ();

  AppMainLoop ();

  exit_avwm (0);
}

void
exit_avwm (int code)
{
  struct Node *node;

  stop_window_monitor ();
  while (node = RemTail (&VWinA[0]))
    free (node);
  while (node = RemTail (&VWinA[1]))
    free (node);
  if (WMSigBit != -1) FreeSignal (WMSigBit);
  if (Win) CloseWindow (Win);
  if ((Options & OPT_NO_SETF) == 0) Delay (100);
  if (TimerIO) {
    AbortIO ((struct IORequest *) TimerIO);
    WaitIO ((struct IORequest *) TimerIO);
    CloseDevice ((struct IORequest *) TimerIO);
    DeleteExtIO ((struct IORequest *) TimerIO, sizeof (struct timerequest));
  }
  if (TimerPort) DeletePort (TimerPort);
  RemoveAllCallbacks ();
  if (IntuitionBase) CloseLibrary ((struct Library *) IntuitionBase);
  exit (code);
}

void
init_timer ()
{
  int err;

  if (!(TimerPort = CreatePort (0, 0)))
  {
    printf ("%s: Can't create message port.\n", Version);
    exit_avwm (1);
  }
  if (!(TimerIO = (struct timerequest *) CreateExtIO (TimerPort, sizeof (struct timerequest))))
  {
    printf ("%s: Out of memory.\n", Version);
    exit_avwm (1);
  }
  if (err = OpenDevice (TIMERNAME, UNIT_VBLANK, (struct IORequest *) TimerIO, 0))
  {
    printf ("%s: Can't open timer.device (code %d).\n", Version, err);
    exit_avwm (1);
  }
  TimerIO -> tr_time.tv_secs = Seconds;
  TimerIO -> tr_time.tv_micro = MSeconds;
  TimerIO -> tr_node.io_Command = TR_ADDREQUEST;
  SendIO ((struct IORequest *) TimerIO);
}

struct Window *
assert_window (struct Window *search)
{
  struct Window *window;

  for (window = Win -> WScreen -> FirstWindow; window; window = window -> NextWindow)
    if (search == window) return window;

  return NULL;
}

void
draw_box (short color, short x1, short y1, short x2, short y2, struct RastPort *rp)
{
  SetAPen (rp, GREY);

  RectFill (rp, x1, y1, x2, y2);

  SetAPen (rp, color);

  Move (rp, x1, y1);
  Draw (rp, x2, y1);
  Draw (rp, x2, y2);
  Draw (rp, x1, y2);
  Draw (rp, x1, y1);
}

void
draw_frame (short color, short x1, short y1, short x2, short y2, struct RastPort *rp)
{
  SetAPen (rp, color);

  Move (rp, x1, y1);
  Draw (rp, x2, y1);
  Draw (rp, x2, y2);
  Draw (rp, x1, y2);
  Draw (rp, x1, y1);
}

void
draw_vwindow (int color, struct vWindow *vw)
{
  int x, y;

  x = vw -> sx + AREA_X (vw -> area) * SUB_WIDTH;
  y = vw -> sy + AREA_Y (vw -> area) * SUB_HEIGHT;

  draw_frame (color, x, y, x + vw -> sw, y + vw -> sh, Rp);
}

void
redraw_graphic ()
{
  struct vWindow *vw;
  struct List *vwl;
  short color;
  int x, y;

  vwl = &VWinA[VWIndex];

  SetDrMd (Rp, JAM2);
  SetAPen (Rp, GREY);

  RectFill (Rp, 0, 0, AVWM_WIDTH, AVWM_HEIGHT);

  SetAPen (Rp, BLACK);

  Move (Rp, 0, SUB_HEIGHT);    Draw (Rp, SUB_WIDTH * 3, SUB_HEIGHT);
  Move (Rp, SUB_WIDTH, 0);     Draw (Rp, SUB_WIDTH, SUB_HEIGHT * 2);
  Move (Rp, SUB_WIDTH * 2, 0); Draw (Rp, SUB_WIDTH * 2, SUB_HEIGHT * 2);

  for (vw = (struct vWindow *) vwl -> lh_TailPred;
       vw -> node.ln_Pred; vw = (struct vWindow *) vw -> node.ln_Pred)
  {
    if (IntuitionBase -> ActiveWindow == vw -> window)
       color = BLUE;
    else
       color = BLACK;

    x = vw -> sx + AREA_X (vw -> area) * SUB_WIDTH;
    y = vw -> sy + AREA_Y (vw -> area) * SUB_HEIGHT;

    draw_box (color, x, y, x + vw -> sw, y + vw -> sh, Rp);
  }
}

void
refresh_graphic ()
{
  struct Window *window;
  struct vWindow *vw;
  short change = 0;
  BYTE pos;
  struct List *vwl, *vwl2;
  struct Layer *layer;
  int area;

  Forbid ();

  if (IntuitionBase -> FirstScreen != Win -> WScreen
      && IntuitionBase -> ActiveScreen != Win -> WScreen)
  {
    Permit ();
    return;	/* Screen not in use, event ignored */
  }
  vwl = &VWinA[VWIndex];
  VWIndex = 1 - VWIndex;
  vwl2 = &VWinA[VWIndex];

  if (LastActiveWindow != IntuitionBase -> ActiveWindow)
  {
    LastActiveWindow = IntuitionBase -> ActiveWindow;
    change = 1;
  }

  /* Set the change status of all windows to "unknown" */
  for (vw = (struct vWindow *) vwl -> lh_Head;
       vw -> node.ln_Succ; vw = (struct vWindow *) vw -> node.ln_Succ)
  {
    vw -> changed = -1;
  }

  /* Find the front layer */
  for (layer = Win -> WScreen -> FirstWindow -> WLayer;
       layer -> front; layer = layer -> front);

  /* Sort the virtual window list like the layers of the screen
     and check if size, position or priority of any window changed
  */
  for (pos = HIGH_PRI; layer -> back; pos--, layer = layer -> back)
  {
    if (!(window = layer -> Window)) continue;

    for (vw = (struct vWindow *) vwl -> lh_Head;
	 vw -> node.ln_Succ; vw = (struct vWindow *) vw -> node.ln_Succ)
    {
      if (vw -> window == window)
      {
        if (vw -> changed != -1) break;

	vw -> changed = 0;	/* Window has moved? */

	if (vw -> ox != window -> LeftEdge)
	  { vw -> ox = window -> LeftEdge; change = vw -> changed = 1; }
	if (vw -> oy != window -> TopEdge)  
	  { vw -> oy = window -> TopEdge;  change = vw -> changed = 1; }
	if (vw -> ow != window -> Width)    
	  { vw -> ow = window -> Width;    change = vw -> changed = 1; }
	if (vw -> oh != window -> Height)   
	  { vw -> oh = window -> Height;   change = vw -> changed = 1; }

	if (vw -> changed)
	{
	  /* Window has moved into another area? */
	  area = AREA (window -> LeftEdge, window -> TopEdge);

	  /* Don't change the area if MoveWindow is used
	     and the window is invisible
	     because it will always be found in the lower
	     "invisible" part of the screen
	  */
          if (Options & OPT_MOVE_SCREEN)
	    vw -> area = area;
	  else if (area == 0)
	    vw -> area = Area;

#ifdef DEBUG
	  printf ("Window %d moved to %d,%d in area %d.\n", vw -> window, vw -> ox, vw -> oy, area);
#endif
	}

	if (vw -> node.ln_Pri != pos) /* Window order has changed */
	{
#ifdef DEBUG
	  printf ("Window order has changed. (area %d) [was: %d, is: %d]\n", vw->area, vw->node.ln_Pri, pos);
#endif
	  vw -> node.ln_Pri = pos;
	  change = 1;
	}
	break;
      }
    }

    if (!vw -> node.ln_Succ) /* New window has been opened */
    {
      if (!(vw = Malloc (struct vWindow))) {
	Permit ();
	printf ("%s: Memory low!.\n", Version);
	exit_avwm (1);
      }
      vw -> area = AREA (window -> LeftEdge, window -> TopEdge);

      /* Set the area to the area currently displayed in area 0 */
      if (!(Options & OPT_MOVE_SCREEN) && vw -> area == 0)
        vw -> area = Area;
 
#ifdef DEBUG
      printf ("Adding window to area %d.\n", vw -> area);
#endif
      vw -> changed = 2;
      vw -> window = window;
      vw -> node.ln_Pri = pos;
      vw -> ox = window -> LeftEdge;
      vw -> oy = window -> TopEdge;
      vw -> ow = window -> Width;
      vw -> oh = window -> Height;

      AddHead (vwl, (struct Node *) vw);

      change = 1;
    }
  } 
  Permit ();

  while (vw = (struct vWindow *) RemTail (vwl))
  {
    if (vw -> changed == -1) { /* Window has been closed */
      free (vw);
      change = 1;
#ifdef DEBUG
      printf ("Window removed.\n");
#endif
    }
    else /* If the window order is unchanged this is just an AddHead() */
      Enqueue (vwl2, (struct Node *) vw);
  }

  if (change)	/* Don't draw if nothing has happened */
  {
    if (Options & OPT_MOVE_SCREEN)
      for (vw = (struct vWindow *) vwl2 -> lh_TailPred;
	   vw -> node.ln_Pred; vw = (struct vWindow *) vw -> node.ln_Pred)
      {
        vw -> sx = (vw -> ox % VisWidth)  * SUB_WIDTH  / VisWidth;
        vw -> sy = (vw -> oy % VisHeight) * SUB_HEIGHT / VisHeight;
        vw -> sw = vw -> ow * SUB_WIDTH  / VisWidth;
        vw -> sh = vw -> oh * SUB_HEIGHT / VisHeight;
      }
    else
      for (vw = (struct vWindow *) vwl2 -> lh_TailPred;
	   vw -> node.ln_Pred; vw = (struct vWindow *) vw -> node.ln_Pred)
      {
        /* ignore window's position (but not size)
           if it is invisible
        */
	if (vw -> area == Area || vw -> changed == 2)
	{
          vw -> sx = (vw -> ox % VisWidth)  * SUB_WIDTH  / VisWidth;
          vw -> sy = (vw -> oy % VisHeight) * SUB_HEIGHT / VisHeight;
        }
        vw -> sw = vw -> ow * SUB_WIDTH  / VisWidth;
        vw -> sh = vw -> oh * SUB_HEIGHT / VisHeight;
      }
    redraw_graphic ();
  }
}

struct vWindow *
find_window (int x, int y)
{
  struct vWindow *vw;
  struct List *vwl;
  int area;

  vwl = &VWinA[VWIndex];

  area = vAREA (x, y); x %= SUB_WIDTH; y %= SUB_HEIGHT;

  for (vw = (struct vWindow *) vwl -> lh_Head;
       vw -> node.ln_Succ; vw = (struct vWindow *) vw -> node.ln_Succ)
  {
    if (x > vw->sx && y > vw->sy && x < vw->sw + vw->sx && y < vw->sh + vw->sy
	&& area == vw -> area)
      return vw;
  }
  return NULL;
}

int
close_window (struct IntuiMessage *msg, struct Callback *cb)
{
  exit_avwm (0);
  return 0;
}

int
refresh_window (struct IntuiMessage *msg, struct Callback *cb)
{
  return 0;
}

int
timer_event (struct timerequest *msg, struct Callback *cb)
{
  TimerIO -> tr_time.tv_secs = Seconds;
  TimerIO -> tr_time.tv_micro = MSeconds;
  TimerIO -> tr_node.io_Command = TR_ADDREQUEST;
  SendIO ((struct IORequest *) TimerIO);

  if (!Action) refresh_graphic ();
  return 0;
}

void
workbench_monitor_event (int signals)
{
#ifdef xDEBUG
  printf ("Monitor Event (signal: %d).\n", signals);
#endif
  if (!Action) refresh_graphic ();
#ifdef xDEBUG
  else printf ("Monitor Event ignored.\n");
#endif  
}

int
mouse_move (struct IntuiMessage *msg, struct Callback *cb)
{
  if (Action == ACT_NONE) return 0;

  if (Action == ACT_MOVE_WIN)
  {
    draw_vwindow (WHITE, Cvw);
    Cvw -> sx += msg -> MouseX - Dx;
    Cvw -> sy += msg -> MouseY - Dy;
    Dx = msg -> MouseX;
    Dy = msg -> MouseY;
    draw_vwindow (WHITE, Cvw);
  }
  return 0;
}

static void
change_area (int x, int y)
{
  struct vWindow *vw;
  WORD dx, dy;
  short oldarea;

  oldarea = Area;
  Area = vAREA (x, y);

  if (oldarea == Area)
    return;

#ifdef xDEBUG
  printf ("Visible area was %d, now %d.\n", oldarea, Area);
#endif

  if (Options & OPT_MOVE_SCREEN)
  {
    dx = VisWidth  * AREA_X (Area) + Win -> WScreen -> LeftEdge;
    dy = VisHeight * AREA_Y (Area) + Win -> WScreen -> TopEdge;

    MoveScreen (Win -> WScreen, -dx, -dy);

    MoveWindow (Win, dx, dy); 
  }
  else
  {
    for (vw = (struct vWindow *) VWinA[VWIndex].lh_Head;
	 vw -> node.ln_Succ; vw = (struct vWindow *) vw -> node.ln_Succ)
    {
      if (vw -> window == Win) /* The awvm window always stays */
      {
        vw -> area = Area;
      }
      else if (vw -> area == oldarea)
      {
        Forbid ();

	if (assert_window (vw -> window))    /* Move it away */
        {
	  ChangeWindowBox (vw -> window,
		0, VisHeight,
		vw -> window -> Width,
		vw -> window -> Height);
        }

        Permit ();
      }
#if 0 /* Remove, then add windows -- this looks better and is probably faster */
      else if (vw -> area == Area)
#else
    }
    for (vw = (struct vWindow *) VWinA[VWIndex].lh_Head;
	 vw -> node.ln_Succ; vw = (struct vWindow *) vw -> node.ln_Succ)
    {
      if (vw -> area == Area && vw -> window != Win)
#endif
      {
        Forbid ();

        if (assert_window (vw -> window))    /* Make it visible */
        {
	  ChangeWindowBox (vw -> window,
		vw -> sx * VisWidth  / SUB_WIDTH + Offset,
		vw -> sy * VisHeight / SUB_HEIGHT + Offset,
		vw -> window -> Width,
		vw -> window -> Height);
        }

	Permit ();
      }
    }
  }
}

int
mouse_event (struct IntuiMessage *msg, struct Callback *cb)
{
  static ULONG lsecs = 0, lmicros = 0;
  static int start_x, start_y;
  ULONG secs = 0, micros = 0;
  int x, y;

  x = Win -> GZZMouseX;
  y = Win -> GZZMouseY;

  switch (msg -> Code)
  {
    case SELECTDOWN:
      CurrentTime (&secs, &micros);
      if (DoubleClick (lsecs, lmicros, secs, micros))
      {
	lsecs = lmicros = 0;
	change_area (x, y);
      }
      else
      {
        if (Cvw = find_window (x, y))
        {
          Action = ACT_MOVE_WIN;
          SetDrMd (Rp, COMPLEMENT);
          draw_vwindow (WHITE, Cvw); /* Create temporary image */
	  Dx = msg -> MouseX;
 	  Dy = msg -> MouseY;

	  start_x = Cvw -> sx;
	  start_y = Cvw -> sy;
        }
        lsecs = secs;
        lmicros = micros;
      }
      break;

    case SELECTUP:
      if (Cvw)
      {
	int area;

	Action = ACT_NONE;
	draw_vwindow (WHITE, Cvw); /* Remove temporary image */
	SetDrMd (Rp, JAM2);

	area = vAREA (x, y);

	while (Cvw -> sx < 0) Cvw -> sx += SUB_WIDTH;
	while (Cvw -> sy < 0) Cvw -> sy += SUB_HEIGHT;
	Cvw -> sx %= SUB_WIDTH;
	Cvw -> sy %= SUB_HEIGHT;

	if ((start_x + 1 > Cvw -> sx && start_x - 1 < Cvw -> sx &&
	     start_y + 1 > Cvw -> sy && start_y - 1 < Cvw -> sy &&
	     area == Cvw -> area) ||
	    (area != Cvw -> area && Cvw -> window == Win))
	{
	  Cvw = NULL;
	  break;
	}
	
	Forbid ();

	if (assert_window (Cvw -> window))
	{
	  struct Window *win;

	  win = Cvw -> window;

	  if (area == Area)	/* Destination area is visible */
	  {
	    if (Options & OPT_MOVE_SCREEN) {
	      ChangeWindowBox (Cvw -> window,
		Cvw -> sx * VisWidth  / SUB_WIDTH  + AREA_X (area) * VisWidth,
		Cvw -> sy * VisHeight / SUB_HEIGHT + AREA_Y (area) * VisHeight,
		win -> Width, win -> Height);
	    }
	    else {
	      ChangeWindowBox (Cvw -> window,
		Cvw -> sx * VisWidth  / SUB_WIDTH,
		Cvw -> sy * VisHeight / SUB_HEIGHT,
		win -> Width, win -> Height);
	    }
	  }
	  else
	  {
	    if (Options & OPT_MOVE_SCREEN) {	/* Six areas (3 * 2) */
	      ChangeWindowBox (Cvw -> window,
		Cvw -> sx * VisWidth  / SUB_WIDTH  + AREA_X (area) * VisWidth,
		Cvw -> sy * VisHeight / SUB_HEIGHT + AREA_Y (area) * VisHeight,
		win -> Width, win -> Height);
	    }
	    else {				/* Two `real' areas (1 * 2) */
	      ChangeWindowBox (Cvw -> window,
		0, VisHeight, win -> Width, win -> Height);	    
	    }
	  }
	  Permit ();

	  Cvw -> area = area;
	}
	else {
	  Permit ();
	  DisplayBeep (Win -> WScreen);
	}
	Cvw = NULL;

	redraw_graphic ();
      }
      break;


    case MIDDLEDOWN: break;
    case MIDDLEUP:
      change_area (x, y);
      break;

    case MENUDOWN: break;
    case MENUUP:
      if (Cvw) break;
      if (Cvw = find_window (x, y))
      {
        Forbid ();
        if (assert_window (Cvw -> window))
        {
          ZipWindow (Cvw -> window);
	}
        Permit ();
	Cvw = NULL;
      }
      break;

    default: break;
  }
  return 0;
}


/* Shouldn't this disable SAS/C's ctrl-c handling? */
void CXBRK     () {}
void chkabort  () {}

