var doBuild = true; // true = build the program
                    // false = write a build script
var quiet = false;  
var verbose = false; // not a command line option
var stopEarly = false;
                    
function objMatches(a,platform,mode)
{
  var ret = ((!a.get('platform') || a.get('platform') == (platform?platform.get('name'):'')) && 
          (!a.get('mode') || a.get('mode') == (mode?mode.get('name'):'')));
   return ret;
}

function getProperty(name,node,platform,mode,delimiter)
{
   var d = '';
   if (delimiter == null) delimiter = '';
   var l = node.find(name);
   for (var i in l)
   { 
       if (objMatches(l[i],platform,mode))
         d += l[i].cdata+delimiter ;
   }
   return d;
}

function getAttribute(name,node,delimiter)
{
   var l = node.get(name);
   if (delimiter == null) delimiter = '';
   if (l) return l+delimiter;
   return '';
}
     
//BCC Error E2285 rs/tbl_main.cpp 78: Message
//gcc filename:error:line: description
function scanErrors(msgs,errors)
{
 var l;
 while(l=msgs.readln())
 {  
  if (l.indexOf("Error") == 0) //Borland C++
  {
   return false;
  }
  if (l.search(/\w+\:\d+\:/) >= 0 && l.indexOf("warning") == -1) //gcc
  {
   return false;
  }
  if (l.indexOf("(.text") >= 0) //gcc link
  {
   return false;
  }
 }
 return true;
}

function runCommand(cmd,errors)
{
if (verbose) writeln(cmd);
   var msgs = new Stream;
   var pc = new Stream('exec://'+cmd);  
   var se = pc.stderr;
   while (pc.canWrite)
     {
      sleep(10);
      while (pc.canRead)    
      {
       var c = pc.read(1);
       if (verbose) write(c);
       msgs.write(c);
      }
      while (se.canRead)
      {
       var c = se.read(1);
       if (verbose) write(c);
       msgs.write(c);
      }
     }
   pc.close();
   delete pc;
   msgs.rewind();
   if (!scanErrors(msgs))   
    {
     msgs.rewind();
     errors.writeln(cmd);
     errors.append(msgs);
     return false;
    }
   return true;
}

function rejoin(template,array)
{
 var s = '';
 for (var a in array) if (array[a]) s += template.replace(/\$value/g,array[a]);
 return s;
}

function compileFile(cmds,compiler,target,platform,mode,group,file)
{
  var define =  getProperty('define',mode,platform,null,';');
      define += getProperty('define',platform,null,mode,';');
      define += getProperty('define',target,platform,mode,';');
      define += getProperty('define',group,platform,mode,';');
      define += getProperty('define',file,platform,mode,';');
      define += getProperty('define',compiler,platform,mode,';');

  var include = group.get('path');
      if (include) include += ';';
      include +=  getAttribute('include',mode,';');
      include += getAttribute('include',platform,';');
      include += getAttribute('include',group,';');
      include += getAttribute('include',file,';');
         
  var cmd = compiler.get('path');
  var params = compiler.get('parameters');
  var infile = group.get('path') + file.cdata;  
  var outfile = file.cdata.replace(/\.\w*$/,compiler.get('output'));
  
  var options = getProperty('options',compiler,platform,mode,' ');
  
  var templates = compiler.find('template','name','define');
  if (templates.length)
  {
    define = rejoin(templates[0].cdata,define.split(';'))
  }
  
  templates = compiler.find('template','name','include');
  if (templates.length)
  {
    include = rejoin(templates[0].cdata,include.split(';'))
  }
  
  params = params.replace(/\$input/g,infile);
  params = params.replace(/\$output/g,outfile);
  params = params.replace(/\$options/g,options);
  params = params.replace(/\$define/g,define);
  params = params.replace(/\$include/g,include);
  params = params.replace(/\$intermediate/g,target.get('intermediate'));
  
  if (doBuild)
  { 
    writeln(file.cdata);
    if (!runCommand(cmd + ' ' + params,cmds)) 
       throw(file.cdata); //+'\r\n'+cmd+' '+params);
  }
  else
  {
   cmds.writeln(cmd + ' ' + params);  
  }
  return outfile;
}

//defines and includes are additive options

function doCompile(cmds,project,target,platform,mode)
{
  var defaultCompiler = platform.find('compile')[0];
  
  var objFiles = [];
  
  var gnames = target.find('group');
  var groups = [target]; //include top-level <file> tags
  for (var i in gnames)
   groups = groups.concat(project.find('group','name',gnames[i].cdata));
  
  for (var i in groups)
  {
    if (objMatches(groups[i],platform,mode))
    {
      var files = groups[i].find('file');
      for (var j in files)
      {
        if (objMatches(files[j],platform,mode))
        {
          var filename = files[j].cdata;
          var type = filename.substr(filename.lastIndexOf('.'));
          var compiler = platform.find('compile','input',type)[0];
          if (!compiler) compiler = defaultCompiler;
          objFiles.push(compileFile(cmds,compiler,target,platform,mode,groups[i],files[j]));
        }             
      }
     }
  } 
  return objFiles;
}

function findFile(target,linker,file)
{
 if (!linker.get('resolve')) return file;
 return target.get('intermediate')+file;
 return file;
}

function doLink(cmds,objFiles,target,platform,mode)
{
  //link step

  var linker = platform.find('link')[0];
  var cmd = linker.get('path');
  var params = linker.get('parameters');
  var infiles = ''; 
  var rfiles = '';
  for (var i in objFiles) 
   { 
     if (objFiles[i].indexOf('.o') > 0) infiles += findFile(target,linker,objFiles[i])+' ';
     else rfiles += findFile(target,linker,objFiles[i]) + ' ';
    }
  var options = getProperty('options',linker,platform,mode,' ');
  
  //<link parameters="$options, C0X32 $input,$output,$mapfile,IMPORT32 CW32MT,$deffile " >
  //<target platform=win32 output="jsdb.exe" mapfile="jsdb.map" deffile="jsdb.def" intermediate="obj\">

  var libraries = target.get('libraries');

  var templates = linker.find('template','name','library');
  if (templates.length)
  {
      libraries = rejoin(templates[0].cdata,libraries.split(';'))
  }
  params = params.replace(/\$input/g,infiles);
  params = params.replace(/\$resource/g,rfiles);
  params = params.replace(/\$output/g,target.get('output'));
  params = params.replace(/\$mapfile/g,target.get('mapfile'));
  params = params.replace(/\$deffile/g,target.get('deffile'));
  params = params.replace(/\$libraries/g,libraries);
  params = params.replace(/\$options/g,options);  
  params = params.replace(/\$intermediate/g,target.get('intermediate'));
  
  if (doBuild)
  { 
    writeln(target.get('output'));
    if (!runCommand(cmd + ' ' + params,cmds)) 
      throw(target.get('output')+'\r\n'+cmd+' '+params);
  }
  else
  {
    cmds.writeln(cmd + ' ' + params); 
  }
}

if (typeof(system.arguments) == 'undefined' || system.arguments.length < 2)
{
  writeln("Usage: JSDB.EXE make.js jsdb.project win32 [/debug] [/preview] [/quiet] [/verbose] [/stopwarn]") 
}
else
{
 load('xml.js')
 var project = XML.read(new Stream(system.arguments[0]),'target,project,mode,configure,define,compile,link,group,file,platform,options,template');
 var platform = project.find('platform','name',system.arguments[1])[0];
 if (!platform) platform = project.find('platform')[0];
  
 var mode = null; 
 
 for (var i=2; i<system.arguments.length; i++)
 { 
   if (system.arguments[i].toLowerCase() == '/preview')
     doBuild = false;
   else if (system.arguments[i].toLowerCase() == '/quiet')
     quiet = true;
   else if (system.arguments[i].toLowerCase() == '/verbose')
     verbose = true;
   else if (system.arguments[i].toLowerCase() == '/stopwarn')
     stopEarly = true;
   else if(!mode)
   {
     mode = project.find('mode','name',system.arguments[i].substr(1))[0];
   }
   // else other flags     
 }

 if (!mode) mode = project.find('mode')[0];
 
 var targets = project.find('target');

 if (doBuild) writeln('Building ',project.get('name'),' for ',platform.get('name'));
 
 var commands = new Stream; 
 
 // run platform-dependent configure scripts
 
 var configure = platform.find('configure')
 try 
 {
  for (var i in configure)
  {
    if (objMatches(configure[i],null,mode)) 
    {
      var cmd = configure[i].cdata;
      if (doBuild)
      { 
        writeln(cmd);
        if (!runCommand(cmd,commands)) 
           throw(cmd); //+'\r\n'+cmd+' '+params);
      }
      else
      {
        commands.writeln(cmd);  
      }    
    }
  }
  
 }catch(err) {writeln("Error in ", err); }
 
 
 // look over potential targets
 
 try {
 for (var i in targets)
  {
   if (objMatches(targets[i],platform,mode)) 
   {
     if (doBuild) writeln('Building ',targets[i].get('output'));
     var objs = doCompile(commands,project,targets[i],platform,mode)
     doLink(commands,objs,targets[i],platform,mode);
   }
  } 
 } catch(err) {writeln("Error in ", err); }
 if (!quiet)
 {
   commands.rewind();
   write(commands);
 }
 commands.rewind();
 log = new Stream('make.log','wt');
 log.append(commands);
}