//Version 0.0, April 10, 2006, (c) jason howald (jason.howald@gmail.com)
//License: GNU General Public License (http://www.gnu.org/copyleft/gpl.html)

//****************************************//
//             Example Tasks              //
//****************************************//

var g = new grammar(); //namespace pollution?    

//new principles
//Task constructor takes 2 arguments: method, name.  input is buried in specialized method.
//input usually expressed as "this" when calling Task constructor.
//principle of ignorance and procrastination:  Don't learn or return any solution until you absolutely
//need to learn it.  But when you have it, return it!
//Computing the arguments of show() should take very little work, because often
//(especially if we're recursively solving), show() does little or nothing.
//in particular, pass the expression E, not E.pretty().

//var Task.ScopeNameSpaceForShow = (function(){
//show = Task.prototype.show;//Do some such monstrosity to make this.show -> show.

Task.prototype.derivative_rules = {
    derivative_constant:function(input,v) {
        this.title("Differentiate ",input,", a constant.");
        this.show("The derivative of any constant is zero, and the quantity ");
        this.show(input.solve()," does not depend on " + v + ".");
        return expression.newFromAscii("0");
    },
    derivative_variable:function(input,v) {
        this.title("Differentiate ",input,", a variable.");
        this.show("The derivative of ", v, " with respect to ", v , " is 1.");
        return expression.newFromAscii("1");
    },
    derivative_functionRule:function(input,v) {
        this.title("Differentiate the function ",input,".");
        var F = input.solve();
        this.show("For the function ",F);
        this.show(", you need to memorize the derivative.");
        var FName = F.name.substr(0,F.name.length-1);//chop the left paren in 'sin('
        var g = new grammar();
        var asciiDerivative = g.FnDiff[FName];//lookup table FnDiff.
        //alert(asciiDerivative);
        var vexp = expression.newFromAscii(v);
        var dFdv = expression.newFromAscii(asciiDerivative).substitute("x",vexp);
        return dFdv;        
    },
    derivative_constantMultiple:function(input,v) {
        this.title("Differentiate ",input," by the constant multiple rule.");
        var inexp = input.solve();
        var constant;
        var varying;
        if (inexp.left.dependsOn(v)) {
            varying = inexp.left;
            constant = inexp.right;
        } else {
            varying = inexp.right;
            constant = inexp.left;
        }
        var F = Task.express(varying).called("F");
        this.show("Notice that the function is a product of a constant part, ",constant);
        this.show(", and a varying part F = ",varying,". ");
        this.show("The constant multiple rule tells us that we may differentiate the varying ");
        this.show("part, then simply multiply by the constant, unchanged.");
        var dFdv = F.derivative(v).called("F'");
        this.show("So the answer is ",constant,"*",dFdv);
        return Task.express(constant).times(dFdv);
    },
    derivative_constantDivisor:function(input,v) {
        this.title("Differentiate ",input," by the constant divisor rule.");
        var inexp = input.solve();
        var numerator = inexp.left;
        var denominator = inexp.right;
        this.show("The quotient rule is unnecessary, because the denominator is constant.");
        this.show("Simply differentiate the numerator, F = ", numerator);
        this.show(", and divide by the denominator, ",denominator,", unchanged.");
        var dFdv = input.left().derivative().called("F'");//not num.deriv: procrastination.
        this.show("So the answer is ",dFdv,"/",denominator);
        return dFdv.div(input.right());
    },
    derivative_power: function(input,v) {
        this.title("Differentiate ",input," by the power rule.");
        var inexp = input.solve();
        var base = inexp.left;
        var power = inexp.right;
        this.show("Because the base, ",base);
        this.show(", is a variable but the power, ",power);
        this.show(", is constant, the power rule applies.");
        this.show("Bring down the exponent (multiplying by it) and decrease the exponent by 1.");
        var newpower;
        if (power.type == g.Numb) {
            newpower = new expression(g.Numb,power.name - 1); //instead of 18x^(18-1), get 18x^17.
        } else if ((power.type == g.Operation) && (power.name.match(/\-/)) && 
            (power.left == undefined) && (power.right.type == g.Numb)) { 
            //power is g.Numb with unary minus.  e.g. "-2"
            newpower = power.clone();
            newpower.right.name = (+newpower.right.name + 1); // 2 becomes 3, preceded by minus.  -2 -> -3
        } else {
            //power is not variable, but it has some complexity of form.
            one = new expression(g.Numb,"1");
            newpower = power.minus(one);
        }
        return power.times(base.tothe(newpower));
    },
    derivative_exponent:function(input,v) {
        this.title("Differentiate ",input," by the exponent rule.");
        var inexp = input.solve();
        var base = inexp.left;
        var power = inexp.right;
        this.show("Because the base, ",base);
        this.show(", is constant but the power, ",power);
        this.show(", is a variable, the exponential function rule applies.");
        if ((base.type == g.Constant)&&(base.name == "e")) {
            this.show("The derivative of e^x is e^x.");
            return inexp;
        } else {
            this.show("The derivative is the original function, times the natural log of the base");
            var logbase = expression.newFromAscii("ln(x)").substitute("x",base);
            return logbase.times(inexp);
        }
    },
    derivative_product:function(input,v) {
        this.title("Differentiate ",input," by the product rule.");
        var F = input.left().called("F");
        var G = input.right().called("G");
        var lefty = F.solve();
        var righty = G.solve();
        this.show("Notice that the function is the product of two functions.");
        this.show("Let's call the left one F, so F = ",lefty);
        this.show(", and the right one G, so G = ",righty,". ");
        var dFdv = F.derivative(v).called("F'");
        var dGdv = G.derivative(v).called("G'");
        this.show("Next we use the product rule formula:  ");
        this.show(dFdv,"*",G,"+",F,"*",dGdv);
        this.show("Combine all the parts into one expression to solve the problem.");
        var Answer = dFdv.times(G).plus(F.times(dGdv));
        return Answer;
    },
    derivative_quotient:function(input,v) {
        this.title("Differentiate ",input," by the quotient rule.");
        var F = input.left().called("F");
        var G = input.right().called("G");
        var numerator = F.solve();
        var denominator = G.solve();
        this.show("Notice that the function is the quotient of two functions.");
        this.show("Let's call the numerator F, so F = ",numerator);
        this.show(", and the denominator G, so G = ",denominator);
        var dFdv = F.derivative(v).called("F'");
        var dGdv = G.derivative(v).called("G'");
        var two = Task.write("2");
        this.show("Next we use the quotient rule formula:  (F'G - FG') / G^2");
        this.show("(",dFdv,"*",G,"-",F,"*",dGdv,")/",G.tothe(two).called("G^2"));
        this.show("Combine all the parts into one expression to solve the problem.");
        var Answer = dFdv.times(G).minus(F.times(dGdv)).div(G.tothe(two));
        return Answer;
    },
    derivative_quotientNegativeExponent:function(input,v) {
        this.title("Differentiate ",input,", avoiding the quotient rule.");
        this.show("The quotient rule would be correct but it is unnecessary.");
        this.show("Rewrite the division by ",input.solve().right," as multiplication");
        this.show("by the same thing with a negated exponent.");
        var rewritten = input.solve().clone();
        rewritten.name = "*"; //(change division to multiplication)
        var denom = rewritten.right;//it *was* the denominator, but it won't be for long!
        if ((denom.type == g.Operation)&&(denom.name.match(/\^/))) {
            //change (x+5)^2 into (x+5)^(-2) 
            var negated = new expression(g.Operation,"-");
            negated.adoptRightChild(denom.right);
            denom.adoptRightChild(negated);
        } else {
            //change (x^2 + 3x + 6) into (x^2 + 3x + 6)^(-1)
            var power = expression.newFromAscii("x^(-1)");
            power.substitute("x",denom);//I'm so clever.
            rewritten.adoptRightChild(power);
        }
        this.show("This produces: ",rewritten);
        this.show("Differentiate that by chain rule, taking advantage of the fact that the original numerator is now a constant multiple.");
        var Answer = Task.express(rewritten).derivative(v);//taskify to procrastinate.
        this.show(Answer);
        return Answer;
    },
    derivative_chain:function(input,v,advice) {
        this.title("Differentiate ",input," by the chain rule.");
        //advice comes from expression object
        var outside = advice.outside; // an expression in x.
        var inside = advice.inside; //expression.
        var F = Task.express(outside).called("F");
        var G = Task.express(inside).called("G");
        this.show("First we must identify the outside function ",F);
        this.show(" and the inside function ",G,".  ");
        this.show("(express the outside function F as a function of 'x')");
        var dFdx = F.derivative("x").called("F'");
        var dGdv = G.derivative(v).called("G'");
        var dFdxG = dFdx.substitute("x",G).called("F'(G)");
        this.show("The chain rule says:  Multiply the derivative (",dFdx,") of F, evaluated");
        this.show("at G, by the derivative of G.  That is: ");
        this.show(dFdxG,"*",dGdv);
        return dFdxG.times(dGdv);
    },

    derivative_functionalExponentiation:function(input,v) {
        //This function is not right.
        this.title("Differentiate ",input," by using the method for f(x)^g(x).");
        var inexp = input.solve();
        var base = inexp.left;
        var power = inexp.right;
        this.show("Because the base, ", base);
        this.show(", is not constant, the exponent rule doesn't apply.");
        this.show("Because the power, ",power);
        this.show(", is not constant, the power rule doesn't apply either..");
        var elnx = expression.newFromAscii("e^ln(x)");
        this.show("To get rid of the exponent, we apply the function ",elnx," to the expression.");
        this.show("This is legal because ",elnx,"=x, so nothing is really changed.");
        var constantE = expression.newFromAscii("e");
        var logx = expression.newFromAscii("ln(x)");
        var converted = constantE.tothe(power.times(logx.substitute("x",base)));
        this.show("The trick is useful because we can use the rules");
        this.show(" of logarithms to convert to ",converted,". ");
        var Answer = Task.express(converted).derivative(v).called("differentiate that");
        this.show("Now ",Answer," using the chain and product rules.");
        return Answer;
    },
    
    derivative_plusMinusGroup:function(input,v) {
        //By far the most sophisticated rule to code, because the parse trees
        //will treat + and - as nested binary operations, but that's pedagogically
        //unacceptable for polynomials, so we have to teach an n-ary rule.
        this.title("Differentiate ",input," by the Sum and/or Difference rules.");
        this.show("In order to differentiate a sum or difference, you may simply");
        this.show("differentiate each part, and recombine them just as they are.");
        var inexp = input.solve();
        var termstruct = inexp.termstructure();
        var terms = termstruct.terms;
        var ops = termstruct.operations;
        var N = terms.length;
        var dTdv = [];
        for (var i = 0;i<N;i++) {
            dTdv[i]= Task.express(terms[i]).derivative(v).called("T"+i+"'");
        }
        this.show("This function has "+N+" terms, which we'll name ");
        for (var i = 0;i<N;i++) {
            this.show("T"+i+"=",terms[i]);
            if (i<N-1) {this.show(", ")} else {this.show(". ")}
        }
        this.show("So the derivative is ");
        for (var i = 0;i<N;i++) {
            this.show(dTdv[i]);
            if (i<N-1) {this.show(" "+ ops[i] +" ")} else {this.show(". ")}
        }
        var Answer=dTdv[0];
        for (var i = 1;i<N;i++) {
            if (ops[i-1].match(/\+/)) {
                Answer = Answer.plus(dTdv[i]);
            } else {
                Answer = Answer.minus(dTdv[i]);
            }            
        }
        return Answer; 
    },
    

    derivative_nohelp:function(input,v) {
        this.title("Differentiate ",input,".");
        this.show("This task doesn't have any help.  It's defective.");
        return input.solve().derivative();
    },
    
    derivative : function(input,wrt) {
        this.title("Differentiate ",input,".");
        var v;
        if (wrt == undefined) {v = "x";} else {v = wrt;}
        var advice = input.solve().derivativeAdvice(v);
        var rule = advice.rule;
        if (this[rule] !== undefined) {
            //We have a task method which corresponds to the application of the rule.
            this.show("Compute the answer using...");
            var Answer = input[rule](v,advice).called(expression.ruleToRuleName[rule]);
            this.show(Answer);
            Answer.gui.onlyHelp();
            return Answer;
        } else {
            //no rule, no instruction, no help.  But there is a solution.
            this.show("I cannot explain it.");
            return input.derivative_nohelp(v);
        }
    }
}

Task.prototype.expression_rules = {
    substitute:function(input,v,G) {
        this.title("Substitute ",G," for ",v," in ",input,".");
        this.show("Anywhere you see the variable " + v + " in the function ", input.solve(), ",");
        this.show("replace it with the expression ", G.solve() , ".");
        return input.solve().clone().substitute(v,G.solve());
    },

    left : function(input) {
        this.title("Get the left side of ",input,".");//contextual -- depends on operation.
        return input.solve().left;
    },

    right : function(input) {
        this.title("Get the right side of ",input,".");//contextual -- depends on operation.
        return input.solve().right;
    },

    express : function(input,exp) {
        //this.title("Write the expression ",input,".");
        //express mustn't call input, b/c it might not exist!
        this.show("Just type it in without changing it.");
        return exp;
    },

    write : function(input,str) {
        //this.title("Write the expression ",input,".");
        //mustn't call input.
        this.show("Just type it in without changing it.");
        return expression.newFromAscii(str);
    },

    plus  : function(input,that) {return input.solve().plus (that.solve());},
    minus : function(input,that) {return input.solve().minus(that.solve());},
    times : function(input,that) {return input.solve().times(that.solve());},
    div   : function(input,that) {return input.solve().div  (that.solve());},
    tothe : function(input,that) {return input.solve().tothe(that.solve());}
}


//************************************************************************//
//************************************************************************//
//                                                                        //
//                         Task Infrastructure                            //
//                                                                        //
//************************************************************************//
//************************************************************************//


Task.HookMethodKey = function(methodGroup,methodName) {
    Task.prototype[methodName] = function() {
        var input = this;
        var newargs = [input];
        //alert(methodName + newargs.length + "@" + arguments[0]);
        for (var i=0;i<arguments.length;i++) {
            newargs.push(arguments[i]);
        }
        var T = new Task(
            function(){
//                alert("method" +methodName+"input " + newargs[0].toStringAscii);
                return methodGroup[methodName].apply(T,newargs);
            },
            methodName
        );
        //alert(methodName + newargs.length + newargs[0].label + "<" + newargs[1] + ">");
        //all tasks in new argument list (including input) must notify me.
        for (var i=0;i<newargs.length;i++) {
            if (newargs[i].__proto__ == Task.prototype) {
                newargs[i].notify[T.uniqID] = 1;
            }
        }
        return T;
    }
}

Task.HookMethodKeys = function(methodGroup) {
    for (var methodName in methodGroup) {
        Task.HookMethodKey(methodGroup, methodName);
        //packaging HMK in a new function causes a unique scope
        //to be created for each methodName.  Otherwise scope overlap causes
        //the Task.functions so created to forget their correct methods
    }
}

Task.HookMethodKeys(Task.prototype.expression_rules);
Task.HookMethodKeys(Task.prototype.derivative_rules);


//Tasks without inputs should be callable standalone.
//Maybe simpler to do this with *all* tasks, and let the
//user intuit which make sense standalone.
Task.express = Task.prototype.express;
Task.write = Task.prototype.write;


Task.population = 0;
Task.allTasks = [];

function Task(method,name) {
    this.planned = false;
    this.solved = false;
    this.revealed = false;
    this.showList = [];
    this.titleList = [];
    this.notify = new Object;
    this.solution = 0;
    this.label = name;
    this.method = method;
    this.gui = new taskInterface(this);//Don't worry -- no work here.
    this.uniqID = Task.population ++;
    Task.allTasks.push(this);
}

Task.prototype.show = function(x) {
    //needs to be a function of more than one thing.
    for (i=0;i<arguments.length;i++) {
        this.showList.push(arguments[i]);
    }
}

Task.prototype.title = function(x) {
    //needs to be a function of more than one thing.
    for (var i=0;i<arguments.length;i++) {
        this.titleList.push(arguments[i]);
    }
}

Task.prototype.plan = function() {
    this.showList = [];
    this.titleList = [];
    this.solution = this.method();
    this.planned = true;
}
    

Task.prototype.solve = function() {
    if (this.solved) {
        return this.solution;
    }
    if (!(this.planned)) {
        this.plan();
    }
    this.solved = true;//well, shortly anyway.
    if (this.solution.method !== undefined) {
        //the so-called "solution" is really a simpler problem (task).  Better solve it.
        //this line can create a recursive chain of tasks, ending with an expression.
        this.solution = this.solution.solve();
    }
    return this.solution;
}

Task.prototype.check = function(guess) {
    var correct = this.solve();
    if (guess.semanticEquiv(correct)) {
        this.solution = guess; //adopt the guess, not the calculated solution!
        this.solved = true;
        this.revealed = true;
        this.notifyAll();
        this.gui.update();
    } else {
        alert("That's not right.");
        this.unSolve();
    }
}


Task.prototype.unPlan = function() {
    this.planned = false;
    this.solved = false;
    this.showList = [];
    this.titleList = [];
    //mustn't happen as a side effect of solving myself, via you!
    //we're safe from that b/c solve() doesn't call notify.
    //the erasure of the plan may update help text.
    this.gui.update();
}


Task.prototype.unSolve = function() {
    this.solved = false;
    this.revealed = false;
    this.notifyAll();
    this.gui.update();
}

Task.prototype.called = function(str) {
    this.label = str;
    return this;
}

Task.prototype.monitor = function(x) {
    //x is one of my arguments, or is my input itself.  If x changes, it has an obligation
    //to notify me so I can unPlan.
    x.notify[this.uniqID] = 1;
}

Task.prototype.notifyAll = function() {
    for (var uniqID in this.notify) {
        var x = Task.allTasks[uniqID];
        //alert(this.label +" gives notice to "+x.label+" to unplan itself.");
        x.unPlan();
    }
}




