/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-

   AZMoveResizeHandler.m for the Azalea window manager
   Copyright (c) 2006        Yen-Ju Chen

   moveresize.c for the Openbox window manager
   Copyright (c) 2004        Mikael Magnusson
   Copyright (c) 2003        Ben Jansens

   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.

   See the COPYING file for a copy of the GNU General Public License.
*/

#import "AZMoveResizeHandler.h"
#import "AZScreen.h"
#import "AZClient.h"
#import "AZClient+Resist.h"
#import "AZClientManager.h"
#import "AZPopUp.h"
#import "grab.h"
#import "prop.h"
#import "openbox.h"
#import "config.h"
#import "render/render.h"
#import "render/theme.h"

static AZMoveResizeHandler *sharedInstance = nil;

@interface AZMoveResizeHandler (AZPrivate)

- (void) popupFormat: (NSString *) format a: (int) a b: (int) b;
- (void) doMove: (BOOL) resist;
- (void) doResize: (BOOL) resist;
- (void) clientDestroy: (NSNotification *) not;

@end

@implementation AZMoveResizeHandler

+ (AZMoveResizeHandler *) defaultHandler
{
  if (sharedInstance == nil)
  {
    sharedInstance = [[AZMoveResizeHandler alloc] init];
  }
  return sharedInstance;
}

- (id) init
{
  self = [super init];
  moving = NO;
  return self;
}

- (void) dealloc
{
  [super dealloc];
}

- (void) startup: (BOOL) reconfig
{
  popup = [[AZPopUp alloc] initWithIcon: NO];
  if (!reconfig) {
    [[NSNotificationCenter defaultCenter] addObserver: self
	    selector: @selector(clientDestroy:)
	    name: AZClientDestroyNotification
	    object: nil];
  }
}

- (void) shutdown: (BOOL) reconfig
{
  if (!reconfig) {
    if (moveresize_in_progress)
      [self end: NO];
    [[NSNotificationCenter defaultCenter] removeObserver: self];
  }
  DESTROY(popup);
}

- (void) startWithClient: (AZClient *) c x: (int) x y: (int) y
                button: (unsigned int) b corner: (unsigned int) cnr
{
    ObCursor cur;

    moving = (cnr == prop_atoms.net_wm_moveresize_move ||
              cnr == prop_atoms.net_wm_moveresize_move_keyboard);

    if (moveresize_in_progress || ![[c frame] visible] ||
        !(moving ?
          ([c functions] & OB_CLIENT_FUNC_MOVE) :
          ([c functions] & OB_CLIENT_FUNC_RESIZE)))
        return;

    moveresize_client = c;
    start_cx = [[c frame] area].x;
    start_cy = [[c frame] area].y;
    /* these adjustments for the size_inc make resizing a terminal more
       friendly. you essentially start the resize in the middle of the
       increment instead of at 0, so you have to move half an increment
       either way instead of a full increment one and 1 px the other. and this
       is one large mother fucking comment. */
    start_cw = [c area].width + [c size_inc].width / 2;
    start_ch = [c area].height + [c size_inc].height / 2;
    start_x = x;
    start_y = y;
    corner = cnr;
    button = b;

    /*
      have to change start_cx and start_cy if going to do this..
    if (corner == prop_atoms.net_wm_moveresize_move_keyboard ||
        corner == prop_atoms.net_wm_moveresize_size_keyboard)
        XWarpPointer(ob_display, None, c->window, 0, 0, 0, 0,
                     c->area.width / 2, c->area.height / 2);
    */

    if (moving) {
        cur_x = start_cx;
        cur_y = start_cy;
    } else {
        cur_x = start_cw;
        cur_y = start_ch;
    }

    moveresize_in_progress = YES;

    if (corner == prop_atoms.net_wm_moveresize_size_topleft)
        cur = OB_CURSOR_NORTHWEST;
    else if (corner == prop_atoms.net_wm_moveresize_size_top)
        cur = OB_CURSOR_NORTH;
    else if (corner == prop_atoms.net_wm_moveresize_size_topright)
        cur = OB_CURSOR_NORTHEAST;
    else if (corner == prop_atoms.net_wm_moveresize_size_right)
        cur = OB_CURSOR_EAST;
    else if (corner == prop_atoms.net_wm_moveresize_size_bottomright)
        cur = OB_CURSOR_SOUTHEAST;
    else if (corner == prop_atoms.net_wm_moveresize_size_bottom)
        cur = OB_CURSOR_SOUTH;
    else if (corner == prop_atoms.net_wm_moveresize_size_bottomleft)
        cur = OB_CURSOR_SOUTHWEST;
    else if (corner == prop_atoms.net_wm_moveresize_size_left)
        cur = OB_CURSOR_WEST;
    else if (corner == prop_atoms.net_wm_moveresize_size_keyboard)
        cur = OB_CURSOR_SOUTHEAST;
    else if (corner == prop_atoms.net_wm_moveresize_move)
        cur = OB_CURSOR_MOVE;
    else if (corner == prop_atoms.net_wm_moveresize_move_keyboard)
        cur = OB_CURSOR_MOVE;
    else
	NSAssert(0, @"Should not reach here");

    grab_pointer(YES, cur);
    grab_keyboard(YES);
}

- (void) end: (BOOL) cancel
{
    grab_keyboard(NO);
    grab_pointer(NO, OB_CURSOR_NONE);

    [popup hide];

    if (moving) {
	[moveresize_client moveToX: (cancel ? start_cx : cur_x)
		                        y: (cancel ? start_cy : cur_y)];
    } else {
	[moveresize_client configureToCorner: lockcorner
		x: [moveresize_client area].x
		y: [moveresize_client area].y
		width: (cancel ? start_cw : cur_x)
		height: (cancel ? start_ch : cur_y)
		user: YES final: YES];
    }

    moveresize_in_progress = NO;
    moveresize_client = nil;
}

- (void) event: (XEvent *) e
{
    NSAssert(moveresize_in_progress, @"Not in moving or resizing");

    if (e->type == ButtonPress) {
        if (!button) {
            start_x = e->xbutton.x_root;
            start_y = e->xbutton.y_root;
            button = e->xbutton.button; /* this will end it now */
        }
    } else if (e->type == ButtonRelease) {
        if (!button || e->xbutton.button == button) {
	    [self end: NO];
        }
    } else if (e->type == MotionNotify) {
        if (moving) {
            cur_x = start_cx + e->xmotion.x_root - start_x;
            cur_y = start_cy + e->xmotion.y_root - start_y;
	    [self doMove: YES];
        } else {
            if (corner == prop_atoms.net_wm_moveresize_size_topleft) {
                cur_x = start_cw - (e->xmotion.x_root - start_x);
                cur_y = start_ch - (e->xmotion.y_root - start_y);
                lockcorner = OB_CORNER_BOTTOMRIGHT;
            } else if (corner == prop_atoms.net_wm_moveresize_size_top) {
                cur_x = start_cw;
                cur_y = start_ch - (e->xmotion.y_root - start_y);
                lockcorner = OB_CORNER_BOTTOMRIGHT;
            } else if (corner == prop_atoms.net_wm_moveresize_size_topright) {
                cur_x = start_cw + (e->xmotion.x_root - start_x);
                cur_y = start_ch - (e->xmotion.y_root - start_y);
                lockcorner = OB_CORNER_BOTTOMLEFT;
            } else if (corner == prop_atoms.net_wm_moveresize_size_right) { 
                cur_x = start_cw + (e->xmotion.x_root - start_x);
                cur_y = start_ch;
                lockcorner = OB_CORNER_BOTTOMLEFT;
            } else if (corner ==
                       prop_atoms.net_wm_moveresize_size_bottomright) {
                cur_x = start_cw + (e->xmotion.x_root - start_x);
                cur_y = start_ch + (e->xmotion.y_root - start_y);
                lockcorner = OB_CORNER_TOPLEFT;
            } else if (corner == prop_atoms.net_wm_moveresize_size_bottom) {
                cur_x = start_cw;
                cur_y = start_ch + (e->xmotion.y_root - start_y);
                lockcorner = OB_CORNER_TOPLEFT;
            } else if (corner ==
                       prop_atoms.net_wm_moveresize_size_bottomleft) {
                cur_x = start_cw - (e->xmotion.x_root - start_x);
                cur_y = start_ch + (e->xmotion.y_root - start_y);
                lockcorner = OB_CORNER_TOPRIGHT;
            } else if (corner == prop_atoms.net_wm_moveresize_size_left) {
                cur_x = start_cw - (e->xmotion.x_root - start_x);
                cur_y = start_ch;
                lockcorner = OB_CORNER_TOPRIGHT;
            } else if (corner == prop_atoms.net_wm_moveresize_size_keyboard) {
                cur_x = start_cw + (e->xmotion.x_root - start_x);
                cur_y = start_ch + (e->xmotion.y_root - start_y);
                lockcorner = OB_CORNER_TOPLEFT;
            } else
		NSAssert(0, @"Should not reach here");

	    [self doResize: YES];
        }
    } else if (e->type == KeyPress) {
        if (e->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
	    [self end: YES];
        else if (e->xkey.keycode == ob_keycode(OB_KEY_RETURN))
	    [self end: NO];
        else {
            if (corner == prop_atoms.net_wm_moveresize_size_keyboard) {
                int dx = 0, dy = 0, ox = cur_x, oy = cur_y;

                if (e->xkey.keycode == ob_keycode(OB_KEY_RIGHT))
                    dx = MAX(4, [moveresize_client size_inc].width);
                else if (e->xkey.keycode == ob_keycode(OB_KEY_LEFT))
                    dx = -MAX(4, [moveresize_client size_inc].width);
                else if (e->xkey.keycode == ob_keycode(OB_KEY_DOWN))
                    dy = MAX(4, [moveresize_client size_inc].height);
                else if (e->xkey.keycode == ob_keycode(OB_KEY_UP))
                    dy = -MAX(4, [moveresize_client size_inc].height);
                else
                    return;

                cur_x += dx;
                cur_y += dy;
                XWarpPointer(ob_display, None, None, 0, 0, 0, 0, dx, dy);
                /* steal the motion events this causes */
                XSync(ob_display, NO);
                {
                    XEvent ce;
                    while (XCheckTypedEvent(ob_display, MotionNotify, &ce));
                }

		[self doResize: NO];

                /* because the cursor moves even though the window does
                   not nessesarily (resistance), this adjusts where the curor
                   thinks it started so that it keeps up with where the window
                   actually is */
                start_x += dx - (cur_x - ox);
                start_y += dy - (cur_y - oy);
            } else if (corner == prop_atoms.net_wm_moveresize_move_keyboard) {
                int dx = 0, dy = 0, ox = cur_x, oy = cur_y;
                int opx, px, opy, py;
		AZScreen *screen = [AZScreen defaultScreen];

                if (e->xkey.keycode == ob_keycode(OB_KEY_RIGHT))
                    dx = 4;
                else if (e->xkey.keycode == ob_keycode(OB_KEY_LEFT))
                    dx = -4;
                else if (e->xkey.keycode == ob_keycode(OB_KEY_DOWN))
                    dy = 4;
                else if (e->xkey.keycode == ob_keycode(OB_KEY_UP))
                    dy = -4;
                else
                    return;

                cur_x += dx;
                cur_y += dy;
		[screen pointerPosAtX: &opx y: &opy];
                XWarpPointer(ob_display, None, None, 0, 0, 0, 0, dx, dy);
                /* steal the motion events this causes */
                XSync(ob_display, NO);
                {
                    XEvent ce;
                    while (XCheckTypedEvent(ob_display, MotionNotify, &ce));
                }
		[screen pointerPosAtX: &px y: &py];

		[self doMove: NO];

                /* because the cursor moves even though the window does
                   not nessesarily (resistance), this adjusts where the curor
                   thinks it started so that it keeps up with where the window
                   actually is */
                start_x += (px - opx) - (cur_x - ox);
                start_y += (py - opy) - (cur_y - oy);
            }
        }
    }
}

- (BOOL) moveresize_in_progress { return moveresize_in_progress; }
- (AZClient *) moveresize_client { return moveresize_client; }

@end

@implementation AZMoveResizeHandler (AZPrivate)

- (void) popupFormat: (NSString *) format a: (int) a b: (int) b;
{
    AZClient *c = moveresize_client;
    NSString *text;

    text = [NSString stringWithFormat: format, a, b];
    if (config_resize_popup_pos == 1) /* == "Top" */
	[popup positionWithGravity: SouthGravity
		x: [[c frame] area].x + [[c frame] area].width/2
		y: [[c frame] area].y];
    else /* == "Center" */
	[popup positionWithGravity: CenterGravity
	    x: [[c frame] area].x + [[c frame] size].left + [c area].width / 2
	    y: [[c frame] area].y + [[c frame] size].top + [c area].height / 2];

    [popup showText: text];
}

- (void) doMove: (BOOL) resist
{
    if (resist) {
	[moveresize_client resistMoveWindowsAtX: &cur_x y: &cur_y];
    	[moveresize_client resistMoveMonitorsAtX: &cur_x y: &cur_y];
    }

    /* get where the client should be */
    [[moveresize_client frame] frameGravityAtX: &cur_x y: &cur_y];
    [moveresize_client configureToCorner: OB_CORNER_TOPLEFT
	    x: cur_x y: cur_y
	    width: [moveresize_client area].width
	    height: [moveresize_client area].height
	    user: YES final: NO];
    if (config_resize_popup_show == 2) /* == "Always" */
	[self popupFormat: @"%d x %d"
                a: [[moveresize_client frame] area].x
                b: [[moveresize_client frame] area].y];
}

- (void) doResize: (BOOL) resist
{
    /* resist_size_* needs the frame size */
    cur_x += [[moveresize_client frame] size].left +
        [[moveresize_client frame] size].right;
    cur_y += [[moveresize_client frame] size].top +
        [[moveresize_client frame] size].bottom;

    if (resist) {
	[moveresize_client resistSizeWindowsWithWidth: &cur_x height: &cur_y corner: lockcorner];
    	[moveresize_client resistSizeMonitorsWithWidth: &cur_x height: &cur_y corner: lockcorner];
    }

    cur_x -= [[moveresize_client frame] size].left +
        [[moveresize_client frame] size].right;
    cur_y -= [[moveresize_client frame] size].top +
        [[moveresize_client frame] size].bottom;
 
    [moveresize_client configureToCorner: lockcorner
	    x: [moveresize_client area].x
	    y: [moveresize_client area].y
	    width: cur_x height: cur_y user: YES final: NO];

    /* this would be better with a fixed width font ... XXX can do it better
       if there are 2 text boxes */
    if (config_resize_popup_show == 2 || /* == "Always" */
            (config_resize_popup_show == 1 && /* == "Nonpixel" */
                ([moveresize_client size_inc].width > 1 ||
                 [moveresize_client size_inc].height > 1))
        )
	[self popupFormat: @"%d x %d"
              a: [moveresize_client logical_size].width
              b: [moveresize_client logical_size].height];
}

- (void) clientDestroy: (NSNotification *) not
{
  AZClient *client = [not object];
  if (moveresize_client == client)
    [self end: YES];
}

@end
