/*
 * BadWM - minimalistic window manager for the X Window System
 * Copyright (C) Robert Annessi <robert@annessi.at>
 *
 * 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.
 *
 * 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.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "include/BadWM.h"
#include "include/config.h"
#include <assert.h>

void draw_outline(Client *c) {
	XDrawRectangle(dpy, c->screen->root, c->screen->invert_gc,
		c->x - c->border, c->y - c->border,
		c->width + opt_bw, c->height + opt_bw);
}

void get_mouse_position(int *x, int *y, Window root) {
	Window dw1, dw2;
	int t1, t2;
	unsigned int t3;

	XQueryPointer(dpy, root, &dw1, &dw2, x, y, &t1, &t2, &t3);
}

void recalculate_sweep(Client *c, int x1, int y1, int x2, int y2,
                      int westwards, int northwards ) {
	int basex, basey;
	int xsnap=0, ysnap=0;

	c->width = abs(x1 - x2);
	c->height = abs(y1 - y2);

	if (c->size->flags & PResizeInc) {
		basex = (c->size->flags & PBaseSize) ? c->size->base_width :
			(c->size->flags & PMinSize) ? c->size->min_width : 0;
		basey = (c->size->flags & PBaseSize) ? c->size->base_height :
			(c->size->flags & PMinSize) ? c->size->min_height : 0;
                xsnap = (c->width - basex) % c->size->width_inc;
                ysnap = (c->height - basey) % c->size->height_inc;
                c->width -= xsnap;
                c->height -= ysnap;
	}

	if (c->size->flags & PMinSize) {
		if (c->width < c->size->min_width) c->width = c->size->min_width;
		if (c->height < c->size->min_height) c->height = c->size->min_height;
	}

	if (c->size->flags & PMaxSize) {
		if (c->width > c->size->max_width) c->width = c->size->max_width;
		if (c->height > c->size->max_height) c->height = c->size->max_height;
	}

	c->x = (x1 <= x2) ? x1 : x1 - c->width;
	c->y = (y1 <= y2) ? y1 : y1 - c->height;
        if (x1>x2)
          xsnap = -xsnap;
        if (y1>y2)
          ysnap = -ysnap;
        if (westwards)
          c->x += xsnap;
        if (northwards)
          c->y += ysnap;
}

void sweep(Client *c) {
	XEvent ev;
        int win_px,win_py;
        double win_px_n,win_py_n;
        int root_px,root_py;
        Window dumw;
        int dumi;
        ResizeDir dir;
        int x1 = c->x;
        int y1 = c->y;
        int x2 = c->x+c->width;
        int y2 = c->y+c->height;
 
        XQueryPointer(dpy,c->window,&dumw,&dumw,&root_px,&root_py,&win_px,&win_py,&dumi);
        win_px_n = (double)win_px/(double)c->width;
        win_py_n = (double)win_py/(double)c->height;
 
        if (win_py_n<0.3)
          {
            if (win_px_n<0.3)
              dir = NorthWest;
            else if (win_px_n>0.7)
              dir = NorthEast;
            else
              dir = North;
          }
        else if (win_py_n>0.7)
          {
            if (win_px_n<0.3)
              dir = SouthWest;
            else if (win_px_n>0.7)
              dir = SouthEast;
            else
              dir = South;
          }
        else
          {
            if (win_px_n<=0.5)
              dir = West;
            else
              dir = East;
          }

	if (!grab_pointer(c->screen->root, MouseMask, resize_cursors[dir])) return;

	if ( config.global->solidwindows == 0 ) {
		XGrabServer(dpy);
		draw_outline(c);
	}

        for (;;) 
          {
            int dx;
            int dy;
 
            XMaskEvent(dpy, MouseMask, &ev);
            switch (ev.type) {
           case MotionNotify:
              draw_outline(c); /* clear */
              XUngrabServer(dpy);
              dx = ev.xmotion.x-root_px;
              dy = ev.xmotion.y-root_py;
              switch (dir)
                {
               case NorthWest:
                  recalculate_sweep(c,x1+dx,y1+dy,x2,y2,1,1);
                  break;
                case North:
                  recalculate_sweep(c,x1,y1+dy,x2,y2,0,1);
                  break;
                case NorthEast:
                  recalculate_sweep(c,x1,y1+dy,x2+dx,y2,0,1);
                  break;
                case East:
                  recalculate_sweep(c,x1,y1,x2+dx,y2,0,0);
                  break;
                case SouthEast:
                  recalculate_sweep(c,x1,y1,x2+dx,y2+dy,0,0);
                  break;
                case South:
                  recalculate_sweep(c,x1,y1,x2,y2+dy,0,0);
                  break;
                case SouthWest:
                  recalculate_sweep(c,x1+dx,y1,x2,y2+dy,1,0);
                  break;
                case West:
                  recalculate_sweep(c,x1+dx,y1,x2,y2,1,0);
                  break;
               }

		if (config.global->solidwindows == 0) {
			XSync(dpy, False);
			XGrabServer(dpy);
			draw_outline(c);
		} else {
			XMoveResizeWindow(dpy, c->parent, c->x - c->border,
					c->y - c->border, c->width + (c->border*2),
					c->height + (c->border*2));
			XMoveResizeWindow(dpy, c->window, c->border, c->border,
					c->width, c->height);
			send_config(c);
		}
		break;
            case ButtonRelease:
	      if (config.global->solidwindows == 0) {
			draw_outline(c); /* clear */
			XUngrabServer(dpy);
		}
		XUngrabPointer(dpy, CurrentTime);
              return;
            }
          }
}

static int absmin(int a, int b) {
       if (abs(a) < abs(b))
               return a;
       return b;
}
static void snap_client(Client *c) {
       int dx, dy;
       Client *ci;

       /* snap to screen border */
       if (abs(c->x - c->border) < config.global->snap_distance) c->x = c->border;
       if (abs(c->y - c->border) < config.global->snap_distance) c->y = c->border;
       if (abs(c->x + c->width + c->border - DisplayWidth(dpy, c->screen->screen)) < config.global->snap_distance)
               c->x = DisplayWidth(dpy, c->screen->screen) - c->width - c->border;
       if (abs(c->y + c->height + c->border - DisplayHeight(dpy, c->screen->screen)) < config.global->snap_distance)
               c->y = DisplayHeight(dpy, c->screen->screen) - c->height - c->border;

       /* snap to other windows */
       dx = dy = config.global->snap_distance;
      for (ci = head_client; ci; ci = ci->next) {
               if (ci != c && (ci->vdesk == vdesk || ci->vdesk == STICKY)) {
                       if (ci->y - ci->border - c->border - c->height - c->y <= config.global->snap_distance && c->y - c->border - ci->border - ci->height - ci->y <= config.global->snap_distance) {
                               dx = absmin(dx, ci->x + ci->width - c->x + c->border + ci->border);
                               dx = absmin(dx, ci->x + ci->width - c->x - c->width);
                               dx = absmin(dx, ci->x - c->x - c->width - c->border - ci->border);
                               dx = absmin(dx, ci->x - c->x);
                       }
                       if (ci->x - ci->border - c->border - c->width - c->x <= config.global->snap_distance && c->x - c->border - ci->border - ci->width - ci->x <= config.global->snap_distance) {
                               dy = absmin(dy, ci->y + ci->height - c->y + c->border + ci->border);
                               dy = absmin(dy, ci->y + ci->height - c->y - c->height);
                               dy = absmin(dy, ci->y - c->y - c->height - c->border - ci->border);
                               dy = absmin(dy, ci->y - c->y);
                       }
               }
       }
       if (abs(dx) < config.global->snap_distance)
               c->x += dx;
       if (abs(dy) < config.global->snap_distance)
               c->y += dy;
}

void drag(Client *c) {
	int x1, y1;
	int old_cx = c->x;
	int old_cy = c->y;
	XEvent ev;
	if (!grab_pointer(c->screen->root, MouseMask, move_curs)) return;
	get_mouse_position(&x1, &y1, c->screen->root);
	if ( config.global->solidwindows == 0 ) {
		XGrabServer(dpy);
		draw_outline(c);
	}

	for (;;) {
		XMaskEvent(dpy, MouseMask, &ev);
		switch (ev.type) {
			case MotionNotify:
				if ( config.global->solidwindows == 0 ) {
					draw_outline(c);
					XUngrabServer(dpy);
				}

				/* changing vdesk when pointer is at the edge of screen */
				if ( ev.xmotion.x ==  0)  {
					ev.xmotion.x = DisplayWidth(dpy, c->screen->screen) - 2;
					setmouse(c->screen->root, ev.xmotion.x, ev.xmotion.y);
					switch_vdesk_down();
					if ( config.global->solidwindows == 1 ) { /* we have to move the window to if solid */
						c->vdesk = vdesk;
						unhide(c, RAISE);
					}
				}
				else 
				if ( ev.xmotion.x == DisplayWidth(dpy, c->screen->screen) - 1)  {
					ev.xmotion.x = 2;
					setmouse(c->screen->root, ev.xmotion.x, ev.xmotion.y);
					switch_vdesk_up();
					if ( config.global->solidwindows == 1 ) { /* we have to move the window to if solid */
						c->vdesk = vdesk;
						unhide(c, RAISE);
					}
				}

				c->x = old_cx + (ev.xmotion.x - x1);
				c->y = old_cy + (ev.xmotion.y - y1);
				snap_client(c);
				if ( config.global->solidwindows == 0 ) {
					XSync(dpy, False);
					XGrabServer(dpy);
					draw_outline(c);
				} else {
					XMoveWindow(dpy, c->parent, c->x - c->border, c->y - c->border);
					send_config(c);
				}
				break;
			case ButtonRelease:
				/* redrawing (unhiding) the window if vdesk changed */
				if ( c->vdesk != vdesk && c->vdesk != STICKY ) {
					c->vdesk = vdesk;
					client_update_current(current, NULL);
					unhide(c, RAISE);
				}
				else
				if ( config.global->solidwindows == 0 ) draw_outline(c);

				XUngrabServer(dpy);
				XUngrabPointer(dpy, CurrentTime);
				return;
			default:
				break;
		}
	}
}

void move(Client *c, int set) {
	if (c) {
		XRaiseWindow(dpy, c->parent);
		if (!set)
			drag(c);
			XMoveWindow(dpy, c->parent, c->x - c->border, c->y - c->border);
			send_config(c);
	}
}

void resize(Client *c, int set) {
	if (c) {
		XRaiseWindow(dpy, c->parent);
		if (!set)
			sweep(c);
		XMoveResizeWindow(dpy, c->parent, c->x - c->border,
				c->y - c->border, c->width + (c->border*2),
				c->height + (c->border*2));
		XMoveResizeWindow(dpy, c->window, c->border, c->border,
				c->width, c->height);
		send_config(c);
	}
}

void maximise_horiz(Client *c) {
	#ifdef DEBUG
        	bad_debug(1,"SCREEN: maximise_horiz()\n");
	#endif
	if (c->oldw) {
		c->x = c->oldx;
		c->width = c->oldw;
		c->oldw = 0;
	} else {
		c->oldx = c->x;
		c->oldw = c->width;
		recalculate_sweep(c, 0, c->y, DisplayWidth(dpy, c->screen->screen),
                                c->y + c->height,0,0);
	}
}

void maximise_vert(Client *c) {
	#ifdef DEBUG
        	bad_debug(1,"SCREEN: maximise_vert()\n");
	#endif
	if (c->oldh) {
		c->y = c->oldy;
		c->height = c->oldh;
		c->oldh = 0;
	} else {
		c->oldy = c->y;
		c->oldh = c->height;
		recalculate_sweep(c, c->x, 0, c->x + c->width,
                                DisplayHeight(dpy, c->screen->screen),0,0);
	}
}

void hide(Client *c) {
	if (c) {
		c->ignore_unmap += 2;
		#ifdef DEBUG
                	bad_debug(1,"screen:XUnmapWindow(parent)\n");
		#endif
		XUnmapWindow(dpy, c->parent);
		#ifdef DEBUG
                	bad_debug(1,"screen:XUnmapWindow(window)\n");
		#endif
		XUnmapWindow(dpy, c->window);
		set_wm_state(c, IconicState);
	}
}

void unhide(Client *c, int raise) {
	XMapWindow(dpy, c->window);
	raise ? XMapRaised(dpy, c->parent) : XMapWindow(dpy, c->parent);
	set_wm_state(c, NormalState);
}

void next(void) {
        Client *newc = current;
        Client *stop;
	#ifdef DEBUG
        	bad_debug(1,"\n");
	#endif

	if (current) {
		stop = current;
		newc = current->fix_next;
	} else {
		stop = newc = head_client;
	}
	/* selecting window */
	assert(newc != NULL);
	for (;;) {
		if ( newc->vdesk == vdesk ) break;
		assert(newc->fix_next != NULL);
		newc = newc->fix_next;
		if ( newc == stop ) {
			newc = NULL;
			break;
		}
	}
	if ( newc ) {
		/* focusing new window */
		unhide(newc,RAISE);
		focus_client(newc);
	#ifdef DEBUG
	} else {
		bad_debug(1,"NEXT: hmm, no next window\n");
	#endif
	}
}

void switch_vdesk(int v) {
	Client *c;
	int wdesk;
	if (v == vdesk) return;
	if (current && current->vdesk != STICKY) {
		client_update_current(current, NULL);
	}
	#ifdef DEBUG
        	bad_debug(1,"Switching to desk %d\n", v);
	#endif
	for (c = head_client; c; c = c->next) {
		wdesk = c->vdesk;
		if (wdesk == vdesk) {
			hide(c);
		} else if (wdesk == v) {
			unhide(c, NO_RAISE);
		}
	}
	vdesk = v;
}

ScreenInfo *find_screen(Window root) {
	int i;
	for (i = 0; i < num_screens; i++) {
		if (screens[i].root == root)
			return &screens[i];
	}
	return NULL;
}

void switch_vdesk_up(void)
{
	if (config.global->max_vdesks == 10 && vdesk == 9) switch_vdesk(0); 
	else if (vdesk < config.global->max_vdesks) switch_vdesk(vdesk+1);
	else switch_vdesk(1);
}

void switch_vdesk_down(void)
{
	if (vdesk > config.global->min_vdesk) switch_vdesk(vdesk-1);
	else if (config.global->max_vdesks == 10 && vdesk == 0) switch_vdesk(9); 
	else switch_vdesk(config.global->max_vdesks);
}


syntax highlighted by Code2HTML, v. 0.9.1