function Variable( name )
{
    // Watch out when adding members: copy method below
    this.sName = name;
    this.dValue = 0.0;
    this.bSet = false;
    this.sCaption = name;
    this.iType = 0; // 0 = double, 1 = Degrees/Minutes
    // Watch out when adding members: copy method below
    
    // -------------------------------------------------------------------------------------------------------------------------------------------------------------
    // Methods
    // -------------------------------------------------------------------------------------------------------------------------------------------------------------    
    this.Set = function( d )
    {
        if( d == 0 || d != "" ) 
        {
            this.dValue = d;
            this.bSet = true;
        }
        else
        {
            this.ResetValues();
        }
    }

    this.Get = function( d )
    {
        return this.dValue;
    }

    this.SetName = function( s )
    {
        this.sName = s;
        if ( this.sCaption == "" ) 
            this.Caption = s;
    }

    this.SetType = function( i )
    {
        this.iType = i;
    }
    
    this.GetType = function()
    {
        return this.iType;
    }
        
    this.GetName = function()
    {
        return this.sName;
    }

    this.SetCaption = function( s )
    {
        this.sCaption = s;
    }

    this.GetCaption = function()
    {
        return this.sCaption;
    }

    this.IsSet = function()
    {
        return this.bSet;
    }

    this.Reset = function()
    {
        this.sName = "";
        this.sCaption = "";
        this.ResetValues();
    }

    this.ResetValues = function()
    {
        this.dValue = "";
        this.bSet = false;
    }    

    this.toString = function()
    {
        if ( this.IsSet() ) 
        {
            sValue = Round( this.dValue, 6 );
            if ( this.GetType() == 1 )
            {
                var deg = new LongLat();
                SetDMS( Ang360( this.dValue ), deg );
                var deg2 = new LongLat();
                SetQuadrantCourse( GetDegrees( deg ), deg2 )
                sValue = GetDMString( deg ) + " = " + GetQuadrantCourseString( deg2 );                
            }
            else if ( this.GetType() == 2 )
            {
                sValue = sValue + "&deg;"
            }
                    
            if ( this.GetCaption() == "" )
                return "<No name> = " + sValue;
            else
                return this.GetCaption() + " = " + sValue; 
        }
        else if ( this.GetCaption() == "" )
            return "::No name:: = ::Value not set::"; 
        else    
            return this.GetCaption() + " = ::Value not set::"; 
    }    

    this.Copy = function( source )
    {
        this.sName = source.sName;
        this.sCaption = source.sCaption;        
        this.dValue = source.dValue;
        this.bSet = source.bSet;    
    }
}

function Expression( avVariablesArg, asExpression )
{
    this.avVariables = new Array();
    this.sEvalExpression = "";
    this.sEvalShowExpression = "";  

    // NOTE: Add variables must go before creating the expression strings because captions of the variables are required later on
    for( var ss in avVariablesArg )
    {
        this.avVariables[ avVariablesArg[ ss ].GetName() ] = avVariablesArg[ ss ] ;    
    }
    
    for ( var ss in asExpression )
    {
        s = asExpression[ ss ];
        if ( s == "+" || s == "-" || s == "*" || s == "/" || 
             s == "(" || s == ")" )
        {
            this.sEvalExpression = this.sEvalExpression + s; 
            this.sEvalShowExpression = this.sEvalShowExpression + s + " " ;        
        }
        else
        {
            this.sEvalExpression = this.sEvalExpression + "this.avVariables[ '" + s +"' ].Get()";     
            this.sEvalShowExpression = this.sEvalShowExpression + this.avVariables[  s  ].GetCaption() + " " ;        
        }
    }
    // trim
    this.sEvalShowExpression = this.sEvalShowExpression.replace(/^\s*|\s*$/g,"");
    
    // -------------------------------------------------------------------------------------------------------------------------------------------------------------
    // Methods
    // -------------------------------------------------------------------------------------------------------------------------------------------------------------    
    this.Eval = function( vResult )
    {
        vResult.ResetValues();
        if ( !this.EvalReady( ) )
            return vResult;
        vResult.Set( eval( this.sEvalExpression ) );
        return vResult;
    }
    
    this.AddVariable = function( v )
    {
        this.avVariables[ v.GetName() ] = v ;
    }
    
    this.EvalReady = function()
    {
        for ( var v in this.avVariables )
        {
            if( !this.avVariables[v].IsSet() )
                return false;
        }
        return true;
    }
    
    this.toString = function()
    {
        return this.sEvalShowExpression;
    }
}

function Equation( sVarName, asExpression, avVariables )
{
    this.varResult = new Variable( sVarName ) ;
    this.expression = new Expression( avVariables, asExpression );
    
    // -------------------------------------------------------------------------------------------------------------------------------------------------------------
    // Methods
    // -------------------------------------------------------------------------------------------------------------------------------------------------------------    
    this.EvalReady = function()
    {
        return this.expression.EvalReady()
    }

    this.Eval = function()
    {
        this.varResult.ResetValues();
        if ( !this.EvalReady( ) )
            return vResult;
        this.expression.Eval( this.varResult );
    }   
    
    this.GetResult = function()
    {
        return this.varResult;
    }
    
    this.GetExpression = function()
    {
        return this.expression 
    }    
    
    this.VariableUsed = function( sName )
    {
        for ( var v in this.expression.avVariables )
        {
            if( v == sName ) 
                return true;
        }
        return false;
    }
    
    this.GetVariable = function( sName )
    {
        for ( var v in this.expression.avVariables )
        {
            if( v == sName ) 
                return this.expression.avVariables[ v ];
        }
    }
    
    this.toString = function()
    {
        if ( this.varResult.GetCaption() == "" ) 
            sName = "<No name>"
        else 
            sName = this.varResult.GetCaption();
        if( !this.varResult.IsSet() )
            sValue = "::Value not set::";
        else
        {
            sValue = Round( this.varResult.Get(), 6 );
            if ( this.varResult.GetType() == 1 )
            {
                var deg = new LongLat();
                SetDMS( Ang360( this.varResult.Get() ), deg );
                var deg2 = new LongLat();
                SetQuadrantCourse( GetDegrees( deg ), deg2 )
                sValue = GetDMString( deg ) + " = " + GetQuadrantCourseString( deg2 );                
            }        
            else if ( this.varResult.GetType() == 2 )
            {
                sValue = sValue + "&deg;"
            }
        }
        return sName + " = " + this.expression + " = " + sValue ;
    }
}

function EquationSet()
{
    this.iCurrSolution = -1;
    this.aEquations = new Array();
    
    this.AddEquation = function( eq )
    {
        this.aEquations[ this.aEquations.length ] = eq;
    }
    
    this.GetCurrentSolution = function()
    {
        return this.aEquations[ this.iCurrSolution ];
    }
    
    this.Solved = function()
    {
        return this.iCurrSolution >= 0;
    }
    
    this.Unsolve = function()
    {
        for ( var v in this.aEquations )
        {
            this.aEquations[ v ].GetResult().ResetValues();
            this.iCurrSolution = -1;
        }
    }
    
    this.Solve = function()
    {
        for ( var v in this.aEquations )
        {
            if ( this.aEquations[ v ].EvalReady() ) 
            {
                this.aEquations[ v ].Eval();
                this.iCurrSolution = v;
                return true;
            }
        }
        return false;
    }
}