var scripthostName = 'node'
var __Promise = {};
var promises = [];
function startPromises(){
  promises.forEach(function(p){ 
    // console.log(p.chain)
    p ? p() : null; 
    // promises.splice(0,1) 
  })
}

function isWin(){ return /^win/.test(process.platform) }
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();

try {
  console.log("detected node")
  // var WScript = console || WScript // Dummy
  // console.Echo = console.log

  __Promise = Promise 
    var __require = require
    var map = Array.map

    function startPromises(){
      promises.forEach(function(p){ 
        // console.log(p.chain)
        p ? Promise.resolve(p) === p ? p : p() : null; 
        // promises.splice(0,1) 
      })
    }
    
      
    // --------------------------------------------
    // 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').existsSync;
      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) {
  WScript.Echo('detected wsh')
  scripthostName = 'wsh'
  WScript.Echo(WScript.FullName)

  // WScript.Echo('--' + WScript.Arguments(0) + '--')
  // WScript.Echo('------------ALL-----------')
  // var argEnumerator = new Enumerator(WScript.Arguments)
  // for(; !argEnumerator.atEnd(); argEnumerator.moveNext()){
  //     WScript.Echo('-arg-' + argEnumerator.item() + '-arg-')    
  // }


  // // Unnamed
  // WScript.Echo('------------UNNAMED-----------')
  // argEnumerator = new Enumerator(WScript.Arguments.Unnamed)
  // for(; !argEnumerator.atEnd(); argEnumerator.moveNext()){
  //     WScript.Echo('-arg-' + argEnumerator.item() + '-arg-')    // Value
  // }

  // // Named
  // WScript.Echo('------------NAMED-----------')
  // argEnumerator = new Enumerator(WScript.Arguments.Named)
  // for(; !argEnumerator.atEnd(); argEnumerator.moveNext()){
  //     // WScript.Echo('-arg-' + argEnumerator.item(argEnumerator) + '-arg-')   // Key
  //     WScript.Echo('-key-' + argEnumerator.item() + '-key-')    // Key
  //     WScript.Echo('-val=' + WScript.Arguments.Named(argEnumerator.item()) + '=val-')    // Value
  // }

  var all = WScript.Arguments.Named('all');
 
  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])}
    }
  }

  // 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;
  //   }
  // };

    // // --------------------------------------------
    // // 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)
      );
    }
   
  
    // __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) {
      if(!all && preq.optional) return
      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;
    }) 
  }

  function unique(arr) {
      var hash = {}, result = [];
      for ( var i = 0, l = arr.length; i < l; ++i ) {
          if ( !hash.hasOwnProperty(arr[i]) ) { //it works with objects! in FF, at least
              hash[ arr[i] ] = true;
              result.push(arr[i]);
          }
      }
      return result;
  }

  // var ENV = Object.assign({}, process.env); // Shallow clone it.
  var WshShell = WScript.CreateObject("WScript.Shell")
  var ENV = WshShell.Environment("Process")
  // WScript.echo( ENV("Path") )
  function sysAddPathVar(path){

    return true; // PB : TODO -- Not yet enabled. Remove sys path from path before saving.
    // Object.assign({
    //   inherit: true, shell: true, env: ENV, title: `${command} ${args}`
    // }, options)
    
    
    // var newpath = ENV("Path").split(';');
    // newpath = Array.from(new Set(newpath.push(path))).join(';')
    var newpath = [];
    newpath.push(path)
    newpath = unique(newpath).join(';')
    // path.split(';').forEach(pel => { var kv = pel.split('='); kv[0] === key ? null : newpath.push(pel);  } )
    return any([nodeShellExec('setx', [/*'/m',*/ 'PATH', '"%PATH%;' + newpath + '"' ])
        // , nodeShellExec('set', [/*'/m',*/ 'PATH', '"%PATH%;' + newpath + '"' ])
      ] );
  }
  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: 'python2'
        , optional : true
        , url: 'https://www.python.org/ftp/python/2.7.18/python-2.7.18.amd64.msi'
        , installer: 'python-2.7.18.amd64.msi'
        , installcmd: ['MSIEXEC.exe', ['/i'
          , path.normalize(downloadsdir + '/' + 'python-2.7.18.amd64.msi')
          , 'ACCEPT=YES', '/passive']]
        , preinstallsteps : function() { return Promise.resolve(true) }
        , install : function() { 
          var self = this; 
          var ifns = [self.installcmd]
          if(!ifns.map) ifns.map = map;
          console.log('Installing')
          return any(ifns.map(callsheltask))['catch'](function(e){ 
            if(e.code === 1602) {
              console.warn("Installation was probably cancelled.")
            }
            else throw e
          }).then(function(){
            return self.postinstallsteps()
          })
        }
        , postinstallsteps : function() { return sysAddPathVar('%USERPROFILE%\\AppData/Local\\Microsoft\\WindowsApps') }
        , exists : exists
    }
    , { 
      shellcmd: 'code'
      , optional : true
      , url: 'https://vscode-update.azurewebsites.net/latest/win32-x64/stable'
      , installer: 'vscode-win32-x64-latest-stable.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() { return Promise.resolve(true) }
      , install : function() { 
        var self = this; 
        var ifns = [self.installcmd]
        if(!ifns.map) ifns.map = map;
        console.log('Installing')
        return any(ifns.map(callsheltask))['catch'](function(e){ 
          if(e.code === 1602) {
            console.warn("Installation was probably cancelled.")
          }
          else throw e
        }).then(function(){
          return self.postinstallsteps()
        })
      }
      , postinstallsteps : function() { return sysAddPathVar('%USERPROFILE%\\AppData/Local\\Microsoft\\WindowsApps') }
      , exists : exists
    }
    , {
      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();
}