// MathTIP.js

var defaultStrokeColor = "white";
var defaultStrokeOutlineColor = "gray";

var silverlightObj;
var inkPresenter;
var mathTipPanel;
var clearButton;
var clearButtonX;
var writeHereText;
var inkScale;
var horzRule;
var recoCanvas;
var mathText;

var currentRecoText = "";
var alternateNdxes;
var alternates;
var topRecoText;

var newStroke;
var erasePoints = null;
var newStroke1;
var lastStroke;
var sawMouseLeave = false;
var strokeNdx = -1;

var initialWd = 1;
var initialHt = 1;

function root_Loaded(sender, args) 
{
    silverlightObj = document.getElementById("silverlightObj");
    inkPresenter = sender.findname("inkPresenterElement");
    mathTipPanel = sender.findname("mathTipPanel");
    clearButton = sender.findname("clearButton");
    clearButtonX = sender.findname("clearButtonX");
    writeHereText = sender.findname("writeHereText");
    inkScale = sender.findName("inkScale");
    horzRule = sender.findName("horzRule");
    recoCanvas = sender.findName("recoCanvas");
    mathText = sender.findName("mathText");

    // Retrieve a reference to the control.
    control = sender.getHost();

    // Set the event handler function for the OnResize event.
    control.content.onResize = onResize;

    initialWd = silverlightObj.clientWidth;
    initialHt = silverlightObj.clientHeight;
    if (initialWd == 0) initialWd = 1;  // Get around divide by 0
    if (initialHt == 0) initialHt = 1;

    // Do initial layout of the app based on initial size.
    //updateLayout(control.content.actualWidth, control.content.actualHeight);
    //alert(silverlightObj.clientHeight);
    updateLayout(silverlightObj.clientWidth, silverlightObj.clientHeight);
}

function onResize(sender, eventArgs)
{
    updateLayout(control.content.actualWidth,
                 control.content.actualHeight);
}

// Resize and reposition application elements.
function updateLayout(width, height)
{
    // Perform layout tasks based on width and height.
    mathTipPanel.Width = width;
    mathTipPanel.Height = height;

    recoCanvas.Width = width;
    recoCanvas.Height = height;

    var scaleX = width / initialWd;
    var scaleY = height / initialHt;

    inkScale.scaleX = scaleX;
    inkScale.scaleY = scaleY;

    // 20070916: Fixed inkPresenter scaling bug when
    // window reduced in width 
    if (scaleX != 0)
        inkPresenter.Width = width / scaleX;
    else
        inkPresenter.Width = 0;
    inkPresenter.Height = height;

    horzRule.width = width - 20;
    mathText["Canvas.Left"] = width - 68;
    clearButton["Canvas.Left"] = width - 32;
    clearButton["Canvas.Top"] = height - 36 - 28;
    clearButtonX["Canvas.Left"] = width - 32 + 6;
    clearButtonX["Canvas.Top"] = height - 36 - 28;

    horzRule["Canvas.Top"] = height - 36;
    
    recoCanvas.Height = 50;
    recoCanvas["Canvas.Top"] = height - 36 + 6;
}

function RootMouseEnter(sender, args)
{
    var spc = args.GetStylusPoints(inkPresenter);
    if (newStroke != null)                                     
    {                                                        
        if (sawMouseLeave && (spc.Count <= 1))                
        {                    
            // Ignore stroke since it happened outside the control
            newStroke = null;
        }                                                    
        else                                                 
        {                                                    
            newStroke.StylusPoints.AddStylusPoints(spc);     
            if (sawMouseLeave)                                
            {                                                
                sawMouseLeave = false;        
                inkPresenter.CaptureMouse();  
            }                                                
        }                                                    
    }                                                        
}
            
function RootMouseLeave(sender, args)
{
    sawMouseLeave = true;
}
            
function InkPresenterMouseDown(sender, args)
{
    // Hide writing prompt as soon as the user starts writing
    writeHereText.Visibility = "Collapsed";

    sawMouseLeave = false;
    inkPresenter.CaptureMouse();
    var spc = args.GetStylusPoints(inkPresenter);
    if (args.GetStylusInfo().isInverted)
    {
        erasePoints = silverlightObj.Content.CreateFromXaml('<StylusPointCollection/>');
        erasePoints.AddStylusPoints(args.GetStylusPoints(inkPresenter));
    }
    else    {
        newStroke = control.Content.CreateFromXaml('<Stroke/>');
        UpdateDAs(newStroke);
        lastStroke = newStroke;
        newStroke.StylusPoints.AddStylusPoints(spc);
        strokeNdx = inkPresenter.Strokes.Add(newStroke);
    }
}
            
function InkPresenterMouseMove(sender, args)
{
    // Update the cursor, only when not inking or erasing
    if (newStroke == null)
    {
        styInfo = args.GetStylusInfo();
        if (!styInfo.IsInverted) 
            inkPresenter.cursor = "Stylus";
        else if(styInfo.IsInverted) 
            inkPresenter.cursor = "Eraser";
    }

    // Determine inking or erasing mode
    var spc = args.GetStylusPoints(inkPresenter); 
    if (args.GetStylusInfo().IsInverted && erasePoints != null)
    {
        // Erasing
        erasePoints.AddStylusPoints(args.GetStylusPoints(inkPresenter));
        var hitStrokes = inkPresenter.Strokes.HitTest(erasePoints);
        for (var i = 0; i < hitStrokes.Count; i++)
        {
            inkPresenter.Strokes.Remove(hitStrokes.GetItem(i));
        }
    }
    else if (newStroke != null)
    {
        // Inking
        newStroke.StylusPoints.AddStylusPoints(spc);
    }
}

function InkPresenterMouseUp(sender, args)
{
    var spc = args.GetStylusPoints(inkPresenter);

    // Release capture
    inkPresenter.ReleaseMouseCapture();  
    
    // Add final styluspoints
    if (newStroke != null)
    {
        newStroke.StylusPoints.AddStylusPoints(spc);

        // Remove stroke just added if it is a strikeout gesture.
        // Delete strokes below it too.
        if (isStrikeoutGesture(newStroke))
        {
            strikeout(newStroke);
            inkPresenter.Strokes.Remove(newStroke);

            if (inkPresenter.Strokes.Count == 0)
                currentRecoText = "";
        }    
    }

    clearRecognizedText();
    updateButtonsAndText();
    
    newStroke = null;
    erasePoints = null;
}

function updateButtonsAndText()
{
    if (inkPresenter.Strokes.Count > 0)
    {
        showClearButton(true);
        mathText.Foreground = "#FF000000";
        recognizeText();
    }
    else
    {
        showClearButton(false);
        mathText.Foreground = "#FFCCCCCC";
    }
}

function showClearButton(visible)
{
    if (visible)
    {
        clearButton.Visibility = "Visible";
        clearButtonX.Visibility = "Visible";
    }
    else
    {
        clearButton.Visibility = "Collapsed";
        clearButtonX.Visibility = "Collapsed";
    }
}

function InkPresenterMouseEnter(sender, args)
{
}
 
function InkPresenterMouseLeave(sender, args)
{
    sawMouseLeave = true;                                    
}

function UpdateDAs(newStroke)
{
/*
    // use the text inputs on the htm page to update drawing  
    //  attributes for the current stroke
    newDAs = newStroke.DrawingAttributes;
                
    color = iColor.value;
    if (color != '')
        newDAs.Color = color;
    else 
        newDAs.Color = 'Blue';
                    
    outline = iOutline.value;
    newDAs.OutlineColor = outline;
                    
    height = iHeight.value;
    if (height != '')
        newDAs.Height = height;
    else 
        newDAs.Height = 2;
                    
    width = iWidth.value;
    if (width != '')
        newDAs.Width = width;
    else 
        newDAs.Width = 2;
*/
}

function onMouseDownClear(sender, args)
{
    showClearButton(false);
    inkPresenter.Strokes.Clear();
    clearRecognizedText();
    currentRecoText = null;
    mathText.Foreground = "#FFCCCCCC";
}

function onMouseDownSearch(sender, args)
{
    if (currentRecoText != null && currentRecoText != "")
    {
        inkPresenter.cursor = "Hand";
    
        search(currentRecoText);
    }
}

function onMouseMoveSearch(sender, args)
{
    inkPresenter.cursor = "Hand";
}

function onMouseEnterSearch(sender, args)
{
    inkPresenter.cursor = "Hand";

    if (currentRecoText != null && currentRecoText != "")
    {
        mathText.Foreground = "#FF007700";
    }
}

function onMouseLeaveSearch(sender, args)
{
    inkPresenter.cursor = "Stylus";
        
    if (currentRecoText != null && currentRecoText != "")
    {
        mathText.Foreground = "#FF000000";
    }
}

////////////////////////////////////////////////////////////////////////////
//
// Recognition support
//

function recognizeText()
{
    var xmlInk = saveStrokesXYOnly(inkPresenter.Strokes);
    //alert(xmlInk);
    PageMethods.GetRecognitionResult(xmlInk, 1033, OnSucceeded, OnFailed, strokeNdx);
}

function clearRecognizedText()
{
    recoCanvas.children.clear();
}

function updateRecognizedText(txt)
{
    clearRecognizedText();
    showRecognizedText(txt);
    currentRecoText = txt;
}

function showRecognizedText(txt)
{
    if (txt == null)
        return;
        
    // Simple splitting for this example. Should be improved to handle
    // punctuation better.
    var words = txt.split(" ");
    showRecognizedWords(words);
}

function showRecognizedWords(words)
{
    var offset = 10;
    var wordSep = 8;

    var previousWordExists = false;
    for (var i=0; i<words.length; i++)
    {
        if (words[i] != null && words[i] != "")
        {
            if (previousWordExists)
                offset += wordSep;
            else
                previousWordExists = true;
            offset += appendRecognizedWord(words[i], offset);
        }
    }
}

function createLinearGradientBrush(plugin)
{
    // Define a XAML fragment.
    var xamlFragment = '<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">';
       xamlFragment +=   '<GradientStop Color="#FFFEFAE0" Offset="0.0" />';
       xamlFragment +=   '<GradientStop Color="#FFFEFAE0" Offset="0.5" />';
       xamlFragment +=   '<GradientStop Color="#FFE2D99F" Offset="1.0" />';
       xamlFragment += '</LinearGradientBrush>';

    // Create the XAML fragment and return it.
    return plugin.content.createFromXaml(xamlFragment);
}

function appendRecognizedWord(txt, offset)
{
    var left = offset;
    var top = 0; //horzRule["Canvas.Top"] + 6;
    var wordMargin = 3;

    var textBlock = silverlightObj.Content.CreateFromXaml('<TextBlock/>');
    textBlock["Canvas.Left"] = left + wordMargin;
    textBlock["Canvas.Top"] = top;
    textBlock.Text = txt;
    textBlock.IsHitTestVisible = false;

    var containerWd = textBlock.actualWidth + wordMargin*2;
    var containingRect = silverlightObj.Content.CreateFromXaml('<Rectangle/>');
    containingRect["Canvas.Left"] = left;
    containingRect["Canvas.Top"] = top;
    containingRect.Height = textBlock.actualHeight;
    containingRect.Width = containerWd;
    containingRect.Stroke = "black";
    containingRect.StrokeThickness = 1;
    containingRect.RadiusX = 3;
    containingRect.RadiusY = 3;
    containingRect.IsHitTestVisible = true;
    
    containingRect.Fill = createLinearGradientBrush(silverlightObj);

    containingRect.AddEventListener("MouseLeftButtonDown", "onWordClicked"); 
    
    recoCanvas.children.add(containingRect);
    recoCanvas.children.add(textBlock);

    return containerWd;
}

function findObjectLeftAlignedToInCanvas(canvas, leftPos)
{
    for (i=0; i<canvas.children.count-1; i++)
    {
        var item = canvas.children.getItem(i);
        if (item["Canvas.Left"] == leftPos)
        {
            return i;
        }
    }
    return -1;
}

// The reco results are in the recoCanvas and are ordered as:
// rectangle, text, rectangle, text, and so on. The user has
// clicked on a rectangle. Find the matching text for it that
// will be updated with a text alternate if there is one.
function onWordClicked(sender, args)
{
    var ndx = findObjectLeftAlignedToInCanvas(recoCanvas, sender["Canvas.Left"]);
    if (ndx == -1)
        return;
        
    // Next index is text object to change
    ndx /= 2;
    //var textBlock = recoCanvas.children.getItem(ndx);
    //textBlock.Text = "Red";

    clearRecognizedText();
    var words = replaceWordWithAlternate(ndx);
    currentRecoText = makeString(words);
    showRecognizedWords(words);
}

function makeString(words)
{
    var txt = "";
    for (i=0; i<words.length; i++)
    {
        if (i != 0)
            txt += " ";

        txt += words[i];        
    }
    return txt;
}

function getWord(txt, ndx)
{
    var words = txt.split(" ");
    if (ndx >= 0 && ndx < words.length)
        return words[ndx];
    else
        return null;
}

function replaceWordWithAlternate(ndx)
{
    var words = currentRecoText.split(" ");
    if (ndx >= 0 && ndx < words.length)
    {
        if (alternates != null && alternates[ndx] != null)
        {
            altNdxes[ndx]++;
            if (altNdxes[ndx] >= alternates[ndx].length ||
                (alternates[ndx][altNdxes[ndx]] == null ||
                 alternates[ndx][altNdxes[ndx]] == ""))
            {
                // Use "top" word for reco
                var word = getWord(topRecoText, ndx);
                altNdxes[ndx] = -1;
                if (word != null && word != "")
                    words[ndx] = word;
            }
            else
            {
                var word = alternates[ndx][altNdxes[ndx]];
                words[ndx] = word;
            }
        }
    }
    return words;
}

function search(txt)
{
    var q = "http://www.google.com/search?q=" + encodeURIComponent(txt);
    //parent.frames[0].location.href = q;
    // Use named frame for Safari
    parent.frames['MathFrame'].location.href = q;
    //document.frames[0].document.href = q;
}

///////////////////////////////////////////////////////////////////////////////
//
// Saving and loading stroke collection

// Returns a stroke collection loaded from the passed in XML node.  
// NOTE: xmlNode must have a child <SC> node for this routine to succeed.
function loadStrokes(xmlNode)
{
  var loadedStrokes = silverlightObj.content.CreateFromXaml('<StrokeCollection/>');
      
  var strokes = xmlNode.selectNodes("./sc/s");
  if (strokes != null)
  {
    // for each stroke, get the stylus points
    for (var j = 0; j < strokes.length; j++)
    {
      var s = strokes(j);
      var sps = s.selectNodes("./sp");

      var newS = silverlightObj.content.CreateFromXaml('<Stroke/>');
     
      var c = s.getAttribute("c");
      var oc = s.getAttribute("oc");
      var w = s.getAttribute("w");
      var h = s.getAttribute("h");
      var o = s.getAttribute("o");
      
      if (c != null) newS.TipOpacity = o;
      if (w != null) newS.DrawingAttributes.Width = w;
      if (h != null) newS.DrawingAttributes.Height = h;
      if (c != null) newS.DrawingAttributes.Color = c;
      if (oc != null) newS.DrawingAttributes.OutlineColor = oc;
      
      var newSPC = silverlightObj.content.CreateFromXaml('<StylusPointCollection/>');
      //alert("newS=" + newS + " newSPC=" + newSPC);

      //for each stylusPoint, get x, y, p
      for (var k = 0; k < sps.length; k++)
      {
        var stylusPoint = sps(k);
        var x = stylusPoint.getAttribute("x");
        var y = stylusPoint.getAttribute("y");
        var p = stylusPoint.getAttribute("p");
        var newSP = silverlightObj.CreateFromXaml('<StylusPoint/>');
        //alert("newSP=" + newSP);
        newSP.X = x;
        newSP.Y = y;
        if (p != null) newSP.PressureFactor = p;
        //alert("newSP x=" + newSP.X + " y=" + newSP.Y);
        newSPC.Add(newSP);
      }
      
      newS.StylusPoints.Add(newSPC);
      
      loadedStrokes.Add(newS);
    }
  }
  
  return loadedStrokes;
}

// Returns a string that is the XML for the persisted strokes.
//function saveStrokes(strokeCollection)
//{
//  var inkXML = "<sc>";
//    
//  if (strokeCollection != null)
//  {
//    var strokeCount = strokeCollection.Count;

//    for (i=0; i<strokeCount; i++)
//    {
//      var s = strokeCollection.GetItem(i);
//      inkXML += saveStroke(s);
//    }
//  }
//  
//  inkXML += "</sc>";
//  
//  return inkXML;
//}

function saveStrokesXYOnly(strokeCollection)
{
  var inkXML = "<sc>";
    
  if (strokeCollection != null)
  {
    var strokeCount = strokeCollection.Count;

    for (i=0; i<strokeCount; i++)
    {
      var s = strokeCollection.GetItem(i);
      inkXML += saveStrokeXYOnly(s);
    }
  }
  
  inkXML += "</sc>";
  
  return inkXML;
}

// Build an xml string with XY coordinates only in a condensed fashion
// to make the remote reco as fast as feasible.
function saveStrokeXYOnly(s)
{
  var inkXML = "<s>";

  var stylusPointCount = s.StylusPoints.Count;

  for (j=0; j<stylusPointCount; j++)
  {
    var sp = s.StylusPoints.GetItem(j);
    inkXML += "<sp x='" + sp.X + "' y='" + sp.Y + "' />";
  }

  inkXML += "</s>";
  return inkXML;
}

//function saveStroke(s)
//{
//  var inkXML = "<s c='" + s.DrawingAttributes.Color + "' oc='" + s.DrawingAttributes.OutlineColor;
//  inkXML += "' w='" + s.DrawingAttributes.Width + "' h='" + s.DrawingAttributes.Height + "' >";

//  var stylusPointCount = s.StylusPoints.Count;

//  for (j=0; j<stylusPointCount; j++)
//  {
//    var sp = s.StylusPoints.GetItem(j);
//    inkXML += "<sp x='" + sp.X + "' y='" + sp.Y + "' p='" + sp.PressureFactor +  "' />";
//  }

//  inkXML += "</s>";
//  return inkXML;
//}

//////////////////////////////////////////////////////////////////////
//
// Local gesture recognition

// Returns true iff this stroke is determined to be a strikeout gesture.
// A strikeout gesture is defined here as a single right to left horizontal
// stroke that is greater or equal in length to some minimal size.
function isStrikeoutGesture(stroke)
{
    var minStrikeoutGetstureLen = 60;
    
    var cnt = stroke.StylusPoints.Count;
    if (cnt >= 2)
    {
        var startPt = stroke.StylusPoints.GetItem(0);
        var endPt = stroke.StylusPoints.GetItem(cnt-1);
        if (startPt.X - endPt.X > minStrikeoutGetstureLen)
        {
            var bounds = stroke.GetBounds();
            return bounds.Height < bounds.Width / 5;
        }
    }
    return false;
}

// Note this will likely miss horizontal strokes, dots above i's,
// and other small marks.
function strikeout(stroke)
{
    var hitStrokes = inkPresenter.Strokes.HitTest(stroke.StylusPoints);
    for (var i = 0; i < hitStrokes.Count; i++)
    {
        inkPresenter.Strokes.Remove(hitStrokes.GetItem(i));
    }
}

//for a given strokecollection, covert it to it's xaml representation
/*function convertStrokeCollectionToXaml(strokeCollection)
{
    var xaml = "<StrokeCollection>";
    if (strokeCollection != null)
    {
        for (var i = 0; i < strokeCollection.Count; i++)
        {
            var stroke = strokeCollection.GetItem(i);
            xaml += "<Stroke><Stroke.DrawingAttributes>";
            xaml += "<DrawingAttributes ";
            xaml += "Color='" + stroke.DrawingAttributes.Color + "' ";
            xaml += "OutlineColor='" + stroke.DrawingAttributes.OutlineColor + "' ";
            xaml += "Width='" + stroke.DrawingAttributes.Width + "' ";
            xaml += "Height='" + stroke.DrawingAttributes.Height + "' ";
            xaml += "/></Stroke.DrawingAttributes>";
            
            xaml += "<Stroke.StylusPoints>";
            for (var j = 0; j < stroke.StylusPoints.Count; j++)
            {
                var stylusPoint = stroke.StylusPoints.GetItem(j);
                xaml += "<StylusPoint X='" + roundToTwoDecimalPlaces(stylusPoint.X) + "' Y='" + roundToTwoDecimalPlaces(stylusPoint.Y) + "' />";
            }
            xaml += "</Stroke.StylusPoints></Stroke>";
        }
    }
    xaml += "</StrokeCollection>";
    
    return xaml;
}
*/

//simple method to round a number
function roundToTwoDecimalPlaces(number)
{
    var wholeNumber = Math.floor(number);
    var fraction = number - wholeNumber;
    fraction *= 100;
    fraction = Math.round(fraction);
    fraction /= 100;
    return wholeNumber + fraction; 
}


