///////////////////////////////////////////////////////////////////////////////
//
//  File: InkCanvas.js
//
//  Implements a basic inking surface for Silverlight
//  by wrapping an InkPresenter and providing WPF
//  InkCanvas like features
//
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// InkCanvas class
function InkCanvas(
            inkPresenter, // InkPresenter used by InkCanvas object
            silverlightObj, // Silverlight control instance
            strokesChangedCallback, // Callback for strokes changed
            strokesMovedCallback) // Callback for strokes moved
{
    // Parameter validation
    if (inkPresenter == null)
    {
        throw "inkPresenter can not be null";
    }
    if (silverlightObj == null)
    {
        throw "a valid reference to the silverlight control must be passed";
    }

    // Mouse event handler setup
    setInkCanvasCallback(inkPresenter, "MouseLeftButtonDown", inkCanvasDelegate(this, this.handleMouseDown));
    setInkCanvasCallback(inkPresenter, "MouseMove", inkCanvasDelegate(this, this.handleMouseMove));
    setInkCanvasCallback(inkPresenter, "MouseLeftButtonUp", inkCanvasDelegate(this, this.handleMouseUp));
        
    // Parameter initialization
    this._stroke = null;
    this._erasePoints = 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 property get
InkCanvas.prototype.getStrokes = function()
{
    return this._inkPresenter.Strokes;
}

// Strokes property 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 a mode other than "Select" is being set, clear the current selection
    else if (lowerEditingMode != "select")
    {
        inkCanvas.clearSelection();
    }
    
    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 - gets the editing mode for the back of the pen (defaults to erase)
InkCanvas.prototype.getEditingModeInverted = function()
{
    return this._editingModeInverted;
}

// EditingModeInverted set - sets the editing mode for the back of the pen (defaults to erase)
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";
    }
}

// ActiveEditingMode get
InkCanvas.prototype.getActiveEditingMode = function(eventArgs)
{
    var activeMode;
    //check to see if we're in an inverted state
    if (eventArgs.getStylusInfo().IsInverted)
    {
        activeMode = this._editingModeInverted;
    }
    else
    {
        activeMode = this._editingMode;
    }
    
    // set the cursor
    if (activeMode == "Ink")
    {
        this._inkPresenter.cursor = "Stylus";
    }
    if (activeMode == "Select")
    {
        this._inkPresenter.cursor = "Hand";
    }
    if (activeMode == "EraseByStroke")
    {
        this._inkPresenter.cursor = "Eraser";
    }
    return activeMode;
}

// Gets currently selected Strokes
InkCanvas.prototype.getSelectedStrokes = function()
{
    if (this._inkCanvasSelection != null && 
        this._inkCanvasSelection.getHasSelection())
    {
        return this._inkCanvasSelection.getSelectedStrokes();
    }
    return null;
}

// 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);
            if (this._stroke == null)
            {}
            else
            {
                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._erasePoints = this._silverlightObj.content.createFromXaml('<StylusPointCollection/>');
            this._erasePoints.addStylusPoints(eventArgs.getStylusPoints(this._inkPresenter));
            this._lastX = eventArgs.getPosition(sender).X;
            this._lastY = eventArgs.getPosition(sender).Y;
        }
        
        this.clearSelection();
    }
}

// Clear any strokes that are selected
InkCanvas.prototype.clearSelection = function(sender, eventArgs) 
{
    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
        {   // added to keep updating lat/long
            var pos = eventArgs.getPosition(sender);
            onMoveDisplayDebug(pos.x, pos.y);
        }
    }
    else if (activeEditingMode == "EraseByStroke")
    {
        if (this._isDown)
        {   
            var stylusPoints = eventArgs.getStylusPoints(sender);
            this._erasePoints.addStylusPoints(stylusPoints);
            var hitStrokes = this._inkPresenter.Strokes.HitTest(this._erasePoints);
                         
            if (hitStrokes.Count > 0)
            {
                for (var i = 0; i < hitStrokes.Count; i++)
                {
                    this._inkPresenter.Strokes.Remove(hitStrokes.getItem(i));
                }    
                this.raiseStrokesChanged(null, hitStrokes);
            }
            var lastErasePoint = stylusPoints.GetItem(stylusPoints.count-1);
            this._erasePoints.clear();
            this._erasePoints.add(lastErasePoint);                    
            
            this._lastX = lastErasePoint.X; // Keep the last point from this MouseMove
            this._lastY = lastErasePoint.Y; // for use in the next MouseMove
        }
         else
        {   // added to keep updating lat/long
            var pos = eventArgs.getPosition(sender);
            onMoveDisplayDebug(pos.x, pos.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 pos = eventArgs.getPosition(sender);
            var minX = Math.min(this._lastX, pos.x);
            var minY = Math.min(this._lastY, pos.y);
            var maxX = Math.max(this._lastX, pos.x);
            var maxY = Math.max(this._lastY, pos.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 pos = eventArgs.getPosition(sender);
        var minX = Math.min(this._lastX, pos.x);
        var minY = Math.min(this._lastY, pos.y);
        var maxX = Math.max(this._lastX, pos.x);
        var maxY = Math.max(this._lastY, pos.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 = selectionResults.SelectedStrokes.getBounds();
            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;
        this._erasePoints = null;
    }
}

// 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 = stroke.getBounds();
        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
//  target - object to invoke the callback on
//  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.
//  target - Silverlight element to set the event on
//  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, callbackName);
}

///////////////////////////////////////////////////////////////////////////////
// InkCanvasSelection class
// Defines the canvas used to show strokes which are selected
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;
}

// Return the current selection
InkCanvasSelection.prototype.getHasSelection = function()
{
    return this._hasSelection;   
}

// Set the selected strokes
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;
}

// Get the selected strokes
InkCanvasSelection.prototype.getSelectedStrokes = function()
{
    if (this._hasSelection)
    {
        return this._inkPresenter.Strokes;
    }
    return null;
}

// Remove selected strokes
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;
    }
}

// MouseUp handler
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;
}

// Returns true of the "this" object contains the rectangle passed
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;    
}

///////////////////////////////////////////////////////////////////////////////
// SelectionResults primitive
function SelectionResults()
{
    this.SelectedStrokes = null;
    this.UnselectedStrokes = null;
}