/*
* Copyright (C) 1998-2018 by Northwoods Software Corporation
* All Rights Reserved.
*
* FLOOR PLANNER: WALL RESHAPING TOOL
* Used to reshape walls via their endpoints in a Floorplan
*/

import * as go from "../../../release/go"
import Floorplan = require("./Floorplan");

class WallReshapingTool extends go.Tool {

	private _handleArchetype: go.Shape;
	private _handle: go.GraphObject;
	private _adornedShape: go.Shape;
	private _angle: number;
	private _length: number;
	private _reshapeObjectName: string;
	private _isBuilding: boolean;
	private _returnData: any;
	private _returnPoint: go.Point;

	constructor() {
		super();

		let h: go.Shape = new go.Shape();
	    h.figure = "Diamond";
	    h.desiredSize = new go.Size(7, 7);
	    h.fill = "lightblue";
	    h.stroke = "dodgerblue";
	    h.cursor = "move";
	    this._handleArchetype = h;

	    this._handle = null;
	    this._adornedShape = null;
	    this._reshapeObjectName = 'SHAPE';
	    this._angle = 0;
	    this._length;
	    this._isBuilding = false; // only true when a wall is first being constructed, set in WallBuildingTool's doMouseUp function

	    this._returnPoint = null; // used if reshape is cancelled; return reshaping wall endpoint to its previous location
	    this._returnData = null; // used if reshape is cancelled; return all windows/doors of a reshaped wall to their old place
	}

	// Get the archetype for the handle (a Shape)
	get handleArchetype() { return this._handleArchetype }

	// Get / set current handle being used to reshape the wall
    get handle() { return this._handle; }
    set handle(value: go.GraphObject) { this._handle = value; }

	// Get / set adorned shape (shape of the Wall Group being reshaped)
	get adornedShape() { return this._adornedShape; }
    set adornedShape(value: go.Shape) { this._adornedShape = value; }

	// Get / set current angle
	get angle() { return this._angle; }
    set angle(value: number) { this._angle = value; }

	// Get / set length of the wall being reshaped (used only with SHIFT + drag)
	get length() { return this._length; }
    set length(value: number) { this._length = value; }

	// Get / set the name of the object being reshaped
	get reshapeObjectName() { return this._reshapeObjectName; }
    set reshapeObjectName(value: string) { this._reshapeObjectName = value; }

    // Get / set flag telling tool whether it's reshaping a new wall (isBuilding = true) or reshaping an old wall (isBuilding = false)
	get isBuilding() { return this._isBuilding; }
    set isBuilding(value: boolean) { this._isBuilding = value; }

	// Get set loc data for wallParts to return to if reshape is cancelled
	get returnData() { return this._returnData; }
    set returnData(value: any) { this._returnData = value; }

	// Get / set the point to return the reshaping wall endpoint to if reshape is cancelled
	get returnPoint() { return this._returnPoint; }
    set returnPoint(value: go.Point) { this._returnPoint = value; }

    /*
	* Places reshape handles on either end of a wall node
	* @param {part} The wall to adorn
	*/
	public updateAdornments(part: go.Part): void {
	    if (part === null || part instanceof go.Link) return;
	    if (part.isSelected && !this.diagram.isReadOnly) {
	        let selelt_go: go.GraphObject = part.findObject(this.reshapeObjectName);
	        if (selelt_go.part.data.category === "WallGroup") {
	        	let selelt: go.Shape = <go.Shape>selelt_go;
	            let adornment: go.Adornment = part.findAdornment(this.name);
	            if (adornment === null) {
	                adornment = this.makeAdornment(selelt);
	            }
	            if (adornment !== null && selelt.geometry != null) {
	                // update the position/alignment of each handle
	                let geo: go.Geometry = selelt.geometry;
	                let b: go.Rect = geo.bounds;
	                // update the size of the adornment
	                adornment.findObject("BODY").desiredSize = b.size;
	                adornment.elements.each(function (h) {
	                    if (h.name === undefined) return;
	                    let x: number = 0;
	                    let y: number = 0;
	                    switch (h.name) {
	                        case 'sPt': x = geo.startX; y = geo.startY; break;
	                        case 'ePt': x = geo.endX; y = geo.endY; break;
	                    }
	                    let xCheck: number = Math.min((x - b.x) / b.width, 1);
	                    let yCheck: number = Math.min((y - b.y) / b.height, 1);
	                    if (xCheck < 0) xCheck = 0;
	                    if (yCheck < 0) yCheck = 0;
	                    if (xCheck > 1) xCheck = 1;
	                    if (yCheck > 1) yCheck = 1;
	                    if (isNaN(xCheck)) xCheck = 0;
	                    if (isNaN(yCheck)) yCheck = 0;
	                    h.alignment = new go.Spot(Math.max(0, xCheck), Math.max(0, yCheck));
	                });

	                part.addAdornment(this.name, adornment);
	                adornment.location = selelt.getDocumentPoint(go.Spot.Center);
	                adornment.angle = selelt.getDocumentAngle();
	                return;
	            }
	        }
	    }
	    part.removeAdornment(this.name);
	}

	// If the user has clicked down at a visible handle on a wall node, then the tool may start
	public canStart(): boolean {
	    if (!this.isEnabled) return false;
	    const diagram: go.Diagram = this.diagram;
	    if (diagram === null || diagram.isReadOnly) return false;
	    if (!diagram.allowReshape) return false;
	    if (!diagram.lastInput.left) return false;
	    let h: go.GraphObject = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
	    return (h !== null || this.isBuilding);
	}

	// Start a new transaction for the wall reshaping
	public doActivate(): void {
	    const diagram: go.Diagram = this.diagram;
	    if (diagram === null) return;
	    if (this.isBuilding) {
	        // this.adornedShape has already been set in WallBuildingTool's doMouseDown function
	        let wall: go.Group = <go.Group>this.adornedShape.part;
	        this.handle = this.findToolHandleAt(wall.data.endpoint, this.name);
	    } else {
	        this.handle = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
	        if (this.handle === null) return;
	        let adorn: go.Adornment = <go.Adornment>this.handle.part;
	        let shape: go.Shape = <go.Shape>adorn.adornedObject;
	        let wall: go.Group = <go.Group>shape.part;
	        if (!shape) return;
	        this.adornedShape = shape;

	        // store pre-reshape location of wall's reshaping endpoint
	        this.returnPoint = this.snapPointToGrid(diagram.firstInput.documentPoint);

	        // store pre-reshape locations of all wall's members (windows / doors)
	        let wallParts: go.Iterator<go.Part> = wall.memberParts;
	        if (wallParts.count != 0) {
	            let locationsMap: go.Map<string, go.Point> = new go.Map(/*"string", go.Point*/);
	            wallParts.iterator.each(function (wallPart) {
	                locationsMap.add(wallPart.data.key, wallPart.location);
	            });
	            this.returnData = locationsMap;
	        }
	    }

	    diagram.isMouseCaptured = true;
	    this.startTransaction(this.name);
	    this.isActive = true;
	}

	// Adjust the handle's coordinates, along with the wall's points
	public doMouseMove(): void {
	    const diagram: go.Diagram = this.diagram;
	    const tool: WallReshapingTool = this;
	    let adorn: go.Adornment = <go.Adornment>tool.handle.part;
	    let wall: go.Group = <go.Group>adorn.adornedPart;

	    if (tool.isActive && diagram !== null) {
	        let mousePt: go.Point = diagram.lastInput.documentPoint;
	        tool.calcAngleAndLengthFromHandle(mousePt); // sets this.angle and this.length (useful for when SHIFT is held)
	        let newpt: go.Point = diagram.lastInput.documentPoint;
	        tool.reshape(newpt);
	    }
	    let fp: Floorplan = <Floorplan>diagram;
	    fp.updateWallAngles();
	}

	// Does one final reshape, commits the transaction, then stops the tool
	public doMouseUp(): void {
	    const diagram: go.Diagram = this.diagram;
	    if (this.isActive && diagram !== null) {
	        let newpt: go.Point = diagram.lastInput.documentPoint;
	        this.reshape(newpt);
	        this.transactionResult = this.name;  // success
	    }
	    this.stopTool();
	}

	// End the wall reshaping transaction
	public doDeactivate(): void {
	    const diagram: go.Diagram = this.diagram;
	    let fp: Floorplan = <Floorplan>diagram;
	    let returnData: any = this.returnData;
	    // if a wall reshaped to length < 1 px, remove it
	    let adorn: go.Adornment = <go.Adornment>this.handle.part;
	    let wall: go.Group = <go.Group>adorn.adornedPart;
	    let sPt: go.Point = wall.data.startpoint;
	    let ePt: go.Point = wall.data.endpoint;
	    let length: number = Math.sqrt(sPt.distanceSquared(ePt.x, ePt.y));
	    if (length < 1) {
	        diagram.remove(wall); // remove wall
	        wall.memberParts.iterator.each(function (member) { diagram.remove(member); }) // remove wall's parts
	        let wallDimensionLinkPointNodes: Array<go.Node> = [];
	        fp.pointNodes.iterator.each(function (node) { if (node.data.key.indexOf(wall.data.key) !== -1) wallDimensionLinkPointNodes.push(node); });
	        diagram.remove(wallDimensionLinkPointNodes[0]);
	        diagram.remove(wallDimensionLinkPointNodes[1]);
	    }

	    // remove wall's dimension links if tool cancelled via esc key
	    if (diagram.lastInput.key === "Esc" && !this.isBuilding) {
	        diagram.skipsUndoManager = true;
	        diagram.startTransaction("reset to old data");
	        if (this.handle.name === "sPt") wall.data.startpoint = this.returnPoint;
	        else wall.data.endpoint = this.returnPoint;

	        fp.updateWall(wall);

	        if (this.returnData) {
	            this.returnData.iterator.each(function (kvp) {
	                let key: string = kvp.key;
	                let loc: go.Point = kvp.value;
	                let wallPart: go.Node = <go.Node>diagram.findPartForKey(key);
	                wallPart.location = loc;
	                wallPart.rotateObject.angle = wall.rotateObject.angle;
	            });
	        }
	        diagram.commitTransaction("reset to old data");
	        diagram.skipsUndoManager = false;
	    }

	    // remove guide line point nodes
	    let glPoints: go.Iterator<go.Node> = this.diagram.findNodesByExample({ category: 'GLPointNode' });
	    diagram.removeParts(glPoints, true);

	    fp.updateWallDimensions();
	    // commit transaction, deactivate tool
	    diagram.commitTransaction(this.name);
	    this.isActive = false;
	}

	/*
	* Creates an adornment with 2 handles
	* @param {Shape} The adorned wall's Shape element
	*/
	public makeAdornment = function (selelt: go.Shape): go.Adornment {
	    let adornment: go.Adornment = new go.Adornment;
	    adornment.type = go.Panel.Spot;
	    adornment.locationObjectName = "BODY";
	    adornment.locationSpot = go.Spot.Center;
	    let h: go.Shape = new go.Shape();
	    h.name = "BODY"
	    h.fill = null;
	    h.stroke = null;
	    h.strokeWidth = 0;
	    adornment.add(h);

	    h = this.makeHandle();
	    h.name = 'sPt';
	    adornment.add(h);
	    h = this.makeHandle();
	    h.name = 'ePt';
	    adornment.add(h);

	    adornment.category = this.name;
	    adornment.adornedObject = selelt;
	    return adornment;
	}

	// Creates a basic handle archetype
	public makeHandle = function (): go.Shape {
	    let h: go.Shape = this.handleArchetype;
	    return h.copy();
	}

	/*
	* Calculate the angle and length made from the mousepoint and the non-moving handle; used to reshape wall when holding SHIFT
	* @param {Point} mousePt The mouse cursors coordinate position
	*/
	public calcAngleAndLengthFromHandle = function (mousePt: go.Point) {
	    const tool: WallReshapingTool = this;
	    const diagram: go.Diagram = this.diagram;
	    let h: go.GraphObject = this.handle;
	    let otherH: go.GraphObject;
	    let node: go.Node = this.adornedShape.part;
	    let adornments: go.Iterator<go.Adornment> = node.adornments.iterator;
	    let adornment: go.Adornment;
	    adornments.each(function (a) { if (a.category === tool.name) adornment = a; })
	    adornment.elements.each(function (e) {
	        if (e.name != undefined && e.name != h.name) {
	        	otherH = e;
	        }
	    });

	    // calc angle from otherH against the horizontal
	    let otherHandlePt: go.Point = otherH.getDocumentPoint(go.Spot.Center);

	    let deltaY: number = mousePt.y - otherHandlePt.y
	    let deltaX: number = mousePt.x - otherHandlePt.x
	    let angle: number = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
	    // because atan2 goes from -180 to +180 and we want it to be 0-360
	    // so -90 becomes 270, etc.
	    if (angle < 0) angle += 360;
	    tool.angle = angle;

	    let distanceBetween: number = Math.sqrt(mousePt.distanceSquared(otherHandlePt.x, otherHandlePt.y));
	    tool.length = distanceBetween;
	}

	/*
	* Takes a point -- returns a new point that is closest to the original point that conforms to the grid snap
	* @param {Point} point The point to snap to grid
	*/
	public snapPointToGrid = function (point: go.Point): go.Point {
	    const diagram: go.Diagram = this.diagram;
	    let newx: number = diagram.model.modelData.gridSize * Math.round(point.x / diagram.model.modelData.gridSize);
	    let newy: number = diagram.model.modelData.gridSize * Math.round(point.y / diagram.model.modelData.gridSize);
	    let newPt: go.Point = new go.Point(newx, newy);
	    return newPt;
	}

	/*
	* Reshapes the shape's geometry, updates model data
	* @param {Point} newPoint The point to move the reshaping wall's reshaping endpoint to
	*/
	public reshape = function (newPoint: go.Point) {
	    const diagram: go.Diagram = this.diagram;
	    const tool: WallReshapingTool = this;
	    let shape: go.Shape = this.adornedShape;
	    let node: go.Group = <go.Group>shape.part;

	    // if user holds SHIFT, make angle between startPoint / endPoint and the horizontal line a multiple of 45
	    if (this.diagram.lastInput.shift) {

	        let sPt: go.Point; // the stationary point -- the point at the handle that is not being adjusted
	        if (tool.handle.name === 'sPt') sPt = node.data.endpoint;
	        else sPt = node.data.startpoint;

	        let oldGridSize: number = diagram.model.modelData.gridSize;
	        let gridSize: number = diagram.model.modelData.gridSize;
	        //if gridSnapping is disabled, just set 'gridSize' var to 1 so it doesn't affect endPoint calculations
	        if (!(this.diagram.toolManager.draggingTool.isGridSnapEnabled)) gridSize = 1;

	        // these are set in mouseMove's call to calcAngleAndLengthFromHandle()
	        let angle: number = tool.angle;
	        let length: number = tool.length;

	        // snap to 90 degrees
	        if (angle > 67.5 && angle < 112.5) {
	            let newy: number = sPt.y + length;
	            newy = gridSize * Math.round(newy / gridSize);
	            newPoint = new go.Point(sPt.x, newy);
	        }
	        // snap to 180 degrees
	        if (angle > 112.5 && angle < 202.5) {
	            let newx: number = sPt.x - length;
	            newx = gridSize * Math.round(newx / gridSize);
	            newPoint = new go.Point(newx, sPt.y);
	        }
	        // snap to 270 degrees
	        if (angle > 247.5 && angle < 292.5) {
	            let newy: number = sPt.y - length;
	            newy = gridSize * Math.round(newy / gridSize);
	            newPoint = new go.Point(sPt.x, newy);
	        }
	        // snap to 360 degrees
	        if (angle > 337.5 || angle < 22.5) {
	            let newx: number = sPt.x + length;
	            newx = gridSize * Math.round(newx / gridSize);
	            newPoint = new go.Point(newx, sPt.y);
	        }
	        // snap to 45 degrees
	        if (angle > 22.5 && angle < 67.5) {
	            let newx: number = (Math.sin(.785) * length);
	            newx = gridSize * Math.round(newx / gridSize) + sPt.x;
	            let newy: number = (Math.cos(.785) * length);
	            newy = gridSize * Math.round(newy / gridSize) + sPt.y;
	            newPoint = new go.Point(newx, newy);
	        }
	        // snap to 135 degrees
	        if (angle > 112.5 && angle < 157.5) {
	            let newx: number = (Math.sin(.785) * length);
	            newx = sPt.x - (gridSize * Math.round(newx / gridSize));
	            let newy: number = (Math.cos(.785) * length);
	            newy = gridSize * Math.round(newy / gridSize) + sPt.y;
	            newPoint = new go.Point(newx, newy);
	        }
	        // snap to 225 degrees
	        if (angle > 202.5 && angle < 247.5) {
	            let newx: number = (Math.sin(.785) * length);
	            newx = sPt.x - (gridSize * Math.round(newx / gridSize));
	            let newy: number = (Math.cos(.785) * length);
	            newy = sPt.y - (gridSize * Math.round(newy / gridSize));
	            newPoint = new go.Point(newx, newy);
	        }
	        // snap to 315 degrees
	        if (angle > 292.5 && angle < 337.5) {
	            let newx: number = (Math.sin(.785) * length);
	            newx = sPt.x + (gridSize * Math.round(newx / gridSize));
	            let newy: number = (Math.cos(.785) * length);
	            newy = sPt.y - (gridSize * Math.round(newy / gridSize));
	            newPoint = new go.Point(newx, newy);
	        }
	        gridSize = oldGridSize; // set gridSize back to what it used to be in case gridSnap is enabled again
	    }
	    if (this.diagram.toolManager.draggingTool.isGridSnapEnabled) newPoint = this.snapPointToGrid(newPoint);
	    else newPoint = new go.Point(newPoint.x, newPoint.y);

	    let type: string = this.handle.name;
	    if (type === undefined) return;
	    // set the appropriate point in the node's data to the newPoint value
	    switch (type) {
	        case 'sPt':
	            reshapeWall(node, node.data.endpoint, node.data.startpoint, newPoint, diagram, tool);
	            break;
	        case 'ePt':
	            reshapeWall(node, node.data.startpoint, node.data.endpoint, newPoint, diagram, tool);
	            break;
	    }
	    this.updateAdornments(shape.part);
	    this.showMatches();
	    let fp: Floorplan = <Floorplan>diagram;
	    fp.updateWallDimensions();
	} // end reshape()


	// Show if the wall (at the adjustment handle being moved) lines up with other wall edges
	public showMatches = function () {
	    const diagram: go.Diagram = this.diagram;
	    if (!diagram.model.modelData.preferences.showWallGuidelines) return;
	    const tool: WallReshapingTool = this;
	    let wall: go.Node = this.adornedShape.part;
	    let comparePt: go.Point;
	    if (this.handle.name === 'sPt') comparePt = wall.data.startpoint;
	    else comparePt = wall.data.endpoint;

	    // the wall attached to the handle being manipulated
	    let hWall: go.Part = this.adornedShape.part;

	    // delete any old guideline points (these are used to show guidelines, must be cleared before a new guideline can be shown)
	    let glPoints: go.Iterator<go.Node> = <go.Iterator<go.Node>>diagram.findNodesByExample({ category: 'GLPointNode' });
	    diagram.removeParts(glPoints, true);

	    let walls: go.Iterator<go.Group> = <go.Iterator<go.Group>>this.diagram.findNodesByExample({ category: 'WallGroup' });
	    walls.iterator.each(function (w) {
	        if (w.data.key != hWall.data.key) {
	            let shape: go.Shape = <go.Shape>w.findObject('SHAPE');
	            let geo: go.Geometry = shape.geometry;

	            let pt1: go.Point = w.data.startpoint;
	            let pt2: go.Point = w.data.endpoint;

	            tool.checkPtLinedUp(pt1, comparePt.x, pt1.x, comparePt);
	            tool.checkPtLinedUp(pt1, comparePt.y, pt1.y, comparePt);
	            tool.checkPtLinedUp(pt2, comparePt.x, pt2.x, comparePt);
	            tool.checkPtLinedUp(pt2, comparePt.y, pt2.y, comparePt);
	        }
	    })
	}

	/* Static function -- checks if there exists a horiontal or vertical line (decided by 'coord' parameter) between pt and compare pt
	* if so, draws a link between the two, letting the user know the wall they're reshaping lines up with another's edge
	* @param {Point} pt
	* @param {Number} comparePtCoord
	* @param {Number} ptCoord
	* @param {Point} comparePt
	*/
	public checkPtLinedUp = function (pt: go.Point, comparePtCoord: number, ptCoord: number, comparePt: go.Point) {
	    function makeGuideLinePoint() {
	        let $ = go.GraphObject.make;
	        return $(go.Node, "Spot", { locationSpot: go.Spot.TopLeft, locationObjectName: "SHAPE", desiredSize: new go.Size(1, 1), },
	        new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
	        $(go.Shape, { stroke: null, strokeWidth: 1, name: "SHAPE", fill: "black", })
	      );}

	    function makeGuideLineLink() {
	        let $ = go.GraphObject.make;
	        return $(go.Link,
	        $(go.Shape, { stroke: "black", strokeWidth: 2, name: 'SHAPE', },
	        new go.Binding("strokeWidth", "width"),
	        new go.Binding('stroke', 'stroke')
	        )
	    );}

	    const diagram: go.Diagram = this.diagram;
	    let errorMargin: number = Math.abs(comparePtCoord - ptCoord);
	    if (errorMargin < 2) {

	        let data = { category: "GLPointNode", loc: go.Point.stringify(pt), key: "glpt" };
	        let data2 = { key: 'movingPt', category: "GLPointNode", loc: go.Point.stringify(comparePt) };
	        let data3 = { key: 'guideline', category: 'guideLine', from: 'movingPt', to: data.key, stroke: 'blue' };
	        let GLPoint1: go.Node = makeGuideLinePoint();
	        let GLPoint2: go.Node = makeGuideLinePoint();
	        let GLLink: go.Link = makeGuideLineLink();
	        diagram.add(GLPoint1);
	        diagram.add(GLPoint2);
	        diagram.add(GLLink);

	        GLPoint1.data = data;
	        GLPoint2.data = data2;
	        GLLink.data = data3;
	        GLLink.fromNode = GLPoint1;
	        GLLink.toNode = GLPoint2;
	    }
	}

}

/*
* Maintain position of all wallParts as best as possible when a wall is being reshaped
* Position is relative to the distance a wallPart's location is from the stationaryPoint of the wall
* This is called during WallReshapingTool's reshape function
* @param {Group} wall The wall being reshaped
* @param {Point} stationaryPoint The endpoint of the wall not being reshaped
* @param {Point} movingPoint The endpoint of the wall being reshaped
* @param {Point} newPoint The point that movingPoint is going to
* @param {Diagram} diagram The diagram belonging WallReshapingTool belongs to
* @param {WallReshapingTool} tool
*/
function reshapeWall(wall: go.Group, stationaryPoint: go.Point, movingPoint: go.Point, newPoint: go.Point, diagram: go.Diagram, tool: WallReshapingTool) {
    let wallParts: go.Iterator<go.Node> = <go.Iterator<go.Node>>wall.memberParts;
    let arr: Array<go.Part> = [];
    let oldAngle: number = wall.rotateObject.angle;
    wallParts.iterator.each(function (part) { arr.push(part); });
    // remember the distance each wall part's location was from the stationary point; store these in a Map
    let distancesMap: go.Map<string, number> = new go.Map(/*"string", "number"*/);
    let closestPart: go.Part = null; let closestDistance: number = Number.MAX_VALUE;
    for (let i: number = 0; i < arr.length; i++) {
        let part: go.Part = arr[i];
        let distanceToStationaryPt: number = Math.sqrt(part.location.distanceSquaredPoint(stationaryPoint));
        distancesMap.add(part.data.key, distanceToStationaryPt);
        // distanceToMovingPt is determined by whichever endpoint of the wallpart is closest to movingPoint
        let endpoints: Array<go.Point> = getWallPartEndpoints(part);
        let distanceToMovingPt: number = Math.min(Math.sqrt(endpoints[0].distanceSquaredPoint(movingPoint)),
                                Math.sqrt(endpoints[1].distanceSquaredPoint(movingPoint)));
        // find and store the closest wallPart to the movingPt
        if (distanceToMovingPt < closestDistance) {
            closestDistance = distanceToMovingPt;
            closestPart = part;
        }
    }
    // if the proposed newPoint would make it so the wall would reshape past closestPart, set newPoint to the edge point of closest part
    if (closestPart !== null) {
        let loc: go.Point = closestPart.location;
        let partLength: number = closestPart.data.length;
        let angle: number = oldAngle;
        let point1: go.Point = new go.Point((loc.x + (partLength / 2)), loc.y);
        let point2: go.Point = new go.Point((loc.x - (partLength / 2)), loc.y);
        point1.offset(-loc.x, -loc.y).rotate(angle).offset(loc.x, loc.y);
        point2.offset(-loc.x, -loc.y).rotate(angle).offset(loc.x, loc.y);
        let distance1: number = Math.sqrt(stationaryPoint.distanceSquaredPoint(point1));
        let distance2: number = Math.sqrt(stationaryPoint.distanceSquaredPoint(point2));

        let minLength: number; let newLoc: go.Point;
        if (distance1 > distance2) {
            minLength = distance1;
            newLoc = point1;
        } else {
            minLength = distance2;
            newLoc = point2;
        }

        let testDistance: number = Math.sqrt(stationaryPoint.distanceSquaredPoint(newPoint));
        if (testDistance < minLength) newPoint = newLoc;
    }
    // reshape the wall
    if (movingPoint === wall.data.endpoint) diagram.model.setDataProperty(wall.data, "endpoint", newPoint);
    else diagram.model.setDataProperty(wall.data, "startpoint", newPoint);
    let fp: Floorplan = <Floorplan>diagram;
    fp.updateWall(wall);
    // calculate the new angle offset
    let newAngle: number = wall.rotateObject.angle;
    let angleOffset: number = newAngle - oldAngle;
    // for each wallPart, maintain relative distance from the stationaryPoint
    distancesMap.iterator.each(function (kvp) {
        let wallPart: go.Node = <go.Node>diagram.findPartForKey(kvp.key);
        let distance: number = kvp.value;
        let wallLength: number = Math.sqrt(stationaryPoint.distanceSquaredPoint(movingPoint));
        let newLoc: go.Point = new go.Point(stationaryPoint.x + ((distance / wallLength) * (movingPoint.x - stationaryPoint.x)),
            stationaryPoint.y + ((distance / wallLength) * (movingPoint.y - stationaryPoint.y)));
        wallPart.location = newLoc;
        wallPart.angle = (wallPart.angle + angleOffset) % 360;
    });
} // end reshapeWall()

/*
* Find and return an array of the endpoints of a given wallpart (window or door)
* @param {Node} wallPart A Wall Part Node -- i.e. Door Node, Window Node
*/
function getWallPartEndpoints(wallPart) {
    var loc = wallPart.location;
    var partLength = wallPart.data.length; var angle = 0;
    if (wallPart.containingGroup !== null) angle = wallPart.containingGroup.rotateObject.angle;
    else angle = 180;
    var point1 = new go.Point((loc.x + (partLength / 2)), loc.y);
    var point2 = new go.Point((loc.x - (partLength / 2)), loc.y);
    point1.offset(-loc.x, -loc.y).rotate(angle).offset(loc.x, loc.y);
    point2.offset(-loc.x, -loc.y).rotate(angle).offset(loc.x, loc.y);
    var arr = []; arr.push(point1); arr.push(point2);
    return arr;
}

export = WallReshapingTool;