/** 
* Programming extension for Numbas
* by Chris Graham and Christian Lawson-Perfect & George Stagg, 2020
*/
Numbas.addExtension('mas-codeassess',['display','util','jme'],function(programming) {
  console.log('Loading programming extension');
  var CODERUNNER_SUBMIT_URL = "https://mas-codeassess.ncl.ac.uk/submit";
  var CODERUNNER_FETCH_URL = "https://mas-codeassess.ncl.ac.uk/fetch";
  var CHECK_DELAY = 250;
  var jme = Numbas.jme;
  var types = jme.types;
  var funcObj = jme.funcObj;
  var TString = types.TString;
  var TDict = types.TDict;
  var TNum = types.TNum;
  var TList = types.TList;
  var THTML = types.THTML;
  var TBool = types.TBool;
  var TExpression = types.TExpression;
  var TNothing = types.TNothing;
  var unwrap = jme.unwrapValue;
  var marking = Numbas.marking;
  var feedback = marking.feedback;

  var render_code_block = programming.render_code_block = function(i,pre) {
    var code = pre.innerHTML.replace(/<br>/g, '\n').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
    var classes = Array.prototype.slice.call(pre.classList).filter(function(c){return c!='ace';});

    var language = classes[0];
    make_editor(pre,{
      readonly: true,
      language: language,
      gutter: pre.classList.contains('gutter'),
      code: code,
      id: pre.getAttribute('id')
    });
  };

  var make_editor = programming.make_editor = function(replace,options) {
    var div = document.createElement('div');
    $(replace).replaceWith(div);
    div.classList.add('acediv');
    div.textContent = options.code || '';
    if(options.id) {
      div.setAttribute('id',options.id);
    }
    var editor = ace.edit(div);
    editor.setTheme("ace/theme/textmate");
    if(options.language) {
      editor.session.setMode("ace/mode/"+options.language);
    }
    editor.setShowPrintMargin(false);
    editor.setHighlightActiveLine(false);
    editor.setReadOnly(options.readonly);
    editor.setOptions({
      maxLines: Infinity
    });
    editor.renderer.setShowGutter(!options.readonly || options.gutter);
    editor.setOptions({
      fontFamily: "monospace",
      fontSize: "12pt"
    });
    editor.renderer.setScrollMargin(10, 10);
    if(options.readonly){
      editor.renderer.$cursorLayer.element.style.display = "none";
    }
    return editor;
  };

  function CodeRunnerPromise(){
    this.test_list = [];
    this.settled = false;
    this.promise = new Promise((resolve, reject)=> {
      this.reject = reject;
      this.resolve = resolve;
    });
  }
  
  var add_coderunner_promise_to_question = programming.add_coderunner_promise_to_question = function(question) {
    var p_promises = [];
    question.allParts().forEach(function(p) {
      if(p.type=='mark-code-2') {
        add_markingJME_to_part(p);
        p.coderunner_task = new CodeRunnerPromise();
        if (p.answered) p_promises.push(p.coderunner_task.promise);
      }
    });
    return Promise.all(p_promises);
  }
  
  var add_code_blocks_to_question = programming.add_code_blocks_to_question = function(question) {
    // Add code block to each code marking part
    question.allParts().forEach(function(p) {
      if(p.type=='mark-code-2') {
        add_code_block_to_part(p);
      }
      $(p.display.html).find('pre.ace').each(render_code_block);
    });
    // Find ace pres
    $(question.display.html).find('pre.ace').each(render_code_block);
  };

  var add_code_block_to_part = programming.add_code_block_to_part = function(part) {
    var question = part.question;
    var language = unwrap(part.settings.code_language);

    // Sub varables into marking algorithm
    var mark_algorithm = unwrap(part.settings.mark_algorithm);

    // Sub varables into correct answer and update
    var correct_answer = unwrap(part.settings.correct_answer);
    var correct_answer_vars = correct_answer.replace(/{([a-z][a-z0-9_]*'*?)}/g, function(match, grp) {
      var v = question.scope.getVariable(grp);
      if (v === undefined) return match;
      return Numbas.jme.subvars(match,question.scope,true);
    });
    part.display.updateCorrectAnswer(correct_answer_vars);
    
    // Inject evaluating code alert
    var input = $(part.display.html).find(".student-answer").last().children().first();
    part.progress = $('<div class="alert alert-info mark-code-progress">Evaluating code...</div>');
    part.progress.insertAfter(input);
    part.progress.css('visibility','hidden');
    
    // Redefine the submit method - first evaluate code, then run the real submit method once that's done
    function replace_submit(submit_part,result_part) {
      if(!submit_part.pre_submits) {
        submit_part.pre_submits = [];
        var oldsubmit = submit_part.submit;
        submit_part.submit = function() {
          var promise = Promise.all(submit_part.pre_submits.map(function(fn){return fn();}));
          promise.then(function() {
            oldsubmit.apply(submit_part);
          });
        };
      }
      submit_part.pre_submits.push(function() {
        result_part.removeWarnings();
        result_part.setDirty(true);
        result_part.code_result = null;
        result_part.storeAnswer(result_part.editor.getValue());

        result_part.progress.css('visibility','visible');
        
        // Modify student code if required
        var student_code = result_part.stagedAnswer;
        if(part.settings.modifier && part.settings.modifier.value){
          var modifier = unwrap(part.settings.modifier);
          student_code = question.scope.evaluate(modifier, variables={
            'submitted_code': student_code
          });
          student_code = unwrap(student_code);
        }

        // Embed submitted code into template
        var template = unwrap(part.settings.template);
        var template_vars = template.replace(/{([a-z][a-z0-9_]*'*?)}/g, function(match, grp) {
          var v = question.scope.getVariable(grp);
          if (v === undefined) return match;
          return Numbas.jme.subvars(match,question.scope,true);
        }).replace(/{submitted_code}/g, function(match, grp) {
          return student_code;
        });

        var promise = submit_code(template_vars,
        {
          language: language,
          mark_algorithm: mark_algorithm,
          question: question,
          part: result_part,
          submitted_code: result_part.stagedAnswer
        }
        )
        .then(function(result) {
          result_part.progress.css('visibility','hidden');
          result_part.code_result = result;
          result_part.display.showWarnings();
        });

        return promise;
      });
    }

    var submit_part = part;
    while(submit_part.parentPart) {
      submit_part = submit_part.parentPart;
    }
    replace_submit(submit_part, part);

    // Sub varables into placeholder answer and store.
    var placeholder = unwrap(part.settings.placeholder);
    placeholder = placeholder.replace(/{([a-z][a-z0-9_]*'*?)}/g, function(match, grp) {
      var v = question.scope.getVariable(grp);
      if (v === undefined) return match;
      return Numbas.jme.subvars(match,question.scope,true);
    });

    if(part.stagedAnswer || part.answered){
      part.editor = make_editor(input,{language: language, code: part.stagedAnswer});
      if(part.answered) part.submit();
    } else {
      part.editor = make_editor(input,{language: language, code: placeholder});
      part.storeAnswer(placeholder);
      part.setDirty(false);
    }
    part.editor.getSession().on('change', function() {
      part.display.hideWarnings();
    });

    var old_parameters = part.marking_parameters;
    part.marking_parameters = function() {
      var d = old_parameters.apply(part,arguments);
      d.code_result = jme.wrapValue(part.code_result);
      return d;
    };
  };

  function hook_explore_mode(question){
    var add_part = question.addExtraPart;
    question.addExtraPart = function(def_index,scope,variables,previousPart,index){
      console.log("hooked addExtraPart");
      var np = add_part.apply(question, [def_index,scope,variables,previousPart,index]);
        setTimeout(function(){
        add_coderunner_promise_to_question(question);
        add_code_blocks_to_question(question);
      },0);
      return np;
    }
  };

  // Put editing blocks in place on question load
  Numbas.signals.on('exam ready',function() {
    Numbas.exam.old_regenQuestion = Numbas.exam.regenQuestion;

    Numbas.exam.regenQuestion = function(){
      Numbas.exam.old_regenQuestion();
      setTimeout(function(){
        var q = Numbas.exam.currentQuestion;
        hook_explore_mode(q);
        add_coderunner_promise_to_question(q);
        add_code_blocks_to_question(q);
      },0);
    };

    q_cr_promises = [];
    Numbas.exam.questionList.forEach(function (question) {
      hook_explore_mode(question);
      // Add initial launch promises and code blocks
      var q_cr_promise = add_coderunner_promise_to_question(question);
      q_cr_promises.push(q_cr_promise);
      question.signals.on('HTMLAttached',function() {
        add_code_blocks_to_question(question);
      });
    });
    console.log('entry type:' + Numbas.exam.entry);
    // If the exam is opening in review mode, get the coderunner task promises
    // added to each question/parts and wait for them to complete before
    // running exam.end()
    if(Numbas.exam.entry == 'review'){
    	var exam_end_old = Numbas.exam.end;
        Numbas.exam.end = function(save){
          setTimeout(
            function(){
              Promise.all(q_cr_promises).then((values) => {
                console.log('Hooked exam.end()');
                Numbas.schedule.add(exam_end_old,Numbas.exam,save);
              });
            }, 500);
        }
    }
  });

  var submit_code = programming.submit_code = function(code,options) {
    if(options.part.coderunner_task.settled){
      options.part.coderunner_task = new CodeRunnerPromise();
    }
    var promise = jme.evaluate(options.mark_algorithm,options.part.scope).value;
    var data =  {
      "env": options.language,
      "code":code,
      "unit_tests":options.part.coderunner_task.test_list,
    };
    console.log(data);
    $.ajaxSetup({ traditional: true });
    $.post(CODERUNNER_SUBMIT_URL,data,function(returned) {
      var submissionid = returned.job_id;
      if(!submissionid) {
        options.part.coderunner_task.resolve({
          status: 'codeassess_error',
        });
        return;
      }
      function make_request() {
        $.getJSON(CODERUNNER_FETCH_URL, {"job_id": submissionid})
        .done(function(data){
          if("status" in data && data.status != "finished" && data.status != "failed" && data.status != "timeout"){
            setTimeout(function(){make_request();},CHECK_DELAY);
          } else {
            data.source = options.submitted_code;
            console.log(data);
            options.part.coderunner_task.settled = true;
            if(data.status == "timeout") {
              options.part.coderunner_task.reject(data);
            } else {
              options.part.coderunner_task.resolve(data);
            }
          }
        })
        .fail(function(){
          options.part.coderunner_task.settled = true;
          options.part.coderunner_task.reject({status: 'network_error'});
        });
      }
      setTimeout(function(){make_request();},CHECK_DELAY);
    },'json')
    .fail(function() {
      options.part.coderunner_task.settled = true;
      options.part.coderunner_task.reject({status: 'network_error'});
    });

    return promise;
  };

  // ----- JME functions and objects supporting code marking algorithms. -------
  //
  // TPromise type for passing promises around in JME
  function TPromise(promise) {
    this.value = promise;
  }
  TPromise.prototype.type = 'promise';
  jme.display.typeToTeX.promise = function(thing,tok,texArgs,settings) {
    return 'TPromise()';
  };
  jme.display.typeToJME.promise = function(tree,tok,bits,settings) {
    return 'TPromise()';
  };
  
  var failed_to_run = function(result){
    var state = []
    if(result.status == "timeout"){ 
    	state.push(feedback.set_credit(0,'invalid',"Your code took too long to run."));
    }
    if(result.status == "network_error"){
    	state.push(feedback.set_credit(0,'invalid',"There has been a network error. Check your internet connection and try again."));
    }
    state.push(feedback.end(true));
    return state;
  }

  var add_markingJME_to_part = function(part){
    // Marking and evaluation functions
    //
    // eval() -> TPromise
    // Evaluate submitted code and set .passed if return code is zero.
    // The result passes if the submitted code runs without an error.
    part.scope.addFunction(new funcObj('eval',[],TPromise,function(){
      return part.coderunner_task.promise.then(function(result){
        var new_result = Object.assign({}, result);
        new_result.passed = (result.rc==0);
        new_result.unit_test = '';
        new_result.state = [];
        return new_result;
      },function(result){
        var new_result = Object.assign({}, result);
        new_result.passed = false;
        new_result.state = failed_to_run(new_result);
        return new_result;
      });
    }));

    // eval(test:TString) -> TPromise
    // Evaluate submitted code followed by some unit test passed as a string.
    // The result passes if the unit test passes (as according to the coderunner results).
    part.scope.addFunction(new funcObj('eval',[TString],TPromise,function(code){
      part.coderunner_task.test_list.push(code);
      return part.coderunner_task.promise.then(function(result){
        var new_result = Object.assign({}, result);
        if(result.rc == -100) {
          new_result.passed = false;
        } else {
          new_result.passed = (result.unit_tests[code].passed);
        }
        new_result.unit_test = code;
        new_result.state = [];
        return new_result;
      },function(result){
        var new_result = Object.assign({}, result);
        new_result.passed = false;
        new_result.state = failed_to_run(new_result);
        return new_result;
      });
    }));

    // source_matches(regexp:TString) -> TPromise
    // Set result as passed if regexp matches submitted source code.
    // Other result properties are as returned by eval().
    part.scope.addFunction(new funcObj('source_matches',[TString],TPromise,function(re){
      return part.coderunner_task.promise.then(function(result){
        var new_result = Object.assign({}, result);
        new_result.passed = RegExp(re).test(result.source);
        new_result.unit_test = 'source_matches('+re+')';
        new_result.state = [];
        return new_result;
      },function(result){
        var new_result = Object.assign({}, result);
        new_result.passed = false;
        new_result.state = failed_to_run(new_result);
        return new_result;
      });
    }));

    // feedback(msg:TString) -> TPromise
    // Set the marking state to include a feedback message.
    // Other result properties are as returned by eval().
    part.scope.addFunction(new funcObj('feedback',[TString],TPromise,function(msg){
      return part.coderunner_task.promise.then(function(result){
        var new_result = Object.assign({}, result);
        new_result.passed = (result.rc==0);
        new_result.unit_test = '';
        new_result.state = [feedback.feedback(msg)];
        return new_result;
      },function(result){
        var new_result = Object.assign({}, result);
        new_result.passed = false;
        new_result.state = failed_to_run(new_result);
        return new_result;
      });
    }));

    // validate() -> TPromise
    // Set the marking state to include feedback on how the submitted code runs.
    // Other result properties are as returned by eval().   
    part.scope.addFunction(new funcObj('validate',[],TPromise,function(){
      return part.coderunner_task.promise.then(function(result){
        var new_result = Object.assign({}, result);
        new_result.passed = (result.rc==0);
        new_result.unit_test = '';
        if(result.rc==0){
          new_result.state = [feedback.feedback("Your code runs without errors.")];
          return new_result;
        } else {
          var message = 'Your code produces errors when run.';
          new_result.state = [
            feedback.warning(message),
            feedback.set_credit(0,'invalid',message),
            feedback.end(true)
          ];
          return new_result;
        }
      },function(result){
        var new_result = Object.assign({}, result);
        new_result.passed = false;
        new_result.state = failed_to_run(new_result);
        return new_result;
      });
    }));

    // validate(msg:TString) -> TPromise
    // Set the marking state to include feedback on how the submitted code runs.
    // Other result properties are as returned by eval().   
    part.scope.addFunction(new funcObj('validate',[TString],TPromise,function(msg){
      return part.coderunner_task.promise.then(function(result){
        var new_result = Object.assign({}, result);
        new_result.passed = (result.rc==0);
        new_result.unit_test = '';
        if(result.rc==0){
          new_result.state = [feedback.feedback("Your code runs without errors.")];
          return new_result;
        } else {
          var message = msg;
          new_result.state = [
          feedback.warning(message),
          feedback.set_credit(0,'invalid',message),
          feedback.end(true)
          ];
          return new_result;
        }
      },function(result){
        var new_result = Object.assign({}, result);
        new_result.passed = false;
        new_result.state = failed_to_run(new_result);
        return new_result;
      });
    }));

    // mark(credit:TNum, msg:TString) -> TPromise
    // Set the marking state to include a marking credit and message.
    // Other result properties are as returned by eval().
    part.scope.addFunction(new funcObj('mark',[TNum,TString],TPromise,function(marks, msg){
      return part.coderunner_task.promise.then(function(result){
        var new_result = Object.assign({}, result);
        new_result.passed = (result.rc==0);
        new_result.unit_test = '';
        new_result.state = [feedback.add_credit(marks,'Test passed: <i>'+msg+'</i>.<br>')];
        return new_result;
      },function(result){
        var new_result = Object.assign({}, result);
        new_result.passed = false;
        new_result.state = failed_to_run(new_result);
        return new_result;
      });
    }));

    // incorrect(msg:TString) -> TPromise
    // Set the marking state as incorrect and give a feedback message.
    // Other result properties are as returned by eval(), except the
    // property 'passed' is forced to false.
    part.scope.addFunction(new funcObj('incorrect',[TString],TPromise,function(msg){
      return part.coderunner_task.promise.then(function(result){
        var new_result = Object.assign({}, result);
        new_result.passed = false;
        new_result.unit_test = '';
        new_result.state = [];
        var m = feedback.feedback(msg);
        m.reason = 'incorrect';
        new_result.state.push(m);
        return new_result;
      },function(result){
        var new_result = Object.assign({}, result);
        new_result.passed = false;
        new_result.state = failed_to_run(new_result);
        return new_result;
      });
    }));

    // validate(marking_result:TPromise, msg:TString) -> TPromise
    // Inspect the given marking_result and return it "as-is" if it passes.
    //
    // If the provided marking_result fails, the original submitted code is checked
    // to see if it passes alone. The feedback given depends on how the original
    // submitted code runs, so that if the original code has an error the feedback
    // explains this.
    //
    // Example use: validate(eval("x==2"), "x equal to 2")
    part.scope.addFunction(new funcObj('validate',[TPromise,TString],TPromise,function(cond, msg){
      return cond.then(function(result){
        if(result.passed){
          return result;
        } else {
          var new_result = Object.assign({}, result);
          var message;
          return part.coderunner_task.promise.then(function(raw_result){
            if(raw_result.rc==0){
              message = 'Your code failed a validation test';
              new_result.state = result.state.concat([
                feedback.warning(message+': <br><i>'+msg+'</i>.<br>'),
                feedback.set_credit(0,'invalid',message),
                feedback.end(true)
                ]);
              return new_result;
            } else {
              message = 'Your code produces errors when run.';
              new_result.state = [
              feedback.warning(message),
              feedback.set_credit(0,'invalid',message),
              feedback.end(true)
              ];
              return new_result;
            }
          },function(result){
            var new_result = Object.assign({}, result);
            new_result.passed = false;
            new_result.state = failed_to_run(new_result);
            return new_result;
          });
        }
      });
    }));

    // mark(marking_result:TPromise, credit: TNum, msg:TString) -> TPromise
    // Inspect given result and append credit to the marking state depending on
    // if the provided marking_result passes.
    //
    // Example use: mark(eval("x==2"), 1.0, "x equal to 2")
    part.scope.addFunction(new funcObj('mark',[TPromise,TNum,TString],TPromise,function(cond, marks, msg){
      return cond.then(function(result){
        if(result.passed){
          result.state.push(feedback.add_credit(marks,'Test passed: <i>'+msg+'</i>.<br>'));
          return result;
        } else {
          var m = feedback.feedback('Test failed: <i>'+msg+'</i>.<br>');
          m.reason = 'incorrect';
          result.state.push(m);
          return result;
        }
      });
    }));


    // Conditionals
    //
    // if_code (condition:TPromise, ift:TPromise, iff:TPromise) -> TPromise
    // Inspect the result given in condition and return the marking result
    // provided in ift or iff depending on if the result in condition passed.
    part.scope.addFunction(new funcObj('if_code',[TPromise,TPromise,TPromise],TPromise,function(cond,ift,iff){
      return cond.then(function(result){
        if(result.passed){
          return ift;
        } else {
          return iff;
        }
      });
    }));

    // if_code (condition:TPromise, ift:TPromise) -> TPromise
    // Inspect the result given in condition and return the marking result
    // provided by ift if the result in condition passed. Otherwise, return
    // a result with no state, provided as if by eval().
    part.scope.addFunction(new funcObj('if_code',[TPromise,TPromise],TPromise,function(cond,ift){
      return cond.then(function(result){
        if(result.passed){
          return ift;
        } else {
          return part.coderunner_task.promise.then(function(result){
            var new_result = Object.assign({}, result);
            new_result.passed = (result.rc==0);
            new_result.unit_test = '';
            new_result.state = [];
            return new_result;
          });
        }
      });
    }));

    // Operators
    //
    // ! c1:TPromise -> TPromise
    // The returned result passes if the input fails. Marking state and
    // other properties are not modified. 
    part.scope.addFunction(new funcObj('not',[TPromise],TPromise,function(c) {
      return c.then(function(result){
        var new_result = Object.assign({}, result);
        new_result.passed = !(result.passed);
        new_result.unit_test = '!('+result.unit_test+')';
        return new_result;
      });
    })); 

    // c1:TPromise & c2:TPromise -> TPromise
    // The returned result passes if both the two inputs have passed.
    // Marking state and other properties are inherited from c1. 
    part.scope.addFunction(new funcObj('&',[TPromise,TPromise],TPromise,function(c1,c2) {
      return c1.then(function(c1_result){
        return c2.then(function(c2_result){
          var composed = Object.assign({}, c1_result);
          composed.passed = c1_result.passed && c2_result.passed;
          composed.unit_test = c1_result.unit_test +' & ' + c2_result.unit_test;
          return composed;
        });
      });
    }));

    // c1:TPromise | c2:TPromise -> TPromise
    // The returned result passes if either of the two inputs have passed.
    // Marking state and other properties are inherited from c1.  
    part.scope.addFunction(new funcObj('|',[TPromise,TPromise],TPromise,function(c1,c2) {
      return c1.then(function(c1_result){
        return c2.then(function(c2_result){
          var composed = Object.assign({}, c1_result);
          composed.passed = c1_result.passed || c2_result.passed;
          composed.unit_test = c1_result.unit_test +' | ' + c2_result.unit_test;
          return composed;
        });
      });
    }));

    // c1:TPromise ; c2:TPromise -> TPromise
    // Marking state is combined from c1 and c2 in that order. Use this to chain
    // statements in the marking algorithm. Other properties are inherited from c1.   
    part.scope.addFunction(new funcObj(';',[TPromise,TPromise],TPromise,function(c1,c2) {
      return c1.then(function(c1_result){
        return c2.then(function(c2_result){
          var composed = Object.assign({}, c1_result);
          composed.state = c1_result.state.concat(c2_result.state);
          return composed;
        });
      });
    }));
    
   	// JME function to mark the part using the coderunner result
    part.scope.addFunction(marking.state_fn('mark_code',[TBool,TBool],TBool,function(show_stdout,show_stderr) {
    	state = part.code_result.state;
        if(show_stderr && part.code_result.stderr) {
          state.unshift(feedback.feedback("<pre class=\"programming-stderr\">"+part.code_result.stderr+"</pre>"));
        }
        if(show_stdout && part.code_result.stdout) {
          state.unshift(feedback.feedback("Your code produced the following output: <pre class=\"programming-stdout\">"+part.code_result.stdout+"</pre>"));
        }
      	return {
        	return: true,
        	state: part.code_result.state
      	};
    }));
  };


  // Numerical methods
  programming.polycalc = function(a) {
    var c = [];
    var d = [];
    var n=a.length-1;

    for(i=0;i<=n+1;i++) {
      d[i]= [];
      c[i]=0;
      for(j=0;j<=n+1;++j){
        d[i][j]=0;
      } 
    }

    for(m=2; m<=n ; ++m) {
      for(i=m;i<=n;++i){
        for(j = m-1;j<=n+1;++j){
          d[i][j] = [a[i][j] * a[m-1][m-1] , a[i][m-1]];
        }
      }
      for(i=m;i<=n;++i){
        for(j=m-1;j<=n+1;++j) {
          a[i][j] = d[i][j][0]-d[i][j][1]*a[m-1][j];
          if(Math.abs(a[i][j])<1e-25){
            a[i][j]=0;
          }
        }
      }
    }

    for(i=n;i>=1;--i) {
      c[i-1] = a[i][n+1] ;       
      if (i!=n){
        for(j=n; j>=i+1; --j){
          c[i-1] = c[i-1] - a[i][j] * c[j-1];
        }
      }
      if(a[i][i]!=0){
        c[i-1]=c[i-1] / a[i][i];
      }else{
        c[i-1]=0;
      }
      if(Math.abs(c[i-1])<1e-25){
        c[i-1]=0;
      }
    }

    c.length=n;
    return c.reverse();
  };


  programming.polyfit = function(x,y,m) {

  // Put x,y arrays into pairs
  z = [];
  for (i = 0; i < x.length; i++){
    z[i] = [x[i],y[i]];
  }

  var a = [];
  var n = 1 + m;
  var ns = z.length;

  z = [[0,0]].concat(z);

  for(i=0;i<=n+1;i++) {
    a[i]=[];
    for(j=0;j<=n+1;++j){
      a[i][j]=0;
    }
  }

  for(m=1;m <= n;m++){
    for(i=1;i<= m;i++) {
      j = m - i + 1; 
      for(ii=1;ii <= ns;ii++){
        a[i][j] = a[i][j] + Math.pow(z[ii][0], m-1);
      }
    }  
  }
  for(i=1;i<= n;++i){
    for(ii=1;ii<=ns;++ii){
      a[i][n+1] = a[i][n+1] +z[ii][1]*Math.pow(z[ii][0],i-1);
    }
  }

  for(m = n+2 ; m <= 2*n ; ++m){
    for(i = m-n; i<= n;++i) {
      j= m - i;
      for(ii=1; ii<=ns; ++ii){
        a[i][j] = a[i][j] + Math.pow(z[ii][0],m-2);
      }
    }
  }

  a.length = a.length - 1  ;
  return programming.polycalc(a);
  };

  programming.polyval = function(p,x) {
    y = [];
    p.reverse();
    d = p.length;
    for(i = 0; i < x.length; i++){
      y[i] = 0;
      for(j = 0; j < d; j++){
        y[i] += p[j]*x[i]**j;
      }
    }
    return(y);
  };


  programming.scope.addFunction(new funcObj('polyfit',[TList,TList,TNum],TList,function(x,y,n) {
    return programming.polyfit(x,y,n);
  },{unwrapValues: true}));

  programming.scope.addFunction(new funcObj('polyval',[TList,TList],TList,function(p,x) {
    return programming.polyval(p,x);
  },{unwrapValues: true}));
});
