var ucsScreen = new Ucs();    
var dScreenScale = -1;
var COLOR_ECHO_POINT1 = "green"
var COLOR_ECHO_POINT2 = "lime"
var COLOR_RELATIVE_DISTANCE = "blue";
var COLOR_RELATIVE_SPEED = "orange";
var COLOR_SELF_SPEED = "magenta";
var COLOR_ABSOLUTE_SPEED = "red";
var PORTSIDE = -1;
var STARBOARDSIDE =1;
var ARROW_LENGTH = 20;
var ARROW_WIDTH = 5;    

// Pnt type to us in DrawPoint
var PNT_FILLED_CIRCLE = 1;
var PNT_EMPTY_CIRCLE = -1;
var PNT_FILLED_SQUARE = 2;
var PNT_EMPTY_SQUARE = -2;
var PNT_FILLED_TRIANGLE = 3;
var PNT_EMPTY_TRIANGLE = -3;
var PNT_FILLED_SQUARE_HORIZ = 4;
var PNT_EMPTY_SQUARE_HORIZ = -4;
var PNT_FILLED_TRIANGLE_HORIZ = 5;
var PNT_EMPTY_TRIANGLE_HORIZ = -5;

function BoatSpeedSolution()
{
    this.vSpeed         = new Pnt();
    this.pTangentPoint  = new Pnt();    
    this.vRelSpeed      = new Pnt();        
    this.iSide          = 0;
    this.timeToTarget   = new TimeCoord();
}

function DrawScale( x, y, l, scale, color, jg )
{
    oldColor = jg.color;
    oldStroke = jg.stroke;    
    jg.setColor( color );
    jg.setStroke( 3 );
    jg.drawLine( x, y, x + l, y );
    jg.setStroke( 1 );
    jg.drawLine( x, y, x, y - 3 );    
    jg.drawLine( x + l + 2, y, x + l + 2, y - 3 );        
    jg.drawString( scale, x + l + 2 + 15, y - 10 );        
    jg.setColor( oldColor );
    jg.setStroke( oldStroke );    
}

function DrawArrow( x0, y0, x1, y1, arrowLength, arrowWidth, jg )
{
    l = Math.sqrt( ( x1 - x0 )*( x1 - x0 ) + ( y1 - y0 )*( y1 - y0 ) );
    if ( arrowLength > l )
        arrowLength = l / 2;
    x2 = x0 + ( 1 - arrowLength / l ) * ( x1 - x0 ) ;
    y2 = y0 + ( 1 - arrowLength / l ) * ( y1 - y0 ) ;    
    alpha = Math.atan2( y1 - y0, x1 - x0 );
    x2a = arrowWidth * Math.cos( alpha + Math.PI / 2 );
    y2a = arrowWidth * Math.sin( alpha + Math.PI / 2 );    
    x2b = x2 - x2a;
    y2b = y2 - y2a;
    x2a = x2 + x2a;
    y2a = y2 + y2a;
    
    old = jg.stroke;
    jg.drawLine( x0, y0, x2, y2 );    
    jg.setStroke( 1 );
    jg.fillPolygon( Array( x2a,x2b,x1 ),  Array( y2a,y2b,y1 ) );
    jg.setStroke( old );    
    
}

function DrawPoint( x, y, color,size, jg )
{
    DrawPoint2( x, y, color,size, jg, PNT_FILLED_CIRCLE );
}

function DrawPoint2( x, y, color,size, jg, type )
{
    var aXVertex = new Array(); 
    var aYVertex = new Array();
    oldColor = jg.color;
    oldStroke = jg.stroke;    
    jg.setColor( color );

    if ( type == PNT_EMPTY_CIRCLE )
    {
        jg.drawEllipse( x-size,y-size,size*2,size*2 );    
    }
    else if ( type == PNT_FILLED_SQUARE || type == PNT_EMPTY_SQUARE ) 
    {
        for( var i = 0; i < 4; i++ )
        {
            aXVertex[ i ] = x + size * Math.cos( i * 90 * Math.PI / 180 );        
            aYVertex[ i ] = y + size * Math.sin( i * 90 * Math.PI / 180 );                    
        }
        aXVertex[ i ] = aXVertex[ 0 ];
        aYVertex[ i ] = aYVertex[ 0 ];
        if ( type == PNT_FILLED_SQUARE ) 
            jg.fillPolygon( aXVertex, aYVertex );    
        else
            jg.drawPolygon( aXVertex, aYVertex );            
    }
    else if ( type == PNT_FILLED_SQUARE_HORIZ || type == PNT_EMPTY_SQUARE_HORIZ ) 
    {
        for( var i = 0; i < 4; i++ )
        {
            aXVertex[ i ] = x + size * Math.cos( ( 45 + ( i * 90 ) ) * Math.PI / 180 );        
            aYVertex[ i ] = y + size * Math.sin( ( 45 + ( i * 90 ) ) * Math.PI / 180 );                    
        }
        aXVertex[ i ] = aXVertex[ 0 ];
        aYVertex[ i ] = aYVertex[ 0 ];
        if ( type > 0 ) 
            jg.fillPolygon( aXVertex, aYVertex );    
        else
            jg.drawPolygon( aXVertex, aYVertex );            
    }
    else if ( type == PNT_FILLED_TRIANGLE || type == PNT_EMPTY_TRIANGLE ) 
    {
        for( var i = 0; i < 3; i++ )
        {
            aXVertex[ i ] = x + size * Math.cos( i * 120 * Math.PI / 180 );        
            aYVertex[ i ] = y + size * Math.sin( i * 120 * Math.PI / 180 );                    
        }
        aXVertex[ i ] = aXVertex[ 0 ];
        aYVertex[ i ] = aYVertex[ 0 ];
        if ( type > 0 ) 
            jg.fillPolygon( aXVertex, aYVertex );    
        else
            jg.drawPolygon( aXVertex, aYVertex );            
    }
    else if ( type == PNT_FILLED_TRIANGLE_HORIZ || type == PNT_EMPTY_TRIANGLE_HORIZ ) 
    {
        for( var i = 0; i < 3; i++ )
        {
            aXVertex[ i ] = x + size * Math.cos( ( 30 + ( i * 120 ) ) * Math.PI / 180 );        
            aYVertex[ i ] = y + size * Math.sin( ( 30 + ( i * 120 ) ) * Math.PI / 180 );                    
        }
        aXVertex[ i ] = aXVertex[ 0 ];
        aYVertex[ i ] = aYVertex[ 0 ];
        if ( type > 0 ) 
            jg.fillPolygon( aXVertex, aYVertex );    
        else
            jg.drawPolygon( aXVertex, aYVertex );            
    }
    else
    {
        //default: filled circle
        jg.fillEllipse( x-size,y-size,size*2,size*2 );
    }

    jg.setColor( oldColor );
    jg.setStroke( oldStroke );    
}

function DrawBackground( jg, graphElem, ucsScreen )
{
    oldColor = jg.color;
    oldStroke = jg.stroke;    

    pX = new Pnt();
    pX.Set( 0, 1, 0 );
    ucsScreen.SetX( pX );
    pY = new Pnt();    
    pY.Set( -1, 0, 0 );
    ucsScreen.SetY( pY );
    ucsScreen.MakeZ();

    if ( graphElem.clientHeight < graphElem.clientWidth ) 
        dim = graphElem.clientHeight - 2;
    else
        dim = graphElem.clientWidth - 2;    
        
    pOrigin = new Pnt();
    pOrigin.Set( dim / 2, -dim / 2, 0 ) ;
    ucsScreen.SetOrigin( pOrigin );
        
    pCenter = ucsScreen.WorldToUcs( new Pnt() );
    
    incr = dim / 10;
    scale = incr / 2;
    jg.setStroke(this.DOTTED);
    
    nCircles = 10;
    //  drawing the grid takes too much, replaced by a gif: grid.gif
    // var i;
    //for( i = 1; i <= nCircles; i++ )
    //{
    //    w = incr * i;    
    //    jg.drawEllipse( pCenter.GetX() - w / 2, pCenter.GetY() - w / 2, w, w ); 
    //}
   
    nLines = 12 ;
    incr = Math.PI / nLines * 2;
    //  drawing the grid takes too much, replaced by a gif: grid.gif
    //for( i = 1; i <= nLines; i++ )
    //{
    //    w = incr * i;
    //    x = pCenter.GetX() * ( 1 + Math.cos( w ) ) ;
    //    y = pCenter.GetY() * ( 1 + Math.sin( w ) ) ;        
    //    jg.drawLine( pCenter.GetX(), pCenter.GetY(), x, y ); 
    //}
    
    jg.setColor( oldColor );
    jg.setStroke( oldStroke ); 

    return scale;
}

function CalcVrVabs()
{
    // Clear the drawing first, just in case there is an unhandled error in the calculations
    var jg = new jsGraphics("Graph");    // Use the "Canvas" div for drawing
    jg.clear();
    jg.setFont( 'verdana,geneva,helvetica,sans-serif', "16px", Font.PLAIN)
    jg.paint();

    // Start calculation
    var bearing1, bearing2, course, timeIncr, auxDMS;
    var pSelfSpeed, pRelSpeed, pPos1, pPos2, pRelDistance, pObjectSpeed; 
    var d1, d2, Vb;
    // Get form data
    bearing1 = new LongLat();
    bearing2 = new LongLat();
    course   = new LongLat(); 
    timeIncr = new LongLat();   
    auxDMS   = new LongLat();
    pSelfSpeed = new Pnt();
    pRelSpeed  = new Pnt();
    pPos1 = new Pnt();
    pPos2 = new Pnt();
    pRelDistance  = new Pnt();    
    pObjectSpeed = new Pnt();
    d1 = 0.0;
    d2 = 0.0;
    Vb = 0.0;

    GetData( bearing1, "DegBearing1", "MinBearing1", "", "" );
    GetData( bearing2, "DegBearing2", "MinBearing2", "", "" );
    GetData( course, "DegCourse", "MinCourse", "", "" );    
    GetData( timeIncr, "HourIncrement", "MinIncrement", "", "" );        
    d1 = GetSingleData( "DistBearing1" );
    d2 = GetSingleData( "DistBearing2" );
    Vb = GetSingleData( "Speed" );    
    
    pSelfSpeed.Set( Vb * Math.cos( GetDegrees( course ) * Math.PI / 180 ), Vb * Math.sin( GetDegrees( course ) * Math.PI / 180 ) , 0 );
    pPos1.Set( d1 * Math.cos( GetDegrees( bearing1 ) * Math.PI / 180 ), 
               d1 * Math.sin( GetDegrees( bearing1 ) * Math.PI / 180 ), 0 );
    pPos2.Set( d2 * Math.cos( GetDegrees( bearing2 ) * Math.PI / 180 ), 
               d2 * Math.sin( GetDegrees( bearing2 ) * Math.PI / 180 ), 0 );    
    pRelDistance.Set( pPos2.GetX() - pPos1.GetX(), pPos2.GetY() - pPos1.GetY(), 0 );
    pRelSpeed.Set( pRelDistance.GetX() / GetDegrees( timeIncr ), pRelDistance.GetY() / GetDegrees( timeIncr ), 0 );
    pObjectSpeed.Set( pSelfSpeed.GetX() + pRelSpeed.GetX(), pSelfSpeed.GetY() + pRelSpeed.GetY(), 0 );
    
    dMaxSpeedModule = 0;
    if( pSelfSpeed.Length() > pObjectSpeed.Length() )
        dMaxSpeedModule = pSelfSpeed.Length();
    else
        dMaxSpeedModule = pObjectSpeed.Length();
    
    var dMaxDistanceModule = 0;
    if( pPos1.Length() > pPos2.Length() )
        dMaxDistanceModule = pPos1.Length();
    else
        dMaxDistanceModule = pPos2.Length();
    
    // Write numeric results and set text boxes
    // relative speed
    document.getElementById("ResultDr").innerHTML = Round( pRelDistance.Length(), 2 ) + "'";
    SetDMS( Ang360( pRelDistance.Angle() ), auxDMS );
    document.getElementById("ResultRr").innerHTML = "<strong>" + GetDMString( auxDMS ) + "</strong>";     
    document.getElementById("ResultVr").innerHTML = Round( pRelDistance.Length(), 2 ) + "' / " + Round( GetDegrees( timeIncr ), 2 ) + "h = <strong>" + Round( pRelSpeed.Length(), 3 ) + "'</strong>";
    // abs speed
    document.getElementById("ResultVa").innerHTML = "<strong>" + Round( pObjectSpeed.Length(), 2 ) + "'</strong>";    
    document.getElementById('SpeedEco').value = Round( pObjectSpeed.Length(), 2 );    
    SetDMS( Ang360( pObjectSpeed.Angle() ), auxDMS );
    document.getElementById("ResultRa").innerHTML = "<strong>" + GetDMString( auxDMS ) + "</strong>";        
    document.getElementById('DegCourseEco').value = auxDMS.degrees;
    document.getElementById('MinCourseEco').value = Round( auxDMS.minutes + auxDMS.seconds / 60, 3 );    
    
    
    document.getElementById('ResultPaneSpeed').style.display = 'block';    
    document.getElementById('ResultPaneBearing').style.display = 'none';        
    
    // Draw graphics
    var dBoardScale = 1;
    
    if( dScreenScale == -1 )
    {
        var graphElemBg = document.getElementById('GraphBg')    
        var jgBg = new jsGraphics("GraphBg");    // Use the "Canvas" div for drawing
        //jgBg.clear(); drawing the grid takes too much, replaced by a gif: grid.gif
        jgBg.setFont( 'verdana,geneva,helvetica,sans-serif', "16px", Font.PLAIN);
        dScreenScale = DrawBackground( jgBg, graphElemBg, ucsScreen );
        //jgBg.paint(); drawing the grid takes too much, replaced by a gif: grid.gif
    }
    
    // calculate manouvering board scale for echo position vectors
    iAux = Math.ceil( ( dMaxDistanceModule - 0.5 ) / 10 );
    if ( iAux < 1 )
        iAux = 1;
    dBoardScale = iAux;
    
    // Draw a graphic scale: depending on the most outer point, the distance between circles in the 10-circle graph will represent a different distance.
    // For example, if the most outer point is 15' away from the center, each circle must represent 2', so the point lies within the 10 circles.
    DrawScale( 0,10, dScreenScale, dBoardScale + "'", COLOR_RELATIVE_DISTANCE, jg);            
    // Calculate screen points: distances on screen (in pixels) are affected by the screen scale (pixels/circle unit) and data scale (use to fit the current data within the outer circle)
    // The graph will always have 10 circles.
    d1 = dScreenScale / dBoardScale * d1;
    d2 = dScreenScale / dBoardScale * d2;    
    
    p0 = new Pnt();
    p1 = new Pnt();
    p1.Set( d1 * Math.cos( GetDegrees( bearing1 ) * Math.PI / 180 ), 
            d1 * Math.sin( GetDegrees( bearing1 ) * Math.PI / 180 ), 0 );
    p2 = new Pnt();
    p2.Set( d2 * Math.cos( GetDegrees( bearing2 ) * Math.PI / 180 ), 
            d2 * Math.sin( GetDegrees( bearing2 ) * Math.PI / 180 ), 0 );    
    
    p0 = ucsScreen.WorldToUcs( p0 );
    p1 = ucsScreen.WorldToUcs( p1 );
    p2 = ucsScreen.WorldToUcs( p2 ); 
    
    jg.setColor( COLOR_ECHO_POINT1 );
    jg.setStroke( -1 );    
    jg.drawLine( p0.GetX(),p0.GetY(), p1.GetX(),p1.GetY() );
    jg.setColor( COLOR_ECHO_POINT2 ); 
    jg.drawLine( p0.GetX(),p0.GetY(), p2.GetX(),p2.GetY() );    
    jg.setStroke( 1 );
    DrawPoint( p1.GetX(),p1.GetY(), COLOR_ECHO_POINT1,4, jg );
    DrawPoint( p2.GetX(),p2.GetY(), COLOR_ECHO_POINT2,4, jg );    
    jg.setStroke( 2 );
    jg.setColor( COLOR_RELATIVE_DISTANCE );     
    DrawArrow( p1.GetX(),p1.GetY(), p2.GetX(),p2.GetY(), ARROW_LENGTH, ARROW_WIDTH, jg );

    // calculate manouvering board scale for speed vectors
    iAux = Math.ceil( ( dMaxSpeedModule - 0.5 ) / 10 );
    if ( iAux < 1 )
        iAux = 1;
    dBoardScale = iAux;
    
    DrawScale( 0,40, dScreenScale, dBoardScale + "'", COLOR_SELF_SPEED, jg);            
    DrawScale( 0,60, dScreenScale, dBoardScale + "'", COLOR_ABSOLUTE_SPEED, jg);                
    DrawScale( 0,80, dScreenScale, dBoardScale + "'", COLOR_RELATIVE_SPEED, jg);                
    pSelfSpeed.Set( dScreenScale / dBoardScale * pSelfSpeed.GetX(), dScreenScale / dBoardScale * pSelfSpeed.GetY(), 0 );
    pObjectSpeed.Set( dScreenScale / dBoardScale * pObjectSpeed.GetX(), dScreenScale / dBoardScale * pObjectSpeed.GetY(), 0 );    
    p1s = ucsScreen.WorldToUcs( pSelfSpeed ); 
    p2s = ucsScreen.WorldToUcs( pObjectSpeed );     
    jg.setStroke( 2 );
    jg.setColor( COLOR_SELF_SPEED ); 
    DrawArrow( p0.GetX(),p0.GetY(), p1s.GetX(),p1s.GetY(), ARROW_LENGTH, ARROW_WIDTH, jg );
    jg.setColor( COLOR_ABSOLUTE_SPEED ); 
    DrawArrow( p0.GetX(),p0.GetY(), p2s.GetX(),p2s.GetY(), ARROW_LENGTH, ARROW_WIDTH, jg );    
    jg.setColor( COLOR_RELATIVE_SPEED ); 
    DrawArrow( p1s.GetX(),p1s.GetY(), p2s.GetX(),p2s.GetY(), ARROW_LENGTH, ARROW_WIDTH, jg );        
    
    
    jg.paint();
   
}

function CalcNewCourse()
{
    // Clear the drawing first, just in case there is an unhandled error in the calculations
    var jg = new jsGraphics("Graph");    // Use the "Canvas" div for drawing
    jg.clear();
    jg.setFont( 'verdana,geneva,helvetica,sans-serif', "16px", Font.PLAIN)
    jg.paint();

    // Start calculation
    var dTargetDistance, dBoatSpeed, dEchoSpeed, dEchoDistance, iSide;
    var dMaxSpeedLength = -1, dMaxDistanceLength = -1, dRelativeDistance = -1;
    var dmsEchoBearing, dmsEchoCourse;

    // vectors and points
    var vEchoSpeed, vEchoPosition;
    var aTangents, aRelSpeedVectors, aBoatSpeedSolutions;

    // Initialize objects
    dmsEchoBearing = new LongLat();
    dmsEchoCourse  = new LongLat();    
    vEchoSpeed     = new Pnt();
    vEchoPosition  = new Pnt();   

    // Get form data
    GetData( dmsEchoBearing, "DegBearing2", "MinBearing2", "", "" );
    dEchoDistance = GetSingleData( "DistBearing2" );    
    GetData( dmsEchoCourse, "DegCourseEco", "MinCourseEco", "", "" );
    dEchoSpeed = GetSingleData( "SpeedEco" );    

    dTargetDistance = GetSingleData( "TargetDistance" );
    dBoatSpeed = GetSingleData( "Speed" ); 
    if( document.getElementById("PortSide").checked )
        iSide = PORTSIDE;
    else
        iSide = STARBOARDSIDE;
    // End of get data    

    if ( dBoatSpeed < 0 || dEchoSpeed < 0 || ( dBoatSpeed == 0 && dEchoSpeed == 0 ) || dBoatSpeed == dEchoSpeed )
    {
        alert( "La velocidad del eco (Va) y/o la velocidad del buque (V) deben ser mayores que 0 y diferentes entre sí." );
        return;
    }
    
    // Calculation    
    var dAux;
    vEchoSpeed.Set( dEchoSpeed * Math.cos( GetDegrees( dmsEchoCourse ) * Math.PI / 180 ), dEchoSpeed * Math.sin( GetDegrees( dmsEchoCourse ) * Math.PI / 180 ), 0 );    
    dAux = vEchoSpeed.Length(); 
    if( dAux > dMaxSpeedLength ) // keep track of all the speed vector to set the scale later
        dMaxSpeedLength = dAux;
    dAux = dBoatSpeed; 
    if( dAux > dMaxSpeedLength ) // keep track of all the speed vector to set the scale later
        dMaxSpeedLength = dAux;

    vEchoPosition.Set( dEchoDistance * Math.cos( GetDegrees( dmsEchoBearing ) * Math.PI / 180 ), dEchoDistance * Math.sin( GetDegrees( dmsEchoBearing ) * Math.PI / 180 ), 0 );        
    dAux = vEchoPosition.Length(); 
    if( dAux > dMaxDistanceLength ) // keep track of all the position vector to set the scale later
        dMaxDistanceLength = dAux;

    aTangents = CalculateTangentPoints( vEchoPosition, dTargetDistance );

    // Calculate rel speed vectors, from the echo position to each tangent point
    aRelSpeedVectors = new Array();
    aBoatSpeedSolutions = new Array();    
    var vRelSpeed;
    for( var i = 0; i < aTangents.length; i++ )
    {
        aRelSpeedVectors[ aRelSpeedVectors.length ] = new Pnt();
        vRelSpeed = aRelSpeedVectors[ aRelSpeedVectors.length - 1 ];
        vRelSpeed.Set( vEchoPosition.GetX() - aTangents[ i ].GetX(), vEchoPosition.GetY() - aTangents[ i ].GetY(), vEchoPosition.GetZ() - aTangents[ i ].GetZ() );
        vRelSpeed.Normalize();
        if ( dRelativeDistance == -1 )
            dRelativeDistance = vRelSpeed.Length();
    }
    // Calculate intersections of -relSpeeds with boat speed circle
    var pAux = new Pnt();
    var pIntersections, pSolution;
    for( var i = 0; i < aRelSpeedVectors.length; i++ )
    {
        pAux.Set( aRelSpeedVectors[ i ].GetX(), aRelSpeedVectors[ i ].GetY(), aRelSpeedVectors[ i ].GetZ() );
        pIntersections = CalculateIntersection( vEchoSpeed, pAux, dBoatSpeed );
        for ( var j = 0; j < pIntersections.length; j++ ) 
        {
            var pSolAux;
            aBoatSpeedSolutions[ aBoatSpeedSolutions.length ] = new BoatSpeedSolution();
            pSolAux = aBoatSpeedSolutions[ aBoatSpeedSolutions.length - 1]; 
            pSolAux.vSpeed.Set( pIntersections[j].GetX(), pIntersections[j].GetY(), pIntersections[j].GetZ() );
            pSolAux.pTangentPoint = aTangents[ i ];
            pSolAux.vRelSpeed = aRelSpeedVectors[ i ];            
            pAux = pSolAux.vSpeed.Cross( pSolAux.pTangentPoint );
            if( pAux.GetZ() > 0 ) 
                pSolAux.iSide = STARBOARDSIDE;
            else
                pSolAux.iSide = PORTSIDE;                
                
            var dDistance, dRelSpeed;
            pAux.Set( vEchoPosition.GetX() - pSolAux.pTangentPoint.GetX(),  
                      vEchoPosition.GetY() - pSolAux.pTangentPoint.GetY(),  
                      vEchoPosition.GetZ() - pSolAux.pTangentPoint.GetZ() );
            dDistance = pAux.Length();
            pAux.Set( vEchoSpeed.GetX() - pSolAux.vSpeed.GetX(),  
                      vEchoSpeed.GetY() - pSolAux.vSpeed.GetY(),  
                      vEchoSpeed.GetZ() - pSolAux.vSpeed.GetZ() );
            dRelSpeed = pAux.Length();
            SetDMS( dDistance / dRelSpeed, pSolAux.timeToTarget );
        }
    }
    
    // Write text result pane
    document.getElementById("ResultTargetDistance").innerHTML = dTargetDistance;    
    document.getElementById("ResultTargetDistance2").innerHTML = dTargetDistance;        
    
    var vsSpeedResult = new Array(), vsTimeResult = new Array(), dmsAux = new LongLat();
    for( var i = 0; i < aBoatSpeedSolutions.length; i++ )
    {
        if( aBoatSpeedSolutions[ i ].iSide != iSide && dTargetDistance > 0 ) 
            continue;
        SetDMS( Ang360( aBoatSpeedSolutions[ i ].vSpeed.Angle() ), dmsAux ) ; 
        vsSpeedResult[ vsSpeedResult.length ] = "R' = <strong>" + GetDMString( dmsAux ) + "</strong>";
                       
        vsTimeResult[ vsTimeResult.length ] = "T = Dr / Vr = <strong>" + 
                       GetHMSString( aBoatSpeedSolutions[ i ].timeToTarget ) + 
                       "</strong>";
    }
    
    var sSpeedResult = "", sTimeResult = "";
    if ( vsSpeedResult.length == 1 )
        sSpeedResult = "<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + vsSpeedResult[ 0 ] ; 
    else if ( vsSpeedResult.length > 1 )
    {
        sSpeedResult = "<ol>";
        for( var i = 0; i < vsSpeedResult.length; i++ )
            sSpeedResult = sSpeedResult + "<li>" + vsSpeedResult[ i ] + "</li>";         
        sSpeedResult = sSpeedResult + "</ol>";
    }
    if ( vsTimeResult.length == 1 )
        sTimeResult = "<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + vsTimeResult[ 0 ]; 
    else if ( vsTimeResult.length > 1 )
    {
        sTimeResult = "<ol>";
        for( var i = 0; i < vsTimeResult.length; i++ )
            sTimeResult = sTimeResult + "<li>" + vsTimeResult[ i ] + "</li>";         
        sTimeResult = sTimeResult + "</ol>";
    }
    
    document.getElementById("ResultNewCourse").innerHTML = sSpeedResult;
    document.getElementById("ResultTimeToTarget").innerHTML = sTimeResult;

    // show the appropriate divs
    document.getElementById('ResultPaneSpeed').style.display = 'none';
    document.getElementById('ResultPaneBearing').style.display = 'block';
    
    if ( sSpeedResult != "" )
        document.getElementById('ResultErrorMessage').style.display = 'none';
    else
        document.getElementById('ResultErrorMessage').style.display = 'block';

    if ( vsSpeedResult.length > 1 )
        document.getElementById('ResultBearingWarning').style.display = 'block';        
    else
        document.getElementById('ResultBearingWarning').style.display = 'none';    
        
        
    // Draw graphics
    var dBoardScale = 1, dBoardScaleSpeed, dBoardSpeedPosition; // manouvering board scale: each circle in the board can represent 1', 2', 3'... depending on the maximum module to draw
    
    // Calculate screen to ucs scale
    if( dScreenScale == -1 )
    {
        var graphElemBg = document.getElementById('GraphBg')    
        var jgBg = new jsGraphics("GraphBg");    // Use the "Canvas" div for drawing
        //jgBg.clear(); drawing the grid takes too much, replaced by a gif: grid.gif
        jgBg.setFont( 'verdana,geneva,helvetica,sans-serif', "16px", Font.PLAIN);
        dScreenScale = DrawBackground( jgBg, graphElemBg, ucsScreen );
        //jgBg.paint(); drawing the grid takes too much, replaced by a gif: grid.gif
    }
    var iAux;
    var pAux = new Pnt();    
    var p0 = new Pnt();
    var p1 = new Pnt();
    var p2 = new Pnt();

    // screen origin
    p0 = ucsScreen.WorldToUcs( p0 );

    // ******************************* speed vectors
    iAux = Math.ceil( ( dMaxSpeedLength - 0.5 ) / 10 ); // calculate manouvering board scale
    if ( iAux < 1 )
        iAux = 1;
    dBoardScaleSpeed = iAux;

    iAux = Math.ceil( ( dMaxDistanceLength - 0.5 ) / 10 ); // calculate manouvering board scale
    if ( iAux < 1 )
        iAux = 1;
    dBoardScalePosition = iAux;

    dBoardScale = dBoardScaleSpeed;
    DrawScale( 0,60, dScreenScale, dBoardScale + "'", COLOR_ABSOLUTE_SPEED, jg);            
    DrawScale( 0,80, dScreenScale, dBoardScale + "'", COLOR_SELF_SPEED, jg);                
    
    // Draw vector: 1.- scale to screen, 2.- convert to screen UCS 3.- Paint
    pAux.Set( dScreenScale / dBoardScale * vEchoSpeed.GetX(), dScreenScale / dBoardScale * vEchoSpeed.GetY(), 0 );
    p1 = ucsScreen.WorldToUcs( pAux ); 
    jg.setStroke( 2 );
    jg.setColor( COLOR_ABSOLUTE_SPEED ); 
    DrawArrow( p0.GetX(),p0.GetY(), p1.GetX(),p1.GetY(), ARROW_LENGTH, ARROW_WIDTH, jg );    

    // Draw vector: 1.- scale to screen, 2.- convert to screen UCS 3.- Paint
    if ( dBoatSpeed > 0 ) 
    {
        var r = ( dScreenScale / dBoardScale ) * dBoatSpeed ;
        jg.setStroke( 1 );
        jg.setColor( COLOR_SELF_SPEED ); 
        jg.drawEllipse( p0.GetX() - r, p0.GetY() - r, 2 * r, 2 * r );    
    }

    //pAux.Set( dScreenScale / dBoardScale * vEchoSpeed.GetX(), dScreenScale / dBoardScale * vEchoSpeed.GetY(), 0 );
    //p1 = ucsScreen.WorldToUcs( pAux );
    // this will draw all the relative speed reference line
    //for( var i = 0; i < aRelSpeedVectors.length; i++ )
    //{
    //    pAux.Set( dScreenScale / dBoardScale * ( vEchoSpeed.GetX() + aRelSpeedVectors[ i ].GetX() * dBoardScale * 20 ), 
    //              dScreenScale / dBoardScale * ( vEchoSpeed.GetY() + aRelSpeedVectors[ i ].GetY() * dBoardScale * 20 ), 
    //              dScreenScale / dBoardScale * ( vEchoSpeed.GetZ() + aRelSpeedVectors[ i ].GetZ() * dBoardScale * 20 ) );
    //    
    //    p2 = ucsScreen.WorldToUcs( pAux );
    //    jg.setStroke( 1 );
    //    jg.setColor( COLOR_RELATIVE_SPEED ); 
    //    jg.drawLine( p1.GetX(),p1.GetY(), p2.GetX(),p2.GetY() );    
    //}

    var vPointType = new Array( PNT_FILLED_CIRCLE, PNT_FILLED_SQUARE, PNT_FILLED_TRIANGLE, PNT_EMPTY_CIRCLE, PNT_EMPTY_SQUARE, PNT_EMPTY_TRIANGLE );
    var vPointSize = new Array( 4, 6, 6, 4, 6, 6 );    
    var iPointTypeIndex = 0;
    for( var i = 0; i < aBoatSpeedSolutions.length; i++ )
    {
        if( aBoatSpeedSolutions[ i ].iSide != iSide  && dTargetDistance > 0 ) 
            continue;
        // Speed vector
        dBoardScale = dBoardScaleSpeed;        
        
        pAux.Set( dScreenScale / dBoardScale * vEchoSpeed.GetX(), dScreenScale / dBoardScale * vEchoSpeed.GetY(), 0 );
        p1 = ucsScreen.WorldToUcs( pAux );

        pAux.Set( dScreenScale / dBoardScale * aBoatSpeedSolutions[ i ].vSpeed.GetX(), 
                  dScreenScale / dBoardScale * aBoatSpeedSolutions[ i ].vSpeed.GetY(), 
                  dScreenScale / dBoardScale * aBoatSpeedSolutions[ i ].vSpeed.GetZ() );
        
        p2 = ucsScreen.WorldToUcs( pAux );

        jg.setColor( COLOR_SELF_SPEED ); 
        jg.setStroke( 2 );
        DrawArrow( p0.GetX(),p0.GetY(), p2.GetX(),p2.GetY(), ARROW_LENGTH, ARROW_WIDTH, jg );    
        jg.setStroke( 1 );
        DrawPoint2( p2.GetX(),p2.GetY(), COLOR_SELF_SPEED, vPointSize[ iPointTypeIndex ], jg, vPointType[ iPointTypeIndex ] );    

        // Relative speed reference line
        pAux.Set( dScreenScale / dBoardScale * ( vEchoSpeed.GetX() + aBoatSpeedSolutions[ i ].vRelSpeed.GetX() * dBoardScale * 20 ), 
                  dScreenScale / dBoardScale * ( vEchoSpeed.GetY() + aBoatSpeedSolutions[ i ].vRelSpeed.GetY() * dBoardScale * 20 ), 
                  dScreenScale / dBoardScale * ( vEchoSpeed.GetZ() + aBoatSpeedSolutions[ i ].vRelSpeed.GetZ() * dBoardScale * 20 ) );
        
        p2 = ucsScreen.WorldToUcs( pAux );
        jg.setStroke( 1 );
        jg.setColor( COLOR_RELATIVE_SPEED ); 
        jg.drawLine( p1.GetX(),p1.GetY(), p2.GetX(),p2.GetY() );    
        
        // Tangent point
        dBoardScale = dBoardScalePosition;

        pAux.Set( dScreenScale / dBoardScale * vEchoPosition.GetX(), dScreenScale / dBoardScale * vEchoPosition.GetY(), 0 );
        p1 = ucsScreen.WorldToUcs( pAux );

        pAux.Set( dScreenScale / dBoardScale * aBoatSpeedSolutions[ i ].pTangentPoint.GetX(), dScreenScale / dBoardScale * aBoatSpeedSolutions[ i ].pTangentPoint.GetY(), 0 );
        p2 = ucsScreen.WorldToUcs( pAux );
        jg.setStroke( 1 );
        jg.setColor( COLOR_ECHO_POINT1 ); 
        jg.drawLine( p1.GetX(),p1.GetY(), p2.GetX(),p2.GetY() );    
        DrawPoint2( p2.GetX(),p2.GetY(), COLOR_ECHO_POINT1, vPointSize[ iPointTypeIndex ], jg, vPointType[ iPointTypeIndex ] );    
        
        iPointTypeIndex++;
        if ( iPointTypeIndex >= vPointType.length )
            iPointTypeIndex = 0;
    }
    
    // ******************************* position vectors    
    dBoardScale = dBoardScalePosition;

    DrawScale( 0,10, dScreenScale, dBoardScale + "'", COLOR_ECHO_POINT2, jg);            
    DrawScale( 0,30, dScreenScale, dBoardScale + "'", COLOR_ECHO_POINT1, jg);                
        
    // Draw vector: 1.- scale to screen, 2.- convert to screen UCS 3.- Paint
    vEchoPosition.Set( dScreenScale / dBoardScale * vEchoPosition.GetX(), dScreenScale / dBoardScale * vEchoPosition.GetY(), 0 );
    p1 = ucsScreen.WorldToUcs( vEchoPosition );
    jg.setStroke( -1 );
    jg.setColor( COLOR_ECHO_POINT2 ); 
    jg.drawLine( p0.GetX(),p0.GetY(), p1.GetX(),p1.GetY() );    
    jg.setStroke( -1 );
    DrawPoint( p1.GetX(),p1.GetY(), COLOR_ECHO_POINT2,4, jg );    
    

    // Draw vector: 1.- scale to screen, 2.- convert to screen UCS 3.- Paint
    if ( dTargetDistance > 0 ) 
    {
        var r = dScreenScale / dBoardScale * dTargetDistance;
        jg.setStroke( 1 );
        jg.setColor( COLOR_ECHO_POINT1 ); 
        jg.drawEllipse( p0.GetX() - r, p0.GetY() - r, 2 * r, 2 * r );    
    }

    // this will draw all the tangents, we just need to draw the tangents with a valid solution (see the solution drawing loop above)
    //for( var i = 0; i < aTangents.length; i++ )
    //{
    //    aTangents[ i ].Set( dScreenScale / dBoardScale * aTangents[ i ].GetX(), dScreenScale / dBoardScale * aTangents[ i ].GetY(), 0 );
    //    p2 = ucsScreen.WorldToUcs( aTangents[ i ] );
    //    jg.setStroke( 1 );
    //    jg.setColor( COLOR_ECHO_POINT1 ); 
    //    jg.drawLine( p1.GetX(),p1.GetY(), p2.GetX(),p2.GetY() );    
    //    DrawPoint( p2.GetX(),p2.GetY(), COLOR_ECHO_POINT1,4, jg );    
    //}
    
    jg.paint();

}

// This function calculates tangent points from "p" to a circle at (0,0) and radius "dRadius"
// The value returned is a 0,1 or 2 element array containing the tangent points
// If p is within the circle, the size of the array is 0
// if "dRadius" is 0, then the size of the array is 1 (obviously drawing a tangent to a point will yield just one solution)
// if "dRadius" is > 0 and "p" is outside the circle the size of the array will be 2. 
function CalculateTangentPoints( p, dRadius )
{
    var vRet = new Array();
    var dDistance = p.Length();
    var dAlphaIncr, dAlphaP, dAlpha1, dAlpha2;
    
    dRadius = Math.abs( dRadius );
    
    // no solution possible
    if ( dDistance <= dRadius )
        return vRet;       
    
    if ( dRadius == 0 )
    {
        var pSol = new Pnt();
        vRet[ 0 ] = pSol;
    }
    else
    {
        dAlphaP = p.Angle() * Math.PI / 180;
        dAlphaIncr = Math.acos( dRadius / dDistance ) ;
        var pSol1 = new Pnt();
        var pSol2 = new Pnt();
        dAlpha1 = dAlphaP + dAlphaIncr; 
        dAlpha2 = dAlphaP - dAlphaIncr;         
        pSol1.Set( dRadius * Math.cos( dAlpha1 ), dRadius * Math.sin( dAlpha1 ), 0 );
        pSol2.Set( dRadius * Math.cos( dAlpha2 ), dRadius * Math.sin( dAlpha2 ), 0 );        
        vRet[ 0 ] = pSol1;
        vRet[ 1 ] = pSol2;
    }
    
    return vRet;
}

// This function calculates the intersection of a line starting at "p0" with direction "v" with a circle of radius "dRadius"
// The solution found will be in the direction of "v", not in the direction of "-v"
function CalculateIntersection( p0, v, dRadius )
{
    // Equation to solve: 
    //       x^2 + y^2 = r^2
    //       x = p0.x + k * v.x
    //       y = p0.y + k * v.y
    // This will translate into:
    //       (v.x^2 + v.y^2)*k^2 + (2*p.x*v.x+2*p.y*v.y)*k + (v.x^2+v.y^2-r^2) = 0
    // Valid solutions will be those for k > 0
    var a,b,c, k1 = -1, k2 = -1, aux;
    var vSolutions = new Array();
    a = v.GetX() * v.GetX() + v.GetY() * v.GetY();
    b = 2*p0.GetX()*v.GetX() + 2*p0.GetY()*v.GetY();    
    c = p0.GetX() * p0.GetX() + p0.GetY() * p0.GetY() - dRadius * dRadius;
    
    aux = b*b-4*a*c;
    if( aux > 0 )
    {
        k1 = ( -b + Math.sqrt( aux ) ) / ( 2*a );
        k2 = ( -b - Math.sqrt( aux ) ) / ( 2*a );
        if ( k1 == k2 )
            k2 = -1;
    }
    else if ( aux == 0 )
    {
        k1 = -b / ( 2 * a );    
    }
    
    var solution, x, y;
    if  ( k1 >= 0 )
    {
        vSolutions[ vSolutions.length ] = new Pnt(); 
        solution = vSolutions[ vSolutions.length - 1 ];
        x = p0.GetX() + k1 * v.GetX(); 
        y = p0.GetY() + k1 * v.GetY();         
        solution.SetX( x );
        solution.SetY( y );        
    }
    if  ( k2 >= 0 )
    {
        vSolutions[ vSolutions.length ] = new Pnt(); 
        solution = vSolutions[ vSolutions.length - 1 ];
        x = p0.GetX() + k2 * v.GetX(); 
        y = p0.GetY() + k2 * v.GetY();         
        solution.SetX( x );
        solution.SetY( y );        
    }
        
    return vSolutions;
}

function Test()
{
    aa = new Pnt();
    b = new Pnt();
    aa.Set( 1,1,0 );
    ucs = new Ucs();
    x = new Pnt();
    y = new Pnt();
    x.Set( Math.cos( 30 * Math.PI / 180 ), Math.sin( 30 * Math.PI / 180 ), 0 );
    y.Set( Math.cos( 120 * Math.PI / 180 ), Math.sin( 120 * Math.PI / 180 ), 0 );
    ang = x.AngleXY();
    ucs.SetX( x );
    ucs.SetY( y );    
    ucs.SetZ( x.Cross( y ) );
    b = ucs.UcsToWorld( aa ) ;
    ang = b.AngleXY();    
    b = ucs.WorldToUcs( b ) ;
    ang = b.AngleXY();    
    aa = b.Length();
    ucs.SetOriginCoord( 50, -50, 0 );
    aa = ucs.GetOrigin();
    ucs.SetXCoord( 0,1, 0 );
    ucs.SetYCoord( -1, 0, 0 );
    ucs.MakeZ();
    ucs.SetZ( ucs.GetX().Cross( ucs.GetY() ) ) ;
    aa = ucs.WorldToUcs( new Pnt() );
    ang = aa.AngleXY();
    aa = 2;
    aa = 3;
    ang = Math.atan2(10,20) ;
    aa = 3;    
}