//-------------------------------------------------------------------------- 
// 
//  Copyright (c) Microsoft Corporation.  All rights reserved. 
// 
//  File: InkCanvas.js
//
//        Provides the basic inking support for Silverlight
//        by wrapping an InkPresenter and providing WPF
//        InkCanvas like features
// 
//-------------------------------------------------------------------------- 
function InkCanvas(inkPresenter, silverlightObj, strokesChangedCallback, strokesMovedCallback)
{
    if (inkPresenter == null)
    {
        throw "inkPresenter can not be null";
    }
    if (silverlightObj == null)
    {
        throw "a valid reference to the silverlight control must be passed";
    }

    setInkCanvasCallback(inkPresenter, "MouseLeftButtonDown", inkCanvasDelegate(this, this.handleMouseDown));
	setInkCanvasCallback(inkPresenter, "MouseMove", inkCanvasDelegate(this, this.handleMouseMove));
	setInkCanvasCallback(inkPresenter, "MouseLeftButtonUp", inkCanvasDelegate(this, this.handleMouseUp));
		
    this._stroke = null;
    this._editingMode = "Ink";
    this._editingModeInverted = "EraseByStroke";
    this._inkPresenter = inkPresenter;
    this._lastX = 0;
    this._lastY = 0;
    this._xMoveOffset = 0;
    this._yMoveOffset = 0;
    this._isDown = false;
    this._silverlightObj = silverlightObj;
    this._inkCanvasSelection = null;
    this._strokesChangedCallback = strokesChangedCallback;
    this._strokesMovedCallback = strokesMovedCallback;
    this._strokeId = 1;
    this._defaultDrawingAttributes = silverlightObj.content.CreateFromXaml("<DrawingAttributes/>");
    this._defaultDrawingAttributes.OutlineColor = "black";
    this._defaultDrawingAttributes.Color = "black";
}

//strokes get
InkCanvas.prototype.getStrokes = function()
{
    return this._inkPresenter.Strokes;
}

//strokes set
InkCanvas.prototype.setStrokes = function(strokes)
{
    this._inkPresenter.Strokes = strokes;
}

//DefaultDrawingAttributes get
InkCanvas.prototype.getDefaultDrawingAttributes = function()
{
    return this._defaultDrawingAttributes;
}

//DefaultDrawingAttributes set
InkCanvas.prototype.setDefaultDrawingAttributes = function(drawingAttributes)
{
    this._defaultDrawingAttributes = drawingAttributes;
}

//EditingMode get
InkCanvas.prototype.getEditingMode = function()
{
    return this._editingMode;
}

//EditingMode set
InkCanvas.prototype.setEditingMode = function(editingMode)
{
    var lowerEditingMode = editingMode.toLowerCase();
    if (lowerEditingMode != "ink" && lowerEditingMode != "erasebystroke" && lowerEditingMode != "select" && lowerEditingMode != "none")
    {
        throw "Unknown EditingMode";
    }
    if (lowerEditingMode == "ink")
    {
        this._editingMode = "Ink";
    }
    else if (lowerEditingMode == "erasebystroke")
    {
        this._editingMode = "EraseByStroke";
    }
    else if (lowerEditingMode == "select")
    {
        this._editingMode = "Select";
    }
    else if (lowerEditingMode == "none")
    {
        this._editingMode = "None";
    }
}

//EditingModeInverted get
InkCanvas.prototype.getEditingModeInverted = function()
{
    return this._editingModeInverted;
}

//EditingModeInverted set
InkCanvas.prototype.setEditingModeInverted = function(editingMode)
{
    var lowerEditingMode = editingMode.toLowerCase();
    if (lowerEditingMode != "ink" && lowerEditingMode != "erasebystroke" && lowerEditingMode != "select" && lowerEditingMode != "none")
    {
        throw "Unknown EditingMode";
    }
    if (lowerEditingMode == "ink")
    {
        this._editingModeInverted = "Ink";
    }
    else if (lowerEditingMode == "erasebystroke")
    {
        this._editingModeInverted = "EraseByStroke";
    }
    else if (lowerEditingMode == "select")
    {
        this._editingModeInverted = "Select";
    }
    else if (lowerEditingMode == "none")
    {
        this._editingModeInverted = "None";
    }
}

//EditingModeInverted get
InkCanvas.prototype.getActiveEditingMode = function(eventArgs)
{
    //check to see if we're in an inverted state
    if (eventArgs.getStylusInfo().IsInverted)
    {
        return this._editingModeInverted;
    }
    return this._editingMode;
}

//return any selected strokes
InkCanvas.prototype.getSelectedStrokes = function()
{
    if (this._inkCanvasSelection != null && 
        this._inkCanvasSelection.getHasSelection())
    {
        return this._inkCanvasSelection.getSelectedStrokes();
    }
    return null;
}

InkCanvas.prototype.getSelectionTopLeftPoint = function()
{
    var point = new SelectionPoint();
    point.X = 0;
    point.Y = 0;
    
    if (this.getSelectedStrokes() != null)
    {
        var selectionInkPresenter = this._inkCanvasSelection.getInkPresenter();
        point.X = selectionInkPresenter.GetValue("Canvas.Left");
        point.Y = selectionInkPresenter.GetValue("Canvas.Top");
    }
    
    return point;
}

//MouseDownLeftButtonDown handler
InkCanvas.prototype.handleMouseDown = function(sender, eventArgs) 
{
    var activeEditingMode = this.getActiveEditingMode(eventArgs);
	if (activeEditingMode != "None" && sender.CaptureMouse())
	{
        if (activeEditingMode == "Ink")
        {
            
            this._strokeId++;
            var uniqueName = this._inkPresenter.Name + "Stroke" + this._strokeId;
            var strokeXaml = "<Stroke xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' x:Name='" + uniqueName + "'/>";
            this._stroke = this._silverlightObj.content.createFromXAML(strokeXaml);
            this._inkPresenter.Strokes.Add(this._stroke);
            this._stroke.DrawingAttributes = this._defaultDrawingAttributes;

            var stylusPoints = 
                eventArgs.GetStylusPoints(sender);
            this._stroke.StylusPoints.AddStylusPoints(stylusPoints);
        }
        else if (activeEditingMode == "EraseByStroke" || activeEditingMode == "Select")
        {
            this._isDown = true;
            this._lastX = eventArgs.getPosition(sender).X;
            this._lastY = eventArgs.getPosition(sender).Y;
        }
        
        //do we have a selection that we need to clear?
        if (this._inkCanvasSelection != null)
        {
            var selectionInkPresenter = this._inkCanvasSelection.getInkPresenter();
            var xOffset = selectionInkPresenter.GetValue("Canvas.Left");
            var yOffset = selectionInkPresenter.GetValue("Canvas.Top");
            
            var unselectedStrokes = 
                this._inkCanvasSelection.removeSelectedStrokes(xOffset, yOffset);
        
            for (var i = 0; i < unselectedStrokes.Count; i++)
            {
                this._inkPresenter.Strokes.Add(unselectedStrokes.getItem(i));
            }
            this._inkPresenter.Children.Remove(this._inkCanvasSelection.getInkPresenter());
            this._inkCanvasSelection = null;
        
            this.raiseStrokesMoved(unselectedStrokes, xOffset - this._xMoveOffset, yOffset - this._yMoveOffset);
            
            this._xMoveOffset = 0;
            this._yMoveOffset = 0;
        }
    }
}

//MouseMove handler
InkCanvas.prototype.handleMouseMove = function(sender, eventArgs) 
{
    var activeEditingMode = this.getActiveEditingMode(eventArgs);
    if (activeEditingMode == "None")
    {
        return;
    }
    if (activeEditingMode == "Ink")
    {
        if (this._stroke != null)
        {
            var stylusPoints = 
                eventArgs.GetStylusPoints(sender);
            this._stroke.StylusPoints.AddStylusPoints(stylusPoints);
        }
    }
    else if (activeEditingMode == "EraseByStroke")
    {
        if (this._isDown)
        {   
            var stylusPoints = eventArgs.getStylusPoints(sender);
            var hitStrokes = this._inkPresenter.Strokes.HitTest(stylusPoints);
                         
            if (hitStrokes.Count > 0)
            {
                for (var i = 0; i < hitStrokes.Count; i++)
                {
                    this._inkPresenter.Strokes.Remove(hitStrokes.getItem(i));
                }    
                this.raiseStrokesChanged(null, hitStrokes);
            }                            
        
            this._lastX = eventArgs.getPosition(sender).X;
            this._lastY = eventArgs.getPosition(sender).Y;
        }
    }
    else if (activeEditingMode == "Select")
    {
        if (this._isDown)
        {
            if (this._inkCanvasSelection == null)
            {
                var inkPresenter = 
                    this._silverlightObj.content.createFromXAML("<InkPresenter Opacity='0.7' Background='LightBlue' />");
                this._inkPresenter.Children.Add(inkPresenter);
                this._inkCanvasSelection = new InkCanvasSelection(inkPresenter, this._silverlightObj);
            }
            
            var minX = Math.min(this._lastX, eventArgs.getPosition(sender).X);
            var minY = Math.min(this._lastY, eventArgs.getPosition(sender).Y);
            var maxX = Math.max(this._lastX, eventArgs.getPosition(sender).X);
            var maxY = Math.max(this._lastY, eventArgs.getPosition(sender).Y);
            var selectionInkPresenter = this._inkCanvasSelection.getInkPresenter();
            selectionInkPresenter.SetValue("Canvas.Left", minX);
            selectionInkPresenter.SetValue("Canvas.Top", minY);
            selectionInkPresenter.SetValue("Width", maxX - minX);
            selectionInkPresenter.SetValue("Height", maxY - minY);
        }
    }
}

//MouseDownLeftButtonUp handler
InkCanvas.prototype.handleMouseUp = function(sender, eventArgs) 
{
    var activeEditingMode = this.getActiveEditingMode(eventArgs);
    if (activeEditingMode == "None")
    {
        return;
    }
    
    sender.ReleaseMouseCapture();
    if (activeEditingMode == "Select" &&
        this._inkCanvasSelection != null && 
        !this._inkCanvasSelection.getHasSelection())
    {
        var minX = Math.min(this._lastX, eventArgs.getPosition(sender).X);
        var minY = Math.min(this._lastY, eventArgs.getPosition(sender).Y);
        var maxX = Math.max(this._lastX, eventArgs.getPosition(sender).X);
        var maxY = Math.max(this._lastY, eventArgs.getPosition(sender).Y);
        var selectionRect = new Rect();
        selectionRect.X = minX;
        selectionRect.Y = minY;
        selectionRect.Width = maxX - minX;
        selectionRect.Height = maxY - minY;
        
        var selectionResults = this.getStrokesInSelection(selectionRect);
        if (selectionResults.SelectedStrokes.Count > 0)
        {
            //we have a selection folks...
            
            var strokesBounds = this.getStrokeCollectionBounds(selectionResults.SelectedStrokes);
            var selectionInkPresenter = this._inkCanvasSelection.getInkPresenter();
            selectionInkPresenter.SetValue("Canvas.Left", strokesBounds.X);
            selectionInkPresenter.SetValue("Canvas.Top", strokesBounds.Y);
            selectionInkPresenter.SetValue("Width", strokesBounds.Width);
            selectionInkPresenter.SetValue("Height", strokesBounds.Height);
            
            this._inkPresenter.Strokes = selectionResults.UnselectedStrokes;
            this._inkCanvasSelection.setSelectedStrokes( selectionResults.SelectedStrokes,
                                                         strokesBounds.X,
                                                         strokesBounds.Y);
                                                         
            this._xMoveOffset = strokesBounds.X;
            this._yMoveOffset = strokesBounds.Y;
        }
        else if (this._inkCanvasSelection != null)
        {
            //remove... nothing was selected
            this._inkPresenter.Children.Remove(this._inkCanvasSelection.getInkPresenter());
            this._inkCanvasSelection = null;
        }
    }
    if (activeEditingMode == "Ink")
    {
        var addedStrokes = this._silverlightObj.content.createFromXaml("<StrokeCollection/>");
        if (this._stroke != null)
        {
        addedStrokes.Add(this._stroke);
        }
        this.raiseStrokesChanged(addedStrokes, null);
        this._stroke = null;
    }
    else if (activeEditingMode == "EraseByStroke" || activeEditingMode == "Select")
    {
        this._lastX = 0;
        this._lastY = 0;
        this._isDown = false;
    }
}

InkCanvas.prototype.getStrokeCollectionBounds = function(strokeCollection)
{
    var rect = new Rect();
    if (strokeCollection.Count > 0)
    {
        rect = this.getStrokeBounds(strokeCollection.GetItem(0));
        for (var i = 1; i < strokeCollection.Count; i++)
        {
            var stroke = strokeCollection.GetItem(i);
            rect = rect.getUnion(this.getStrokeBounds(stroke));
        }
    }
    return rect;
}

InkCanvas.prototype.getStrokeBounds = function(stroke)
{
//    The Silverlight Beta has a bug in it where it isn't updated
//    for programmatic changes to StylusPoints.  The following code
//    is all that is needed once this bug is fixed.
//    var strokeBounds = stroke.getBounds();

    var minX = 99999999;
    var maxX = -99999999;
    var minY = 99999999;
    var maxY = -99999999;
    
    for (var i = 0; i < stroke.StylusPoints.Count; i++)
    {
        var stylusPoint = stroke.StylusPoints.GetItem(i);
        if (stylusPoint.X < minX)
        {
            minX = stylusPoint.X;
        }
        if (stylusPoint.X > maxX)
        {
            maxX = stylusPoint.X;
        }
        if (stylusPoint.Y < minY)
        {
            minY = stylusPoint.Y;
        }
        if (stylusPoint.Y > maxY)
        {
            maxY = stylusPoint.Y;
        }
    }
    
    //inflate for pen width
    minX -= (stroke.DrawingAttributes.Width / 2) + 1;
    minY -= (stroke.DrawingAttributes.Height / 2) + 1;
    maxX += stroke.DrawingAttributes.Width + 2;
    maxY += stroke.DrawingAttributes.Height + 2;
    
    var rect = new Rect()
    rect.X = minX;
    rect.Y = minY;
    rect.Width = maxX - minX;
    rect.Height = maxY - minY;
    
    return rect;
}

//returns the selected and unselected strokes
InkCanvas.prototype.getStrokesInSelection = function(rect)
{
    var selectionResults = new SelectionResults();
    selectionResults.SelectedStrokes = this._silverlightObj.content.createFromXAML("<StrokeCollection/>");
    selectionResults.UnselectedStrokes = this._silverlightObj.content.createFromXAML("<StrokeCollection/>");
    
    for (var i = 0; i < this._inkPresenter.Strokes.Count; i++)
    {
        var stroke = this._inkPresenter.Strokes.GetItem(i);
        var strokeRect = this.getStrokeBounds(stroke);
        if (rect.contains(strokeRect))
        {
            selectionResults.SelectedStrokes.Add(stroke);
        }
        else
        {
            selectionResults.UnselectedStrokes.Add(stroke);
        }
    }
    return selectionResults;
}

//calls the strokes changed callback
InkCanvas.prototype.raiseStrokesChanged = function(addedStrokes, removedStrokes)
{
    if (this._strokesChangedCallback != null)
    {
        this._strokesChangedCallback(addedStrokes, removedStrokes);
    }
} 

//calls the strokes moved callback
InkCanvas.prototype.raiseStrokesMoved = function(movedStrokes, xOffset, yOffset)
{
    if (this._strokesMovedCallback != null)
    {
        this._strokesMovedCallback(movedStrokes, xOffset, yOffset);
    }
} 

// Helper method for creating per-object callbacks
/// @param target object to invoke the callback on
/// @param callback method to be called on the object
function inkCanvasDelegate(target, callback) {
	var func = function() {
		callback.apply(target, arguments);
	}
	return func;
}

/// Hook up the specified event to the specified inkCanvasDelegate.
/// Allows javascript inkCanvasDelegates to be used and dynamically
/// creates the global method.
/// @param target Silverlight element to set the event on
/// @param eventName name of the event to set, ie "MouseEnter"
/// @param inkCanvasDelegate function to call when the event occurs.
function setInkCanvasCallback(target, eventName, inkCanvasDelegate) {
	if (!window.methodID)
		window.methodID = 0;
	
	var callbackName = "uniqueCallback" + (window.methodID++);
	eval(callbackName + " = inkCanvasDelegate;");
	
	target.addEventListener(eventName, "javascript:" + callbackName);
}

function InkCanvasSelection(inkPresenter, silverlightObj)
{
    setInkCanvasCallback(inkPresenter, "MouseLeftButtonDown", inkCanvasDelegate(this, this.handleMouseDown));
	setInkCanvasCallback(inkPresenter, "MouseMove", inkCanvasDelegate(this, this.handleMouseMove));
	setInkCanvasCallback(inkPresenter, "MouseLeftButtonUp", inkCanvasDelegate(this, this.handleMouseUp));
    this._inkPresenter = inkPresenter;
    this._isDown = false;
    this._isEnabled = true;
    this._lastX = 0;
    this._lastY = 0;
    this._silverlightObj = silverlightObj;
    this._hasSelection = false;
}

//simple helper to ref the inkPresenter
InkCanvasSelection.prototype.getInkPresenter = function()
{
    return this._inkPresenter;
}

InkCanvasSelection.prototype.getHasSelection = function()
{
    return this._hasSelection;   
}

InkCanvasSelection.prototype.setSelectedStrokes = function(strokes, xOffset, yOffset)
{
    //translate from InkCanvas to InkCanvasSelection relative coordinates
    //xOffset is the dist from InkCanvas's origin to InkCanvasSelection.Left
    for (var i = 0; i < strokes.Count; i++)
    {
        var stroke = strokes.GetItem(i);
       
        for (var j = 0; j < stroke.StylusPoints.Count; j++)
        {
            var stylusPoint = stroke.StylusPoints.GetItem(j);
            stylusPoint.X -= xOffset;
            stylusPoint.Y -= yOffset;
        }
    }
    
    this._inkPresenter.Strokes = strokes;
    this._hasSelection = true;
}

InkCanvasSelection.prototype.getSelectedStrokes = function()
{
    if (this._hasSelection)
    {
        return this._inkPresenter.Strokes;
    }
    return null;
}

InkCanvasSelection.prototype.removeSelectedStrokes = function(xOffset, yOffset)
{
    var newStrokes = this._silverlightObj.content.CreateFromXAML("<StrokeCollection/>");
    for (var i = 0; i < this._inkPresenter.Strokes.Count; i++)
    {
        var stroke = this._inkPresenter.Strokes.GetItem(i);
        for (var j = 0; j < stroke.StylusPoints.Count; j++)
        {
            var stylusPoint = stroke.StylusPoints.GetItem(j);
            stylusPoint.X += xOffset;
            stylusPoint.Y += yOffset;
        }
        newStrokes.Add(stroke);
    }
    
    this._hasSelection = false;
    return newStrokes;
}

//MouseDownLeftButtonDown handler
InkCanvasSelection.prototype.handleMouseDown = function(sender, eventArgs) 
{
    if (this._isEnabled)
    {
        if (sender.CaptureMouse())
	    {
	        this._isDown = true;
	        this._lastX = eventArgs.getPosition(sender).X;
	        this._lastY = eventArgs.getPosition(sender).Y;
	    }
	}
}

//MouseMove handler
InkCanvasSelection.prototype.handleMouseMove = function(sender, eventArgs) 
{
    if (this._isEnabled && this._isDown)
    {
        var xDelta = eventArgs.getPosition(sender).X - this._lastX;
        var yDelta = eventArgs.getPosition(sender).Y - this._lastY;
        
        var left = this._inkPresenter.GetValue("Canvas.Left");
        var top = this._inkPresenter.GetValue("Canvas.Top");
        
        this._inkPresenter.SetValue("Canvas.Left", left + xDelta)
        this._inkPresenter.SetValue("Canvas.Top", top + yDelta)
        
        this._lastX = eventArgs.getPosition(sender).X;
        this._lastY = eventArgs.getPosition(sender).Y;
    }
}

InkCanvasSelection.prototype.handleMouseUp = function(sender, eventArgs) 
{
    if (this._isEnabled)
    {
        this._isDown = false;
    }
}

///rect primitive
function Rect()
{
    this.X = 0;
    this.Y = 0;
    this.Height = 0;
    this.Width = 0;
}

Rect.prototype.getIntersection = function(rect)
{
    var retRect = new Rect();
    var minX = Math.max(rect.X, this.X);
    var minY = Math.max(rect.Y, this.Y);
    var maxX = Math.min(rect.X + rect.Width, this.X + this.Width);
    var maxY = Math.min(rect.Y + rect.Height, this.Y + this.Height);
    
    if (minX <= maxX && minY <= maxY)
    {
        retRect.X = minX;
        retRect.Y = minY;
        retRect.Width = maxX - retRect.X;
        retRect.Height = maxY - retRect.Y;
    }
    return retRect;
}

Rect.prototype.getUnion = function(rect)
{
    var retRect = new Rect();
    var minX = Math.min(rect.X, this.X);
    var minY = Math.min(rect.Y, this.Y);
    var maxX = Math.max(rect.X + rect.Width, this.X + this.Width);
    var maxY = Math.max(rect.Y + rect.Height, this.Y + this.Height);
    
    if (minX <= maxX && minY <= maxY)
    {
        retRect.X = minX;
        retRect.Y = minY;
        retRect.Width = maxX - retRect.X;
        retRect.Height = maxY - retRect.Y;
    }
    return retRect;
}

Rect.prototype.contains = function(rect)
{
    if (rect.X >= this.X &&
        rect.Y >= this.Y &&
        rect.X + rect.Width <= this.X + this.Width &&
        rect.Y + rect.Height <= this.Y + this.Height)
    {
        return true;
    } 
    return false;    
}

function SelectionResults()
{
    this.SelectedStrokes = null;
    this.UnselectedStrokes = null;
}

function SelectionPoint()
{
    this.X = 0;
    this.Y = 0;
}
