var __Promise = {};
// if(!String.prototype.trim) String.prototype.trim = function(){
//   return this.replace(/^\s+|\s+$/g, '');
// };

// var ovrrides = {
//   resolve : function(v){
//     if(v && v.then) return v;
//     var p = new Promise(function(resolve, reject){  resolve(v) });
//     return p;
//   }
// };

var stampedFilePfx = function(date) {
  return date.getFullYear() +
  ('0' + (date.getMonth() + 1)).slice(-2) + 
  ('0' + date.getDate()).slice(-2) +
  ('0' + date.getHours()).slice(-2) +
  ('0' + date.getMinutes()).slice(-2) +
  ('0' + date.getSeconds()).slice(-2);
}
var runtimestamp = (new Date()).getTime();

var promises = [];
  function startPromises(){
    promises.forEach(function(p){ 
      // console.log(p.chain)
      p(); 
      // promises.splice(0,1) 
    })
  }

function isWin(){ return /^win/.test(process.platform) }

try{ 
  __Promise = Promise 
  var __require = require
  var map = Array.map
  
    
  // --------------------------------------------
  // Node Exists. Lets launch ourselves in Node itself 
  
  // var wait = function(ms, cb) {any 
  //     return new __Promise(function(resolve){ setTimeout(resolve, ms) } ).then(function(){ cb() });
  //   }
  //   var fs = require('fs')
  //   var { existsSync } = require('fs');
  //   var existsSyncFolder = existsSync
  //   var path = require('path');
  //   var cli = require('./cliverse')
  //   var nodeShellExec = cli.nodeShellExec;
  //   var utils = require('bbhverse');
  //   var any = utils.any;
  //   var wait = setTimeout



  // --------------------------------------------

  var selectedinstance = { root : path.resolve(".") }
  __main(selectedinstance)
}
// catch(e){}
catch(e){ 
  // // --------------------------------------------
  // // Cscript
  var wsh = true;
  function isWin(){ return true; }
  // If UCase( Right( WScript.FullName, 12 ) ) = "\CSCRIPT.EXE" Then

  var clii = {
    question : function(q, answercb){
      WScript.Echo(q)
      // console.log('WScript.StdIn : ' + WScript.StdIn.ReadLine() + ' ==== ')
      var answer = WScript.StdIn.ReadLine()
      answercb(answer)
    }
  }
  var prompter = { 
      ask : function(q){
        // Needs to be serialized. Parallel asks are not possible.
        // const clii = readline.createInterface({ input: process.stdin, output: process.stdout });
        
        
        return new Promise(function(resolve, reject){
          clii.question(q, function(answer){ 
            // clii.close();
            // console.log("resolve is being called");
            resolve(answer) 
          })
        })
      }
    }



  var cli = {
    prompt : function(choices, label, defaultchoice){
      var options = [];
      choices.forEach = forEach
      choices.forEach(function(choice){ options.push(  ((+choice) + 1) + ' ' +  choices[choice] )})
      return prompter.ask( label + ' \\n ' + options.join('\n') + '\n  default ( <= ' + (defaultchoice || choices[0]) + ' ) : ' 
        ).then( function(choice){
          if(!choice) return defaultchoice || choices[0];
          if(choice && isNaN(+choice)) return choice;
          return choices[(+choice) - 1];
        })
    } 
  }

  var any = function(iterable, continueOnFailure) {
    // var cancelsignal = Symbol()
    if(!iterable.reduce) iterable.reduce = reduce
    // console.log('iterable.reduce ' + iterable.reduce)
    var cancelsignal = "cancelme{mangledrunid}"
    return iterable.reduce( 
        function(p, tasq, i ,a) {
          // console.dir(a)
          // console.log('Entered')
            var handleError = function(err, pVal){
              if(err !== cancelsignal) {
                // Cancel only once on first failure.
                console.warn('Possible failure for task with result : ' )
                console.log('Failed : ' + err.message + ' ')
                console.dir(pVal)
                console.dir(p)
                if(i>0 && a[i-1].info) console.dir(a[i-1].info)
                console.error('Error : ' + err.stack)
                console.error(a[i-1])
                a[i-1] ? console.log("tasq : " + a[i-1].toString()) : null;
                if(!continueOnFailure) {console.log("Cancelling remaining on any one failure ..."); throw cancelsignal}
                else return pVal;
                // tasq ? console.log("tasq : " + tasq.toString()) : null; // Previous task is what failed not the one we are going to run.
              }
            }
  
            if(Promise.resolve(p) === p ) {
              if(i>0 && a[i-1].info) p.info = a[i-1].info;
              console.dir(' p.then is ' + p.then)
              return p.then( function(pVal){
                // Falsy values are no longer treated as task failure exceptions. Specially for Promises. 
                // Even tasq function wrappers are required to return promises that eventually either resolve or reject..
                // Failures are known for promises on reject.
                // In future if we support direct sync function execution with a result examination for failure 
                // we could examine the result of the function as falsy's... or a result evaluator handler needs to be passed in... 
                // if(!pVal) handleError({ error : true, message : 'Failed without result' }, pVal)
  
                // Truthy values are failures if obj has error=true.
                if(pVal && pVal.error) handleError(pVal, pVal)
  
                var trycall = function(tasq){
                  try { 
                    var result = tasq() // PB : TODO -- Handle scope for call 
                    if(tasq.resultHandler) return tasq.resultHandler(result)
                    // else (Promise.resolve(result) === result ) ? result.start() : null
                    return result 
                  } catch (error) {
                    console.error(error);
                    console.error('Error : ' + error ? error.stack : 'No stack')
                    if(!continueOnFailure) throw error; // PB : TODO -- Support array of results for any with or without continueonfailure.
                  }
                }
                
                var handleNext = function(){
                  // console.log('Task finished with result : ')
                  // console.dir(pVal)
                  if(i>0 && a[i-1].info) console.dir(a[i-1].info)
                  if(!tasq && !continueOnFailure) { console.log('Error : No task specified.'); throw false;}
                  else if(!tasq) { console.log('Error : No task specified.'); return false;}
                  return (Promise.resolve(tasq) === tasq ) ? tasq /*.start()*/ : trycall(tasq) ;
                }
  
                if(Promise.resolve(pVal) === pVal) {
                  // Passed in function retured a promise. We still need to wait for it.
                  pVal.then(function(pVal){ return handleNext(); })
                }
                else return handleNext()
                  
              })['catch'](function(error) {
                if(error !== cancelsignal) {
                  console.log('E3 : i = ' + i);
                  if(error.result) console.error(error.result)
                  console.error('Error : ' + (error.message || error.messages))
                  console.error('Error : ' + error.stack)
                  tasq ? console.log("tasq : " + tasq.toString()) : null;
                  console.log('debugData 3-------------------------');
                  // handleError()
                  throw error
                }
                else throw cancelsignal;
              })  
            }
            else if(!p) {
              handleError({ error : true, message : 'Failed without result' }, pVal)
              console.log("Bypass remaining on prior failure");
              return false; // All remaining tasks will return false in the any results even if they are promisies still running or functions not initiated.
            }
            else return p; // A truthy value 
        }
        , Promise.resolve(true)
    );
  }
  var console = { log : function(m) {WScript.Echo(m)}, error : function(m) {WScript.Echo(m) } 
    , dir : function(o) {
      for(var i in o){ console.log(i + ' : ' + o[i])}
    }
  }

  // __Promise = ovrrides 
  function forEach(eachFn){
    for(var i=0; i<this.length; i++) eachFn(this[i])
  }
  function reduce(reducnFn, iv){
    var acc = iv
    for(var i=0; i<this.length; i++) {acc = reducnFn(acc, this[i], i, this) }
    return acc;
  }

  function map(eachFn){
    var mapped = []
    for(var i=0; i<this.length; i++) mapped.push(eachFn(this[i]))
    return mapped
  }

  var wait = function(cb, ms) { WScript.Sleep(ms); cb() }
  var fso = new ActiveXObject('Scripting.FileSystemObject');
  var existsSync = function(filepath){ return fso.FileExists(filepath) }
  var fs = {
    writeFileSync : function(filepath, text) {
      // console.log(filepath)    
      var fh = fso.CreateTextFile(filepath, true); 
      fh.WriteLine(text); 
      fh.Close(); 
    }
    
    , mkdirSync : function(path) {
      fso.CreateFolder(path)
    }
    , readFileSync : function(filepath){
      var objFileToRead = fso.OpenTextFile(filepath,1)
      var strFileText = objFileToRead.ReadAll()
      objFileToRead.Close()
      return strFileText    
    }
    , unlinkSync : function(filepath){ fso.DeleteFile(filepath) }
  }

  var path = {
    resolve : function(path){ return fso.GetAbsolutePathName(path) }
      , dirname : function(filepath) {
        var normalized = this.normalize(filepath)
        var li = normalized.lastIndexOf("\\")
        if( li > -1) {
          return normalized.substring(0, li)
        }
      }
      , normalize : function(path){ 
        return path.replace(/\//g,'\\');
      }
  }

  // Detect or specify install directory.
  var selectedinstance = { root : path.resolve(".") }

  var existsSyncFolder = function(path){
        return fso.FolderExists(path)
      }
  
  var shell = new ActiveXObject('shell.application'); 

  promises.forEach = forEach
  function startPromises(){
    promises.forEach(function(p){ 
      // console.log(p.chain)
      p.start(); 
      // promises.splice(0,1) 
    })
  }

  function nodeShellExec(command, cargs, options){

    var elevatedshellexecute = function(cmd, argstr){
      shell.ShellExecute(cmd, argstr , "", "", 1);
    }

    var shellExec = function(cmd, argstr){
      
      var objShell = WScript.createobject("wscript.shell")
      // console.log(argstr.join( ' '))
      var oExec = objShell.Exec(cmd + ' ' + argstr.join(' '))
      var result = {}
      var shellresult = { shell : objShell, result : result }

      var WshRunning = 0
      var WshFinished = 1
      var WshFailed = 2
      while(oExec.Status === WshRunning){
        WScript.StdOut.write('s.')
        WScript.Sleep(500)
      }
      var strOutput = '\n'
      switch(oExec.Status) {
        case WshFinished : 
          strOutput = oExec.StdOut.ReadAll() 
          result.success = true;
          result.code = 0
          break;
        case WshFailed : 
          strOutput = oExec.StdErr.ReadAll() 
          result.success = false;
          result.code = WshFailed
          break;
        default : strOutput = 'failed' 
        break;
      }
      result.result = command + ' ' + cargs + ' exited with code ' + result.code
      result.messages = [strOutput]
      // console.log(strOutput)
      // WScript.Echo(oExec.Status)
      // WScript.Echo(oExec.ProcessID)
      // WScript.Echo(oExec.ExitCode)

      // console.log(objShell.StdOut.ReadAll)
      // console.log(objShell.StdErr.ReadAll)
      // objShell = WScript.createobject("wscript.shell")
      objShell = null;
      return shellresult;
    }

    var p = null;
    var pworker = function(resolve, reject){
      // console.dir(p)
      var pfx = selectedinstance.root + '\\.elxr\\run-' + runtimestamp + '\\' + stampedFilePfx(new Date())
      // console.log('p.chain.length ================ ' +  p.chain.length)
      options = options || {
        runFile : path.normalize( pfx + "out.txt")
        // runFile : null
      }
      var runFile = null;
      var runFile = options.runFile || pfx + command + cargs + "out.txt";
      // console.log(runFile)
      var args = cargs.concat() 
      // runFile ? (args.push(">"), args.push(runFile)) : cargs 
      // console.log(command + ' ' + args.join(' '))

      // command = 'cmd'
      // args = ['/c', 'start',
      //   '/WAIT', selectedinstance.root + '/Downloads' + '/' + 'Git-2.33.0.2-64-bit.exe'
      //   , '/VERYSILENT'
      //   // , '/MERGETASKS=!runcode' // This is required only for vscode...
      // ]


      var runbat = path.normalize(pfx + "run.bat")
      // console.log('runbat : ' + runbat)
      fs.writeFileSync(runbat, 
        '@echo off  \r\n' +
        (options.cwd ? 'cd ' + options.cwd + '  \r\n' : '') + 
        // ' cmd /k notepad.exe \r\n' +  
        command + ' ' + args.join(' ') + ' \r\n' +
        'cmd /c echo done >> ' + runFile  + ' \r\n' +
        'echo done'
      )
      // fs.writeFileSync(runFile, 'started')
      // WScript.Quit()
      // elevatedshellexecute(runFile)
      // while(!existsSync(runbat)) { wait(function(){
      //   console.log('awiting batch : ' + runbat)
      //   // shellExec( 'start', ['/W', '/b', runbat])
      //   // shellExec( 'cmd', ['/k', '/b', runbat])
      //   shellExec( command, args)
      //   // cmd /b /c
      // }, 500) }

      
      console.log(options.waitmsg || ('awaiting ' + command + ' ' + args.join(' ')))
      var shellresult = shellExec( 'cmd', [ '/c', runbat])
      // var shellresult = shellExec( 'cmd', ['/c', runbat])
      // var shellresult = shellExec( command, args)
      
 
      
      var wrapup = function(result) {
        // console.log('Wrapping up')
        try {
          // console.log('resolving.....................'); 
          // console.log('--------------P' + result.messages.join(' ').trim() +'P--------------')
          // if(result.messages.join().trim()) resolve(result)
          // else reject(result)
          resolve(result)
        }
        catch(e){
          // console.dir(e) 
          if(e.message === 'Input past end of file') {
            // console.log('---------------------------------------')
            // console.dir(result)
            resolve(result)
          }
          else {
            // console.dir(e)
            reject(e)
          }
        }
      }

      // if(runFile){
      //   var waitr = function(){
      //     WScript.StdOut.write('w.')
      //     if(existsSync(runFile)) {
      //       var strOutput = fs.readFileSync(runFile)
      //       shellresult.result.messages = [strOutput]
      //       wrapup(shellresult.result)
      //       fs.unlinkSync(runFile)
      //     }
      //     // console.dir(shellresult.result)
      //     wait( waitr, 500)
      //   }
      //   // console.log(wait)
        
      //   wait( waitr, 500)
      // }
      // else {
        // console.log('There is no runfile.')
        // console.log(shellresult.strOutput)
        
        wrapup(shellresult.result)
      // }
    }
    p = new Promise(pworker)
    // promises.push(p)
    // // Promise tThens don't get hooked up... we need to postpone promise starting until all the thens are registered...
    // wait(function(){ 
    //   console.log('p.chain.length' + p.chain.length)
    //   p.start() } , 5000) // Bind and setTImeout is not supported by JScript

    return p;
  } 
  createPromiseClass(/*ovrrides*/)
  __main( selectedinstance )
}

function createPromiseClass(overrides) {

  function reject(e){ 
    var p = this;
    console.log('Promise Rejection : ' + p.fn)
    console.dir(e)
    // throw e;f
    if(p.state !== PromiseClass.PENDING) { console.error ('Error : Promise Rejection can only be called once')}
    var __i = 0;
    var __e = e;
    do {
      for(var i = __i; i < p.chain.length; i++, __i = i) if(p.chain[i].isCatch) break;
      try {
        for(var i = __i; i < p.chain.length; i++, __i = i) if(p.chain[i].isCatch) { p.chain[i](__e); break; } 
        __i++;
        __e = null;
      }
      catch(e){ __i++; __e = e}
    } while(__e)
    do {
      try { for(var i = __i; i < p.chain.length; i++, __i = i) if(!p.chain[i].isCatch) { p.result = p.chain[i](p.result); } }
      catch(e){ 
        __i ++;
        do {
          try {
            for(var i = __i; i < p.chain.length; i++, __i = i) if(p.chain[i].isCatch) { p.chain[i](__e); break; } 
            __i ++;
            __e = null;
          }
          catch(e){ __i++; __e = e}
        } while(__e)
      } 
    } while ( __i < p.chain.length )
    p.state = PromiseClass.REJECTED;
  }

  function resolve(result){   
    var p = this;
    // console.log(result + ' resolve was called with chain length ' + p.chain.length )
    if(p.state !== PromiseClass.PENDING) { console.error ('Error : Promise Resolve can only be called once')}
    p.result = result;
    if(PromiseClass.resolve(p.result) === p.result) {
      // console.log( 'result is still a promise waiting for result and promisechain' )
      waitForResult(p.result, function(r){ p.processchain(r) })
    }
    else {
      // console.log( 'result is value waiting for promisechain' )
      // console.dir(result)
      return p.processchain(result)
    }
  };

  function processchain(r){
      
    var __i = 0;
    var i = __i;
    var __e = null;
    p = this;
    // console.log('processchain chain.length : ' + p.chain.length)

    function __processchain(r){

      function waitForThen(p){  
        if(i < p.chain.length) {
          if(!p.chain[i].isCatch) { 
            // console.log('chain idx : ' + i + ' Executing : then ' + p.result + ' ' + p.chain[i])  
            try {
              p.result = p.chain[i](p.result)
              
              if(PromiseClass.resolve(p.result) === p.result) {
                // console.log('chain idx : ' + i + ' result is still a promise starting it ')  
                // console.dir(p.result.chain)
                // console.log('p.result.start : ' + p.result.start + '    ------------------    ')
                p.result.start()
                // console.log(p.result.fn + '    ------------------    ')
                waitForResult(p.result, function(r){
                  // console.log('we waited')
                  // WScript.write('.')
                  p.result = r; i++; __i = i;
                  waitForThen(p)
                })
              }
              else {
                i++; __i = i;
                waitForThen(p)                          
              }
            }
            catch(e) { 
              i++; __i = i;
              __e = e;
              console.log('failed on index ' + __i + p.chain[__i-1] )
              console.dir(e)
              waitForCatch(p);
            }
          } 
          else {
            // console.log(i + ' Skipping catch : ' + p.result + ' ' + p.chain[i])  
            i++; __i = i;
            waitForThen(p)
          }
        }
        else return p.state = PromiseClass.FULFILLED;
      }
      function waitForCatch(p) {  
        if(i < p.chain.length) {
          if(p.chain[i].isCatch) { 
            console.log('chain idx : ' + i + ' Executing : catch : ' + p.result + ' ' + p.chain[i]) 
            try {
              p.result = p.chain[i](__e);
  
              if(PromiseClass.resolve(p.result) === p.result) {
                p.result.start()
                waitForResult(p.result, function(r){
                  p.result = r; i++; __i = i;
                })
                waitForThen(p)
              }
              else {
                p.result = r; i++; __i = i;
                waitForThen(p)
              }
            }
            catch(e){ i++; __i = i;; __e = e; waitForCatch(p) } 
          } 
          else {
            i++; __i = i;
            waitForCatch(p)
          }
        }
        else return p.state = PromiseClass.REJECTED
      }
      waitForThen(p);
    }
    __processchain(r)
  }

  var create = function(fn){
    var p = { 
      then : function(thenfn){
        // console.log('Adding then') 
        thenfn.isThen = true
        // if(Object.prototype.toString.call(p.chain) !== '[object Array]') console.dir(p.chain)
        p.chain.push(thenfn)
        return p;
      }
      , 'catch' : function(catchfn) {
        catchfn.isCatch = true
        p.chain.push(catchfn)
        return p;
      }
      , start : function(){
        if(this.started) {
          console.error('Cannot start more than once...')
          return p
        };
        this.started = true;
        this.fn = fn;
        try { fn( function(r){ 
          // console.log('calling presolve in starter ')
          p.resolve(r) } , function(r){ p.reject(r) } ) }
        catch(e){ 
          // console.log('start catch : ' + e)
          // console.log('p.chain.length : ' + p.chain.length)
          // // console.dir(p)
          // console.dir(e)
          // console.log(fn)
          p.reject(e) 
        }
        return p
      }
      , state : PromiseClass.PENDING
      , chain : []
      , reject : reject
      , resolve : resolve
      , processchain : processchain
    }
    p.chain.forEach = forEach;
    return p;
  }

  var PromiseClass = function(fn /*, donotstart */){
    var p = create(fn)   
    // if(!donotstart) {
    //   wait(function(){ 
    //     console.log('p.chain.length' + p.chain.length)
    //     p.start() } , 500) // Bind and setTImeout is not supported by JScript
    // }
    return p
  }

  PromiseClass.PENDING = 1
  PromiseClass.FULFILLED = 2
  PromiseClass.REJECTED = 3
  // PromiseClass.STARTED = 4
  PromiseClass.isSETTLED = function(p){ p.state === PromiseClass.FULFILLED || p.state === PromiseClass.REJECTED }

  PromiseClass.resolve = function(v){
    if(v && v.then) return v;
    var p = create(function(resolve, reject){
      resolve(v)
    });
    // console.log('p.chain in resolve. length ' + p.chain.length)
    // p.result = v;
    // wait(function(){ p.processchain(v) }, 0)
    return p;
  }

  function waitForResult(p, cb){
    if(!p) return cb(p)
    if(p.state !== PromiseClass.PENDING) cb(p.result) 
    // console.log(p.state + ' Waiting for ..... ' + p.runFile)
    if(p.runFile && false) {
      while(!existsSync(p.runFile) && p.state === PromiseClass.PENDING) { 
        console.log('Waiting for ResultFle'); wait(cb, 500) 
      }
      cb(p.result)
    }
    else {
      // while(p.state === PromiseClass.PENDING) {
        // console.log('Waiting for Result')
        function waiter(){
          // console.log(p.result)
          if(p.state === PromiseClass.PENDING) wait(waiter, 500);
          else return cb(p.result)            
        }
        wait(waiter, 500)
      // }
    }
  }

  PromiseClass.all = any; // Serialized...

  PromiseClass.__all = function(arr){
    arr.forEach = forEach;
    var resultPs = [];
    var results = [];
    console.log('All : ' + arr.length)
    var pAll = new PromiseClass(function(resolve, reject){

      console.log('All started : ' + pAll)
      // console.dir(pAll)
      
      arr.forEach(function(p){ 
        if(!p.then) { var pfn = p; 
          p = new PromiseClass(function(resolve, reject){ 
            try{
              resolve(pfn()) 
            }
            catch(e){
              reject(e)
            }
          })
          p.start() 
        }
        else { 
          // !p.start ? p.start = function(){return p} : null
          // p.start()
          // .then( function(){ waitForResult(p, function(r){ results.push(r) })  })
        }
        resultPs.push(p)
        // waitForResult(p, function(r){ results.push(r) })
      })
      
      // PB : TODO -- This is the same as processchain!!!
      var allwaitr = function(){
        var allResolved = true
        console.log('resultPs : ' + resultPs.length)
        for(var rIdx =0; rIdx < resultPs.length; rIdx++ ){
            
          if(resultPs[rIdx]) {
            allResolved = false
            waitForResult(resultPs[rIdx], function(r){ 
              if(Promise.resolve(r) !== r) results[rIdx] = r; resultPs[rIdx] = null; 
            })
            break;
          }
        }
        if(allResolved) {
          // console.log('All Reseloved')
          // console.dir(results)
          resolve(results)
        }
        else wait(allwaitr, 500)
      }
      wait(allwaitr, 500)
    })
    
    // pAll.chain = arr;
    return pAll
  }
  
  // PromiseClass.resolve = overrides.resolve;
  Promise = PromiseClass;
  return PromiseClass;
}

function __main( selectedinstance ){
  var downloadsdir = selectedinstance.root + '/Downloads';  

  var callsheltask = function(args) { return function() { return nodeShellExec.apply(null, args) } }
  var gitUser = 'guest';
  var gitEmail = 'guest@bbh.org.in';

  var BUILD_VERSION = '[VI]Version: {version} - built on {date}[/VI]';
  function getVersion() { return BUILD_VERSION; }
  console.log(getVersion())

  function ensureDirectoryExistence(filePath) {
    var dirname = path.dirname(filePath);
    if (existsSyncFolder(dirname)) {
      return filePath;
    }
    ensureDirectoryExistence(dirname);
    fs.mkdirSync(dirname);
    return filePath;
  }

  var getTaskCheckExists = function(command, options) {
    options = options || {}
    return function() {
      var runFile = path.normalize(selectedinstance.root + '/.elxr/run-' + runtimestamp + '/' + stampedFilePfx(new Date()) + 'where' + command + "out.txt");
      var p = nodeShellExec.apply(null, ['cmd', ['/c', 'where', command], { runFile : runFile } ])
      p.runFile = runFile;
      if (options.ignorefailures) {
        return p.then(function(v) { 
          // WScript.Echo('firstThen ' + v);
          return v })['catch']( function(e) { 
            console.error(e);
            // Ignore. Not a major error if where command fails !!!
            throw e;
        })
      }
      else return p.then(function() { 
        // WScript.Echo('firstThen ddd'); 
        return v });
    }
  }

  function verifyAndInstallPrerequisites() {
    fs.writeFileSync(ensureDirectoryExistence(downloadsdir + '/readme.txt'), getVersion() + ' Your local downloads for this instance');
    var downloadbatch =
    "::************************************************************************** \r\n \
    :Download_ <url> <File> \r\n \
      Powershell.exe ^\r\n \
      $AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'; ^\r\n \
      [System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols; ^\r\n \
      (New-Object System.Net.WebClient).DownloadFile('%1','%2') \r\n \
    exit /b \r\n \
    ::**************************************************************************";
    ensureDirectoryExistence(path.normalize(selectedinstance.root + '/.elxr/run-' + runtimestamp + '/readme.txt'))
    fs.writeFileSync(path.normalize(selectedinstance.root + '/.elxr/run-' + runtimestamp + '/download.bat'), downloadbatch);

    var downloadtasks = [];
    var installtasks = [];
    prerequisites.forEach(function(preq) {
      var p = preq.exists().then(function(exists) {
        if (exists) console.log( preq.shellcmd + ' exists');
        else {
          console.log(exists)
          console.log(preq.shellcmd + ' is not installed');
          return preq.preinstallsteps().then(function(){
            // console.log(' task.install : ' + preq.install)
            installtasks.push( function(){ return preq.install() } );
          })
        }
      })
      // .then(function(res){ console.log( 'preinstallsteps ' + res)});
      downloadtasks.push( p )
    });
    // console.log('downloadtasks')
    // console.dir(downloadtasks[0])
    var p = Promise.all(downloadtasks).then(function(){ 
      // console.log('calling install tasks : ' + installtasks.length)
      return any(installtasks) })

    // console.log('Promise.all.chain : ' + p.chain)
    return p 
  }

  // var choiceHandler = function(choices, choice) { 
  //   console.log('chosen : ' + choice)
  //   var decision = choices['d'];
  //   if (choice && choice === 'd' || !choice) {
  //     decision = choices['d']
  //   }
  //   else if (isNaN((+choice))) {
  //     decision = choice
  //   }
  //   else decision = choices[choice-1]
  //   if(!decision) throw 'Invalid selection : ' + decision
  //   return decision
  // }

  // prereq definition helpers. We can't do proper inheritance in cscript. So fallback to global functions.
  function exists(next){
    var self = this;
    console.log('checking existence of ' + self.shellcmd)
    return getTaskCheckExists(self.shellcmd, { ignorefailures: true })().then(function(exists) {
      // console.log('-------------exists=======================')
      // console.dir(exists)
      // console.log(exists + ' ' + self.shellcmd + '     exists')
      if(exists && exists.messages.join(' ').indexOf(self.shellcmd) > -1 ) {
        return true;
      }
      else return false
    })['catch'](function(e){
      // console.log('-------------exists catch=======================')
      console.dir(e)
      return false;
    }) 
  }


  var prerequisites = [
    {
      shellcmd: 'git',
      url: 'https://github.com/git-for-windows/git/releases/download/v2.33.0.windows.2/Git-2.33.0.2-64-bit.exe'
      , installer: 'Git-2.33.0.2-64-bit.exe'
      , installcmd: ['cmd', ['/c', 'start',
        '/WAIT', downloadsdir + '/' + 'Git-2.33.0.2-64-bit.exe'
        , '/VERYSILENT'
        // , '/MERGETASKS=!runcode' // This is required only for vscode...
      ]]
      , preinstallsteps: function() {
        var self = this;
        console.log('Git preinstall steps')

        var steps = [];
        steps.push(
          function(){
            // console.log('cli prompt steps')
            var choices = { 0 : 'guest', 1 : 'chessdemo' }
            return cli.prompt(choices, 'git user name', gitUser).then(function(choice){ gitUser = choice } )
          }
        )

        steps.push(
          function(){
            var choices = { 0 : 'guest@bbh.org.in', 1 : 'chessdemo@bbh.org.in' }
            return cli.prompt(choices, 'git user email', gitEmail).then(function(choice){ gitEmail = choice })
          }
        )
        steps.push(
          function(){
            if (!existsSync(downloadsdir + '/' + self.installer)) {
              return nodeShellExec(selectedinstance.root + '/.elxr/run-' + runtimestamp + '/download.bat', [self.url, downloadsdir + '/' + self.installer])
            }
            else {
              console.log(self.installer + ' Already exits Download skipped.') 
              return Promise.resolve(true)
            }
          }
        )
        return any(steps)
        // return any([any(steps), any(prompts)])
      }
      , installsteps: function () {
        var self = this;
        console.log('Git Installsteps called')
        var ifns = [self.installcmd]
        if(!ifns.map) ifns.map = map;
        return any(ifns.map(callsheltask))['catch'](function(e){ 
          if(e.code === 1602) {
            console.warn("Installation was probably cancelled.")
          }
          else throw e
        })
      }
      , postinstallsteps: function(){

        // PB : TODO -- Detect failure or cancellation before attenpting postinstall steps...
        var steps = [];
        steps.push(
          function(){
            var choices = { 0 : 'guest', 1 : 'chessdemo' }
            return cli.prompt(choices, 'git user name', gitUser).then(function(choice){ gitUser = choice } )
          }
        )

        steps.push(
          function(){
            var choices = { 0 : 'guest@bbh.org.in', 1 : 'chessdemo@bbh.org.in' }
            return cli.prompt(choices, 'git user email', gitEmail).then(function(choice){ gitEmail = choice })
          }
        )

        return any(steps).then(function(){
          var steps = [
            ['git', ['config', '--global', '--add', 'user.name', gitUser]]
            , ['git', ['config', '--global', '--add', 'user.email', gitEmail]]
          ]
          
          if(!steps.map) steps.map = map;
          return any(steps.map(callsheltask)).then(function(){ 

          })
        });
      }
      , install: function () {
        var self = this;
        console.log('Git Install called')
        return any([ /*self.preinstallsteps,*/ function(){ return self.installsteps() }, function(){ return self.postinstallsteps() } ])
      }
      , verifyAndInstall : function(){
        var self = this;
        return self.exists().then( function(exits) {
          if(exists) return self.getUser(null, function(){ return self.postinstallsteps() } )
          else return self.install();
        })
      }
      , exists : exists
      , getUser : function(repo, onNoResult){
        
        
        onNoResult = onNoResult || function(){return false}
        var globalOrLocal = '--global';
        if(!repo) globalOrLocal = '--global';
        else globalOrLocal = '--local'
        var fns = [['git', ['config', globalOrLocal, '--get-all', 'user.name']]]
        if(!ifns.map) ifns.map = map;
        return any(fns.map(callsheltask)).then(function(result){
          // not yet configured.
          if(!result.success) return onNoResult()
          else {
            var users = result.messages[0].trim().split('\n');
            if(users.length === 0 ||
                users.length === 1 && users[0] === 'guest') {
              
              return onNoResult()
            }
            else return users[0]; // PB : TODO == We should probably prompt with all the users available for selection ! 
          }
        })
        ['catch'](function(e){ 
          console.log(e) 
          return onNoResult()
        })
      }
    }
    ,
    {
      shellcmd: 'node',
      url: 'https://nodejs.org/dist/v14.17.6/node-v14.17.6-x64.msi'
      , installer: 'node-v14.17.3-x64.msi'
      , installcmd: ['MSIEXEC.exe', ['/i'
        , path.normalize(downloadsdir + '/' + 'node-v14.17.3-x64.msi')
        , 'ACCEPT=YES', '/passive']]
      , install : function() { 
        var self = this; 
        var ifns = [self.installcmd]
        if(!ifns.map) ifns.map = map;
        return any(ifns.map(callsheltask))['catch'](function(e){ 
          if(e.code === 1602) {
            console.warn("Installation was probably cancelled.")
          }
          else throw e
        })
      }
      
      , exists : exists
      , preinstallsteps: function() {
        var self = this;
        console.log('Node preinstall steps')

        var steps = [];
        steps.push(
          function(){
            if (!existsSync(downloadsdir + '/' + self.installer)) {
              return nodeShellExec(selectedinstance.root + '/.elxr/run-' + runtimestamp + '/download.bat', [self.url, downloadsdir + '/' + self.installer]
                , { waitmsg : 'downloading node please wait' })
            }
            else {
              console.log(self.installer + ' Already exits Download skipped.') 
              return Promise.resolve(true)
            }
          }
        )
        
        return any(steps)
        // return any([any(steps), any(prompts)])
      }
    }
    , 
    {
      shellcmd: 'elxr'
      , installcmd: [ isWin() ? 'npm.cmd' : 'npm' , ['link'], { cwd : selectedinstance.root + '\\elxr' /* cwd should be the cloned dir*/}]
      , preinstallsteps: function() {
        var self = this;
        console.log('Elxr preinstall steps')

        var steps = [];
        steps.push(
          function(){
            console.log('Elxr PreInstallsteps called')
            var ifns = [ ['git', ['clone', 'http://git.bbh/chess\\elxr'] ] ]
            if(existsSyncFolder( selectedinstance.root + '\\elxr')) {
              if(existsSyncFolder( selectedinstance.root + '\\elxr\\.git')) {
                // PB : TODO -- use a elxr guid signature to detect more reliably folders named the same that is not us.
                ifns = [ ['git', ['pull'], { cwd : selectedinstance.root + '\\elxr' } ] ]
              }
              else {
                throw 'elxr subfolder not recognized as a git repository. Please cleanup and continue.'
              }
            }
            else console.log(selectedinstance.root + '\\elxr' + ' NOT FOUND ')
            if(!ifns.map) ifns.map = map;
            return any(ifns.map(callsheltask))['catch'](function(e){ 
              if(e.code === 1602) {
                console.warn("Installation was probably cancelled.")
              }
              else throw e
            })
          }
        )
        return any([any(steps)])
      }
      , installsteps: function () {
        var self = this;
        console.log('Elxr Installsteps called')
        var ifns = [self.installcmd]
        if(!ifns.map) ifns.map = map;
        return any(ifns.map(callsheltask))['catch'](function(e){ 
          if(e.code === 1602) {
            console.warn("Installation was probably cancelled.")
          }
          else throw e
        })
      }
      , install: function () {
        var self = this;
        console.log('Elxr Install called')
        return any([ /*self.preinstallsteps,*/ function(){ return self.installsteps() } ])
      }
      // , exists : function(){
      //   console.log('Elxr PreInstallsteps called')
      //   var ifns = [ ['git', ['clone', 'http://git.bbh/chess\\elxr'] ] ]
      //   if(existsSyncFolder( selectedinstance.root + '\\elxr')) {
      //     if(existsSyncFolder( selectedinstance.root + '\\elxr\\.git')) {
      //       // PB : TODO -- use a elxr guid signature to detect more reliably folders named the same that is not us.
      //       ifns = [ ['git', ['pull'], { cwd : selectedinstance.root + '\\elxr' } ] ]
      //     }
      //     else {
      //       throw 'elxr subfolder not recognized as a git repository. Please cleanup and continue.'
      //     }
      //   }
      //   else console.log(selectedinstance.root + '\\elxr' + ' NOT FOUND ')
      //   if(!ifns.map) ifns.map = map;
      //   return any(ifns.map(callsheltask))['catch'](function(e){ 
      //     if(e.code === 1602) {
      //       console.warn("Installation was probably cancelled.")
      //     }
      //     else throw e
      //   })
      // }
      , exists : exists
      
    }
  ]

  if(!prerequisites.forEach) prerequisites.forEach = forEach;

  // nodeShellExec(selectedinstance.root + '/.elxr/run-' + '1629889572461' + '/download.bat'
  //   , ['https://github.com/git-for-windows/git/releases/download/v2.31.0.windows.1/Git-2.32.0.2-64-bit.exe'
  //   , downloadsdir + '/' + 'Git-2.32.0.2-64-bit.exe']).start()
  promises.push(verifyAndInstallPrerequisites())

  startPromises();
}