/*  custom javascript functions, for this instrument, can be specified here */

//=================================================
// compute results using current round of data

function computeResults() {

var nrounds,clientIDs,poo,use_Shares,use_Discounts,shareList,discountsList,use_Q;
  var quality_maxpoints ;


 setControlVar('FUNCTION_SUCCESS',0);

// current round

nround=getVarInfo('nowround.g','value');
if (typeof(submittedData['$rounds'][nround])=='undefined') {
  poo='<b>Error:</b> submitted data does not contain information on this round ('+nround+')';
  return poo;
}

// the following are globals that are used by the doBasic and doShares functions

var clientIDs=extractClientList('S',nround);
  
navailTickets=getVarInfo('avail_Tickets.g','value');
naccept=getVarInfo('naccept.g','value');
use_Shares=getVarInfo('use_Shares.g','value');
use_Discounts=getVarInfo('use_Discounts.g','value');

use_Fee=getVarInfo('use_fee.g','value');
use_Q=getVarInfo('use_quality.g','value');
qCost=getVarInfo('quality_price.g','value');

ntickets=getVarInfo('numtickets.g','value'); 
isDemo=getVarInfo('isDemo.g','value');

use_UnderBid=getVarInfo('use_TargetBid.g','value');

use_BidPenalty=getVarInfo('use_BidPenalty.g','value');
bidPenaltyFactor=getVarInfo('bidPenalty_factor.g','value');



poo='<ul> \n' ;
poo=poo+'<li>Current round: '+nround + ' \n ';
if (isDemo==1) poo=poo+' (demo) ' ;
poo=poo+'<li># of participants : '+clientIDs.length+ ' \n ';
poo=poo+'<li># tickets per entry: '+ ntickets+ ' \n ';
poo=poo+'<li>Total #tickets accepted (target): '+ naccept+ ' \n ';

shareList=[];  discountsList=[];
if (use_Discounts==1) {
   discountsList=getVarInfo('discounts.g','value');   
   poo=poo+ '<li> Discounts applied per type: '+discountsList.join(' | ' )+' \n ';
}
if (use_Shares==1) {
  shareList=getVarInfo('shares.g','value');
  poo=poo+ '<li> Maximum accepted per type: '+shareList.join(' | ' )+' \n ';
}

if (use_Q==1) {
      quality_maxpoints=getVarInfo('quality_maxpoints.g','value'); var quality_price=getVarInfo('quality_price.g','value');
      poo=poo+ '<li> Quality discounts: maximum of '+quality_maxpoints+' at '+quality_price+' per point  \n ';
}

if (use_UnderBid==1) {
    underBidFactor=getVarInfo('underBid_factor.g','value');
    poo=poo+ '<li> underBid discount: factor of '+underBidFactor+' \n ';
}

poo=poo+'\n </ul> \n ';

if (nround==0) {
  poo=poo+'<p/> <b>Round zero?</b> ';
  return poo;
}

saveResultsVar()  ;              // clear resultsData
saveResultsVar('','ROUND',nround)  ;    // set ROUND in resultsData

if (use_Shares!=1) poo=poo+'<p />'+doBasic(clientIDs,use_Q);
if (use_Shares==1) poo=poo+'<p />'+doShares(clientIDs,use_Discounts,use_Q);



return poo ;  
}

/*  =======================
 compute basic results: no discounts, no shares.

  STATUS  : "disposition" code for the tickets 
               Possible values are:
                1 : Not offered
                2  : Rejected in first round
                3 : Rejected in 2nd round               (used in doShares)
                4  : accepted
                .  : not available (not displayed)


*/
function doBasic(clientIDs,use_Q) {

/* ids offers groups prodcosts maxbids fees qpoints scores underbids earnings times ticknums */

var z,zoffersA,zoffersR,zNoOffers,zOffers;
var aid,i,zoffer,zstatus,totalCostAllSubmit=0;
var totalEarnAllAccept=0,totalCostAllAccept=0;totalCostAllReject=0;
var vlist,tt,amaxbid,zoffer,score0,ofsort,nacceptDo,ddg,indices,im0,igroup ;  
var freqGroups=[],submitGroups=[],acceptGroups=[],glist=[],m,agroup;   
var tmp,noOffers,gotOffers,ilistAccept,ilast;
var roundEarn,vmtx,aearn,ztmp;

var tmins=[],minBids_group=[];

// make tickets object for all tickets  

z=readTickets(clientIDs,nround,use_Q) ;                  // creates a data-object containing the tickets


// initialize stuff. Status= 1 if not offered, otherwise 2 (rejected)
 
z=z.math( " isOffer= (offers eq '') +  (offers eq 0 ) ; isOffer=1-isOffer ; "+ 
             ' earnActual=0; status= 1 + isoffer ; scores2=scores ;'+
             ' totalCostAllReject.c=0 ; totalCostAllAccept.c=0; '+
             ' totalCostAllSubmit.C=0; totalEarnAllAccept.c=0 ');

zNoOffers=z.math("select isOffer eq 0 " );

zOffers=z.math(' select isOffer eq 1 ;  '+
                   ' totalCostAllSubmit.C=prodcosts.sum(); '+
                   ' if use_Fee eq 1 then earnActual=-fees ;   ','use_Fee',use_Fee); 

  nacceptDo=Math.min(naccept,zOffers.nentries);         // can not accept more than total number of offers

// groupMin computation
    zOffers=zOffers.math(' ' +
       ' BY groups ; '+
       '    groupList.A=by_Vals();   groupOfferMin=offers.minVal();  groupMin.A=by_Stats() ;  '+
       ' freqGroups.A=groups.freq(groupList) ; '+
       ' BY 0 ; ');

     tmins=zOffers.get('groupMin');
     glist=zOffers.get('groupList');
     for (var ig=0;ig<glist.length;ig++) {
         gnum=glist[ig];
         minBids_group[gnum]=tmins[ig];                 // indices correspond to group numbers
     }

// augment score with penalty?
  if (use_BidPenalty==1) {
    zOffers=zOffers.math(' ' +
       ' overBid=offers-groupOfferMin ;  overBid=overBid - (qCost*qpoints) ; overBid=overBid.max(0); '+
       ' oPenalty=overBid*bPenalty ; '+
       ' scores2=scores + oPenalty ','bPenalty qCost',bidPenaltyFactor+' '+qCost); 

   } else {                             // do NOT augment
      zOffers=zOffers.math(' groupList.A=groups.unique(1);  freqGroups.A=groups.freq(groupList) ; '+
                                 ' scores2=scores ');
   }

  zOffers=zOffers.math(' sortScore=scores2.sort() ');             // sort 'em, perhaps by "augmented" score
  
// zoffersA will contain the "accepted" tickets

  zOffersA=zOffers.math('select sortScore lt naccept ','naccept',nacceptDo);     

  if (zOffersA.nentries>0) {
     tmp= ' earnActual=earnings ; status=4 ; '+
          '  totalEarnAllAccept.C=earnings.sum(); ' +
          '  totalCostAllAccept.C=prodcosts.sum() ; '+
          '  acceptGroups.A=groups.freq(groupList) ';
     zOffersA=zOffersA.math(tmp);
  }

// zoffersR contains the rejected ones

  zOffersR=zOffers.math('select sortScore ge naccept ','naccept',nacceptDo);   
  if (zOffersR.nentries>0) { 
      zOffersR=zOffersR.math('status=2 ; totalCostAllReject.C=prodcosts.sum() ' );
  }

// udpate zOffers using accepted (zOffersA) and rejected (zOffersR)
 z.update(zOffersA);
 if (zOffersR.nentries>0) z.update(zOffersR,'TOTALCOSTALLREJECT EARNACTUAL SCORES2 STATUS') ;

/* --- aggregate earnings across tickets, and save to  RoundEarn variable */
for (var im=0;im<clientIDs.length;im++) {
    aid=clientIDs[im];                                     // client specific resutls (for non-empty offers)
    tmp=0 ;
     if (z.nentries>0) {
       tmp=z.math('tmp= (ids eq anid) * earnactual; totearn.c=tmp.sum(); return  totearn ;  ','anid',aid) ;
    }
    saveResultsVar(aid,'RoundEarn',tmp);
}
 
   
// more  general results for this round (to save to resultsVar

saveResultsVar('*','nTickets',ntickets);
saveResultsVar('*','isDemo',isDemo);

maxAccept='n.a';
if (zOffersA.nentries>0) {
   maxAccept=zOffersA.math('maxAccept.C=scores2.maxVal() ; return maxAccept ');
   maxAccept= fix2(maxAccept);
}
saveResultsVar('*','maxAccept',maxAccept);


/* ---  save id specific results to resultsVar. Since need to save by ticket, so do one ticket at a time */
for (var im=0;im<z.nentries;im++) {
  aid=z.IDS[im];                                     // client specific resutls (for non-empty offers)
  itick=z.TICKNUMS[im];
  igroup=z.GROUPS[im];
  saveResultsVar(aid,'earn['+itick+']' ,z.EARNACTUAL[im]);
  saveResultsVar(aid,'status['+itick+']',z.STATUS[im]);
  saveResultsVar(aid,'scores2['+itick+']',z.SCORES2[im]);
  saveResultsVar(aid,'maxAcceptTicket['+itick+']',maxAccept);
  saveResultsVar(aid,'minBidGroup['+itick+']',minBids_group[igroup]); 
}

/*  -- save some general results --- */
minReject='n.a';
if (zOffersR.nentries>0) {        
   minReject=zOffersR.math('minReject.C=scores2.minVal() ; return minreject ');
   minReject=fix2(minReject);
}
saveResultsVar('*','minReject',minReject);

saveResultsVar('*','nSubmits',zOffers.nentries);
saveResultsVar('*','nAccept',nacceptDo);

saveResultsVar('*','totalEarnAllAccept',fix2(z.TOTALEARNALLACCEPT));
saveResultsVar('*','totalCostAllAccept',fix2(z.TOTALCOSTALLACCEPT));
saveResultsVar('*','totalCostAllReject',fix2(z.TOTALCOSTALLREJECT));
for (var m=1;m<=navailTickets;m++)  {
  submitGroups[m]=0; acceptGroups[m]=0;
}

glist=z.get('grouplist');

for (var m=0;m<glist.length;m++) {
 agroup=glist[m];
 submitGroups[agroup]=z.FREQGROUPS[m] ;
 acceptGroups[agroup]=z.ACCEPTGROUPS[m] ;
}
saveResultsVar('*','groupList[*]',glist);
saveResultsVar('*','nAcceptsGroup[*]',acceptGroups);
saveResultsVar('*','nSubmitsGroup[*]',submitGroups);


// and some stuff to display
var sorder=indexSort2(z.OFFERS,z.STATUS,'ND');
var zz=[];
for (var m=0;m<sorder.length;m++) zz=zz.concat(sorder[m]);

tt=makeResultsTable(z,0,zz,use_Q,minBids_group);

setControlVar('FUNCTION_SUCCESS',1);           // set success flag

ddg='<ul><li>Number of valid offers: '+zOffers.nentries+' \n <li>Number of empty offers: '+zNoOffers.nentries+'</ul> \n ';

setControlVar('roundTotalEarn',z.TOTALEARNALLACCEPT);

return ddg+'<p/>'+tt ;                  // done!

}


//=========================================== maxa
//  Compute results: shares

function doShares(clientIDs,use_Discounts,use_Q) {  
 
var z,z1,zNoOffers,tmp,zOffers,glist,gfreq;
var shareList,gNum,gShare,ig,iv,tmp,maxG;
var zGroupSurvive,maxAcceptG=[],minRejectG=[],maxAcceptBoth=[] ;
var naccept,nacceptDo;
var aids,ticknums,groups,earnActual,status,im,aid,itick,igroup,zz,sorder,tt,ddg ;
var submitGroups=[],acceptGroups=[],glist=[],m,agroup;
var glistA=[], gFreqA=[];
var minBids_group=[];

var nacceptActual=0, totalCostAllReject=0,totalCostNoOffers=0 ,totalCostAllSubmit=0 ;
var maxAccept2=-1 , maxAccept2=-1 , minReject2=-1 ;
var totalEarnAllAccept=0, totalCostAllAccept=0;
var tmins;

// vars in z will be:
//   ids offers groups prodcosts maxbids fees qpoints scores underbids earnings times ticknums

z=readTickets(clientIDs,nround,use_Q) ;                  // creates a data-object containing the tickets

// more initialization

z=z.math( " isOffer= (offers eq '') +  (offers eq 0 ) ; isOffer=1-isOffer ; earnActual=0; status= 1 ");

z1=z.math('select 1 eq 2 ' );            // clone it, with no records (1st and 2nd round results will be combined into here )

zNoOffers=z.math("select isOffer eq 0 ; totalCostNoOffers.C = prodcosts.sum() ;   " );

if (zNoOffers.nentries>0) z=z.combine(zNoOffers);

zOffers=z.math(' select isOffer eq 1 ;  status=2 ; '+
                   ' totalCostAllSubmit.C=prodcosts.sum() ;  '+
                   ' totalCostAllAccept.C=0 ;totalCostsAllReject.C=0   ; totalEarnAllAccept.C=0 ; '+
                   ' if use_Fee eq 1 then earnActual=-fees ;   ','use_Fee',use_Fee);   

  zOffers=zOffers.math(' '+
       ' BY groups ; '+
       '    groupList.A=by_Vals();   groupOfferMin=offers.minVal();  groupMin.A=by_Stats() ;  '+
       ' BY 0 ;  '+
       ' freqGroups.A=groups.freq(groupList)  ');

  tmins=zOffers.get('groupMin');
  glist=zOffers.get('groupList');
  for (var ig=0;ig<glist.length;ig++) {
         gnum=glist[ig];
         minBids_group[gnum]=tmins[ig];                 // indices correspond to group numbers
  }

  if (use_BidPenalty==1) {

    zOffers=zOffers.math(' '+
       ' overBid=offers-groupOfferMin ;  overBid=overBid - (qCost*qpoints) ; overBid=overBid.max(0); '+
       ' oPenalty=overBid*bPenalty ; '+
       ' scores2=scores + oPenalty ','bPenalty qCost',bidPenaltyFactor+' '+qCost); 
  } else {
      zOffers=zOffers.math('groupList.A=groups.unique(1); '+
                'scores2=scores ');
   }
   zOffers=zOffers.math('sortScore=scores2.sort() ');

// get some general variables 
glist=zOffers.get('grouplist');
gfreq=zOffers.get('freqGroups');

totalCostAllSubmit=zOffers.get('totalCostAllSubmit');

// do first round (within group rejections)

shareList=getVarInfo('shares.g','value');               // number of shares accepted per group (nb: group 0 is never used)

// for each group, see who survives

for (var ig=0;ig<glist.length;ig++) {
  gNum=glist[ig];
  gShare=shareList[gNum];

   zGroupSurvive=zOffers.math(' '+
      ' select groups eq agroup ; '+
      ' sort1=scores2.sort() ; n1.C = nacceptG - 1;  '+
      ' if sort1.length()  gt nacceptG then maxAcceptG.C=scores2.order(n1) ~ minRejectG.C = scores2.order(nacceptG) ; ' +
      ' if sort1.length()  le nacceptG then maxAcceptG.C=scores2.maxVal()~minRejectG.c=-1 ;  '+
      ' select sort1 lt nacceptG  ; status=3; '+
      ' ','agroup nacceptG',[gNum,gShare]) ;

// get group specific (1st round) results 
  maxAcceptG[gNum]=zGroupSurvive.get('maxAcceptG');
  minRejectG[gNum]=zGroupSurvive.get('minRejectG');

  z1=z1.combine(zGroupSurvive);            // combine back into all-offers dataObject

}

// z1 now contains survivors of first round; with status=3 ("did not survive second, all tickets, round ")
// now use these survivores in a second round 

naccept=getVarInfo('naccept.g','value');
nacceptDo=Math.min(naccept,zOffers.nentries);         // can not accept more than total number of offers

// note: use default values, defined above, if NO accepted offers

if (z1.nentries>0) {                    
  tmp= ' sort2=scores2.sort() ; n2.c=naccept-1;  '+
    ' if sort2.length() gt naccept then  maxAccept.C=scores2.order(n2)~  minReject.C = scores2.order(naccept) ; ' + 
    ' if sort2.length() le naccept then  maxAccept.C=scores2.maxVal() ~ minReject.c=-1 ;  ' +
    ' select sort2 lt naccept  ; '+
    ' status=4 ; earnActual=earnings ; ' +    
    ' totalEarnAllAccept.C=earnings.sum(); totalCostAllAccept.C=prodcosts.sum() ; '+
    ' groupListA.A=groups.unique(1); freqGroupsA.A=groups.freq(groupListA)  ; ' ;

  z2=z1.math(tmp,'naccept',nacceptDo);     // z2 contains accepted offers (surviors of 2nd round)

  nacceptActual=z2.nentries ;

  maxAccept2=z2.get('maxAccept');
  minReject2=z2.get('minReject');
  totalEarnAllAccept=z2.get('totalEarnAllAccept');
  totalCostAllAccept=z2.get('totalCostAllAccept');
  glistA=z2.get('groupListA');
  gFreqA=z2.get('freqGroupsA');

 z1=z1.combine(z2);                                 // add 2nd round results back into 1st round results

 zOffers= zOffers.combine(z1)                      // combine offers back into all tickets
 z= z.combine(zOffers);                          // and combine this 1st&2nd round results back into offers)

}


// ----  Z now has all info on accepted and rejected. So save ticket info to resultsVar. After that, create some general stats and save them

// 2,3 status is rejected -- sum up offers from all rejected offers (have to be careful, might be NO rejected offers)
totalCostAllReject=z.math('arf.a=array(2,3); zoob=status.in(arf); arf2=zoob * prodcosts ; _t3.c=arf2.sum(); return _t3 ; ');

ddg='<ul><li>Number of valid offers: '+zOffers.nentries+' \n <li>Number of empty offers: '+zNoOffers.nentries+'</ul> \n ';

saveResultsVar()  ;              // clear resultsData
saveResultsVar('','ROUND',nround)  ;    // set ROUND in resultsData

saveResultsVar('*','nTickets',ntickets);
saveResultsVar('*','isDemo',isDemo);

saveResultsVar('*','nSubmits',zOffers.nentries);
saveResultsVar('*','nAccept',nacceptActual);

saveResultsVar('*','groupListOffer[*]',glist);
saveResultsVar('*','freqListOffer[*]',gfreq);

if (maxAccept2<0) {
   saveResultsVar('*','maxAccept','n.a.');
} else {
  saveResultsVar('*','maxAccept',fix2(maxAccept2));  
}
if (minReject2<0) {
   saveResultsVar('*','minReject','n.a.');
} else {
  saveResultsVar('*','minReject',fix2(minReject2));
}

tmp=[];
for (var iv=0;iv<maxAcceptG.length;iv++) {
   if (maxAcceptG[iv]<0 ) {
        tmp[iv]='n.a';
   } else {
        tmp[iv]=fix2(maxAcceptG[iv]);;
   }
}
saveResultsVar('*','maxAcceptGroup[*]',tmp);

tmp=[];
for (var iv=0;iv<minRejectG.length;iv++) {
   if (minRejectG[iv]<0 ) {
        tmp[iv]='n.a';
   } else {
        tmp[iv]=fix2(minRejectG[iv]);;
   }
}
saveResultsVar('*','minRejectGroup[*]',tmp);
   
saveResultsVar('*','totalEarnAllAccept',fix2(totalEarnAllAccept));
saveResultsVar('*','totalCostAllAccept',fix2(totalCostAllAccept));
saveResultsVar('*','totalCostAllSubmit',fix2(totalCostAllSubmit));
saveResultsVar('*','totalCostAllReject',fix2(totalCostAllReject));


saveResultsVar('*','groupListAccept[*]',glistA);
saveResultsVar('*','freqListAccept[*]',gFreqA);


/* Compute maximum accepted bid (min of group and overall maximum) for all types*/

for (var ig=0;ig<glist.length;ig++) {
  gNum=glist[ig]; 
  maxG=maxAcceptG[gNum];                       
  if (maxG <0 && maxAccept2 <0) {                // be careful with -1 values (they signify "missing")
        maxAcceptBoth[gNum]='n.a.';
  } else if (maxG<0) {
        maxAcceptBoth[gNum]=maxAccept2 ;
  } else if (maxAccept2<0) {
        maxAcceptBoth[gNum]=maxG ;
  } else {
       maxAcceptBoth[gNum]=Math.min(maxG,maxAccept2);
  }
}

/* save id/ticket specific info (even for non-submitters) */
aids=z.get('IDS');
ticknums=z.get('TICKNUMS');
groups=z.get('GROUPS');
earnActual=z.get('earnActual');
status=z.get('status');

for (var im=0;im<z.nentries;im++) {
  aid=aids[im];                                     // client specific resutls
  itick=ticknums[im];                              // and ticket specific
  igroup=groups[im];
  saveResultsVar(aid,'earn['+itick+']' ,earnActual[im]);
  saveResultsVar(aid,'status['+itick+']',status[im]);
  saveResultsVar(aid,'scores2['+itick+']',z.SCORES2[im]);
  saveResultsVar(aid,'maxAcceptTicket['+itick+']',maxAcceptBoth[igroup]);
  saveResultsVar(aid,'minBidGroup['+itick+']',minBids_group[igroup]); 

}

/* --- for each participant: aggregate earnings across tickets, and save to  RoundEarn variable */
for (var im=0;im<clientIDs.length;im++) {
    aid=clientIDs[im];                                     // client specific resutls (for non-empty offers)
    tmp=0 ;
    if (z.nentries>0) {
       tmp=z.math('constant totearn=0; tmp= (ids eq anid) ; if tmp.sum() gt 0 then tmp2=tmp * earnactual ~ constant totearn=tmp2.sum(); return  totearn ;  ','anid',aid) ;
    }
    saveResultsVar(aid,'RoundEarn',tmp);

}

for (var m=1;m<=navailTickets;m++)  {
  submitGroups[m]=0; acceptGroups[m]=0;
}

for (var m=0;m<glist.length;m++) {
 agroup=glist[m];
 submitGroups[agroup]=gfreq[m] ;
}
for (var m=0;m<glistA.length;m++) {
 agroup=glistA[m];
 acceptGroups[agroup]=gFreqA[m] ; 
}

saveResultsVar('*','groupList[*]',glist);
saveResultsVar('*','nAcceptsGroup[*]',acceptGroups);
saveResultsVar('*','nSubmitsGroup[*]',submitGroups);


// and some stuff to display
var sorder=indexSort2(z.GROUPS,z.STATUS,'ND');  
var zz=[];
for (var m=0;m<sorder.length;m++) zz=zz.concat(sorder[m]);
tt=makeResultsTable(z,0,zz,use_Q,minBids_group);

setControlVar('FUNCTION_SUCCESS',1);           // set success flag

setControlVar('roundTotalEarn',z.TOTALEARNALLACCEPT);


return ddg+'<p>'+tt;

}
 
// totalcostallreject 
//=======================================================
// extract basic info from submitData for this round, save in the z object
// Several arrays containing TICKET information are extracted. 
// Note: total number of entries is ntickets*nclients
// globals used: ntickets
// z contains ...
//    arrays saved (arrayList)= ids, offers, gorups, prodcosts, maxbids, fees, times, ticknums, scores, noOffers, gotOffers, earnings, status
//    and nclients arrays: roundEarn
//    constants saved (constList)= warnings, round, nentries, nclients 

function readTickets(clientIDs,nround,use_Q) {

var i,aid,im,zoffer,m,v1;
var oname,gname,pname,mname,fname,igroup,tname,namelist,igroup,itick,scname,ubname,tbname,earname;
var nentries,scores ;
var bigv=[]; 
var z ;
warnings='';


// have to do some goofy stuff to break out multiple tickets from each submission
// (each ticket is considered seperately, but not submitted seperately)
for (var m=0;m<ntickets;m++) {
    im=(1*m)+1 ;
    oname='BIDS_USED['+im+']' ; gname='MYGROUP['+im+']'; pname='PRODCOST_USED['+im+']' ; 
    mname='MAXBID_USED['+im+']' ; fname='FEE_USED['+im+']';  qname='QPOINTS_USED['+im+']' ;
    scname='SCORES_USED['+im+']';
    ubname='UNDERBIDPOINTS_USED['+im+']' ;   
    tbname='TARGETBID['+im+']' ;   
    earname='EARNINGS_USED['+im+']' ;   


    tname='='+im ;
//  The names of variables in the submittedData file
//              0    1         2          3        4           5        6       7              8       9       10        11
    namelist='$ID '+oname+' '+gname+' '+pname+' '+mname+' '+fname+' '+qname+' '+scname+' '+ubname+' '+earname+' $TIME '+tname  ;
    v1=extractRoundData('S',nround,namelist,'',clientIDs);
    bigv=appendRows(bigv,v1);
}

var nentries=bigv.length ;
z=createDO(nentries);

z.addConstant([nround,ntickets,clientIDs.length],'round tickets nclients');

// the names of variables in the dataObject 
//              0    1    2       3        4      5    6       7        8       9       10        11
z.add(bigv,'ids offers groups prodcosts maxbids fees qpoints scores underbids earnings times ticknums');

for (var m=0;m<nentries;m++) {                                  // double check offers against maxbid
  if (1*z.OFFERS[m]>1*z.MAXBIDS[m]) {
       warnings+='For id '+z.IDS[m]+' the bid ('+z.OFFERS[m]+') is > maxBid '+z.MAXBIDS[m]+') \n '; 
  }
}


z.add(0,'earnActual');                  // actual earnings  (non zero only if accepted)
z.add(1,'status');                      // assume noSubmit status for each ticket

// save some defaults to resultsvar
for (var i=0;i<nentries;i++){ 
    aid=z.IDS[i];
    itick=z.TICKNUMS[i]
    saveResultsVar(aid,'earn['+itick+']',0);                  // default earnings of 0 for this ticket
    saveResultsVar(aid,'status['+itick+']',1);               // default is this ticket not submitted
}  

z.addConstant(warnings,'warnings',1);
z.addConstant([clientIDs],'clientIDs',1);

return z ;
  
}



//==========================
// create a table of basic results -- z is an object containing results
function makeResultsTable(z,showtime,sortOrder,use_Q,mbq) {
  
var showscore=1;
if (sortOrder.length!=z.IDS.length) {
        alert('Error displaying table: mismatch length in sortOrder: '+z.IDS.length+','+sortOrder.length);
        return 1 ;
}

var tt,m,m0;
var oo='Offer';
if (showscore!=0 || use_Q==1 ) {
 if (use_BidPenalty==1) {
     oo='Offer <span  style="text-decoration:underline" title="Score, after adding bid penalty" style="font-size:90%">score</span> '
     oo=oo+' <span style="font-size:90%;text-decoration:underline" title="Score, before applying bid penalty">(scoreOrig)</span> '
 } else { 
      oo='Offer <span style="font-size:90%">score</span> '
 }
}

if (showtime==0) {
  tt='<table border="1"><tr><th>ID.....</th><th>Earnings / Status</th><th><span title="Ticket type">Type</span></th><th>'+oo+'</th><th>Qpoints / Underbid</th><th>ProdCost</th><th>Fee</th><th>MaxBid</th></tr> \n ';
} else {
  tt='<table border="1"><tr><th>ID.....</th><th>Earnings / Status</th><th><span title="Ticket type">Type</span></th><th>Offer</th><th>Qpoints / Underbid</th><th>ProdCost</th><th>Fee</th><th>MaxBid</th><th>Time</th></tr> \n ';
}

var totearnings=0,totprodcost=0,totoffers=0 ;
for (var m0=0;m0<z.OFFERS.length;m0++){   
  m=m0;
  if (sortOrder.length>0) m=sortOrder[m];       
  if (z.STATUS[m]==4) {
     tt=tt+'<tr bgcolor="lime">';               // ACCEPTED
  }else if (z.STATUS[m]==3) {
     tt=tt+'<tr bgcolor="yellow">';             // REJECT 2nd stage
  }else if (z.STATUS[m]==1) {
     tt=tt+'<tr bgcolor="#dadede">';            // NOT OFFERS
  }else {
     tt=tt+'<tr>';                              // REJECT FIRST stage, or reject if only one stage
  }
  tt=tt+'<td>' +z.IDS[m]+'</td> ';
  tt=tt+'<td>'+fix2(z.EARNACTUAL[m],0)+' / <tt>'+ z.STATUS[m] +'</tt></td> ';  
  if (z.STATUS[m]==4) {
         totoffers=totoffers+(1*z.OFFERS[m]) ;
         totprodcost=totprodcost +(1*z.PRODCOSTS[m]) ;
         totearnings=totearnings+(1*z.EARNACTUAL[m]);
   }

  tt=tt+'<td>'+z.GROUPS[m]+'</td> ';
  if (showscore!==0) {
      tt=tt+'<td>'+z.OFFERS[m]+'<span style="text-align:right;padding-left:5px;font-size:90%">'+z.SCORES2[m]+'</span>' ;
     if (use_BidPenalty==1) tt=tt+'<span style="text-align:right;padding-left:5px;font-style:oblique;font-size:90%">('+z.SCORES[m]+')</span></td> ';
  } else {
      tt=tt+'<td>'+z.OFFERS[m]+'</td> ';
  } 

  tt=tt+'<td>'+z.QPOINTS[m]+' / <tt>'+ z.UNDERBIDS[m] +'</tt></td> ';  

  tt=tt+'<td>'+z.PRODCOSTS[m]+'</td> ';
  tt=tt+'<td>'+z.FEES[m]+'</td> ';
  tt=tt+'<td>'+z.MAXBIDS[m]+'</td> ';  
  if (showtime==1)  {
        goo=new Date(); goo.setTime(z.TIMES[m]);
        tt=tt+'<td>'+z.TIMES[m].toLocaleString()+'</td> ';   
   }
  tt=tt+'</tr> \n ';
}
tt=tt+'</table> \n ';

if (mbq.length>0) {
 tt=tt+ '<br/><span style="border:1px dotted #ababab" title="Minimum bids are used to compute bid Penalty "> '+
        ' Minimum bids, by Type: </span><tt> '+ mbq.join(' | ' )+' </tt>\n ';
}

tt=tt+'<p/><table><tr><th>For accepted tickets:</th> <td><ul> \n';
tt=tt+'<li>Sum of bids: <tt>'+fix2(totoffers)+'</tt></li> \n';
tt=tt+'<li>Earnings  <tt>'+fix2(totearnings)+'</tt> \n' ;
tt=tt+'<li>Costs= <tt>'+fix2(totprodcost)+'</tt> \n' ;
tt=tt+'</ul></td></tr></table>';

return tt ;


}
 


//=------------------------------
// compute final  results (after final round)
// javascript routines for ers09 instrument: compute payout info after final auction round */
// the workingData must contain the results from all rounds (as downloaded from the servers results archive)
//
// the following variables are used by participants (so must be written to the client-accessible parameters files, using postCAP):
// my_roundUsed : chosen round (only used if final_type=0
// my_earnings : earnings (sum, mean, or random round) in ebucks
// my_cashPay : real cash earnings (
// my_totalCash ; my_cashPay (cash from auctions) + my_risk_earnings (cash from risk experiment) + startup_Bonus
//

function computeFinalResults(istep) {  

if (arguments.length<1) istep=1;
var aid,zvars2,zvars,i,dd,aRound,aearn,ftype,erate;
var cashPay,dd,rearn1,rearn2,rearn3,rearn4,r45,totalCashPay ;
var tmpo={};
var clientNames=[];
var clientIDs,roundlist;
var rearn1,rearn2,rearn3,rearn4,acash ;
var utype,pBonusV,pBonus ;
var demos,tmp,zr,goob ;

setControlVar('FUNCTION_SUCCESS',0);


nround=getVarInfo('nowround.g','value');                        // current round
saveResultsVar()  ;              // clear resultsData
saveResultsVar('','ROUND',nround)  ;    // set ROUND in resultsData

if (istep==2) {                                 // 2nd call: after final auction and after risk round. So sum up risk earnings

  zvars='my_totalCash ' ;
  zz=retrieveDO('FinalA');

  if (typeof(zz)=='undefined') {                        // the FinalA dataobject must be created first (using a call here with istep ne 2)
      writeDoStatus('Error: you must first use the End Auctions task to compute and save the final round results ');
      return 0;
   }

  acash=zz.my_cashPay ;         /* real earnings from auction rounds (as saved in FinalA) */
  var clientIDs=zz.ID ;  
  for (var i=0;i<clientIDs.length;i++) {
     aid=strip(clientIDs[i]);
     if (aid=='*') continue ;                   // don't process the default id

     utype=readUserVar(aid,'myType',0);
     dd=extractClientData('S',aid,'$ID',0,nround) ;

     if (dd.length==0)     continue ;                   // since this may be called many times within a round, ignore if no entry for this id

     rearn4=fix2(acash[i],0)  ;  
     r45=fix2(parseFloat(rearn4)+parseFloat(pBonus),-1);
     goob=zz.updateRecord(aid,r45,zvars);
     if (goob!=1) {alert('Error in computeFinalResult='+goob) ; return 0 ; }
   }

   goob=zz.display('*',0,1)
   writeDoStatus('Earnings for all participants<p>'+goob);
 
   goob=zz.display(0,'*',1);
   writeDoStatus('Summary stats (non-client specific) <p>'+goob);
   saveDOResultsVar(zz,'ID',zvars,'xx',1);
   zz.save('FinalA');
   return 1 ;
}

// else, first call (compute final round stuff, and setup FinalA dataObject )

var clientIDs=extractClientList('W','*',1);     // do NOT get the * client
var roundlist=extractRoundList('W','*',1);      // do NOT ge the * round

demos=extractClientData('W','*','ISDEMO ','',roundlist);   // for each round get the generic variable isdemo:  1 means "demo round" 

for (var i=0;i<clientIDs.length;i++) {          // get the name of this client (if it is available)
   aid=clientIDs[i];
   if (typeof(currentParticipants[aid])!='undefined') clientNames[i]=currentParticipants[aid].NAME;
}

ftype=strip(getVarInfo('final_type.g','value'));                // FINAL_TYPE: Type of final payment (0=single round, 1=sum, 2=average)  
erate=strip(getVarInfo('edollar_rate.g','value'));              // ebuck to real $ conversion
pBonus=getVarInfo('p_bonus.g','value');   

var zz=createDO(clientIDs);                     // zz holds client specific info

//zz.add(clientIDs,'ID');
zz.add(clientNames,'NAME');          

zvars='my_roundUsed my_earnings my_cashPay my_totalCash ' ;            // initialize variables to be computed
zz.add(0,zvars);


for (var i=0;i<clientIDs.length;i++) {                  // for each client, process several rounds of data

  aid=strip(clientIDs[i]);
  zr=createDO(roundlist);                       // this holds all rounds or data

  zr.add(demos,'ISDEMO');

  dd=extractClientData('W',aid,'$ROUND ROUNDEARN  ',-9,roundlist);   
  zr.add(dd,'ROUND ROUNDEARN ');
  aRound='N.a.';

  switch (ftype) {
      case '0':
        tmp='notdemos= isdemo ne 1 ; arand=random() * notdemos; arow.1=arand.maxVal_row(); aval.1=roundearn[arow] ; return arow aval  ';
        tvals=zr.math(tmp);
        aearn=tvals[1] ;                  // aii is a 1 row array pointing whose value is the row of dd to use, element [1] in this row is the earnings
        aRound=tvals[0] ;
        break ;
      case '1':   
        tmp='notdemos= isdemo ne 1 ; if notdemos ; aearn.1=roundearn.sum() ; return aearn  ';
        aearn=zr.math(tmp);
        break ;
       case '2':  
        tmp='notdemos= isdemo ne 1 ; if notdemos ; aearn.1=roundearn.mean() ; return aearn  ';
        aearn=zr.math(tmp);
        break ;
  }                                  // switch

  aearn=fix2(aearn);
  cashPay=fix2(erate*aearn);
  totalCashPay=parseFloat(cashPay) + parseFloat(pBonus) ; 
  goob=zz.updateRecord(aid,[aRound,aearn,cashPay,totalCashPay],zvars);

}

tmp=' all_earnings.1=my_earnings.sum() ; all_cashPay.1=my_cashPay.sum() ';
zz=zz.math(tmp) ;

tmp=zz.display('*',0,1)
writeDoStatus('Earnings for all participants:<p/>'+tmp);

goob=zz.display(0,'*',1)
writeDoStatus('Summary stats (non-client specific) <p/>'+goob);


zvars2=zvars+' all_cashPay all_earnings';
saveDOResultsVar(zz,'ID',zvars2,'N.A.',0);

zz.save('FinalA');

setControlVar('FUNCTION_SUCCESS',1);


return 'done' ;
}



// === display final results
function displayFinalResults(i) {  
var zz,tmp,goob;
 
  zz=retrieveDO('FinalA');

tmp=zz.display('*',0,1)
writeDoStatus('Earnings for all participants:<p/>'+tmp);

goob=zz.display(0,'*',1)
writeDoStatus('Summary stats (non-client specific) <p/>'+goob);

return  1 ;
}


//============================================
// summary stats on all rounds to date

function computeStats() {
 controlVars['FUNCTION_SUCCESS']=0 ;              // assume failure

var nrounds,clientIDs,poo,use_Shares,use_Discounts,shareList,discountsList;
var vv,sums1; 
// current round

rlist=extractRoundList('w')
nround=getVarInfo('nowround.g','value');

poo='<h4>>Current round: '+nround + ' </h4> \n ';

vv=extractClientData('W','*','NSUBMITS NACCEPT TOTALEARNALLACCEPT TOTALCOSTALLACCEPT TOTALCOSTALLREJECT MAXACCEPT MINREJECT',0);

z=createDO(rlist);
z.add(vv,'NSUBMITS NACCEPT TOTALEARNALLACCEPT TOTALCOSTALLACCEPT TOTALCOSTALLREJECT MAXACCEPT MINJREJECT');

sums1=[];
for (var i=0;i<rlist.length;i++) {
   var vv=extractRoundData('S',rlist[i],'QPOINTS_USED[1] QPOINTS_USED[2] ',0);
   sums1[i]=sumArray(vv);
}
 
z.add(sums1,'QPOINTS1 QPOINTS2');
ast=z.display('*',0,1,10);
writeDoStatus('<h3>Stats ... (for  each round) </h3>'+ast);


// ===== all round stats
var sums=[];
sums[0]=sumArray(z.NSUBMITS);
sums[1]=sumArray(z.NACCEPT);
sums[2]=sumArray(z.TOTALEARNALLACCEPT);
sums[3]=sumArray(z.TOTALCOSTALLACCEPT);
sums[4]=sumArray(z.TOTALCOSTALLREJECT);
var zsum=createDO(sums,'NSUBMITS NACCEPT TOTALEARNALLACCEPT TOTALCOSTALLACCEPT TOTALCOSTALLREJECT');

ast=zsum.display('*',0,1,10);
writeDoStatus('<h3>Totals: across all rounds </h3>'+ast);


}


function ALERT(x) { alert(x); return 0 };



//---------------------
// check (if unchecked) the demo, results page, and rules pages buttons on advance to next round
// Note that simulating click will change the control variable associated with this button  AND will fill in the checkbox
// Doing both is why a simulated click is used
// 1:demo,  2: results, 3: rules, 4: all
// iforce: 0=always simulate click, 1=force to click state, -1= force to unclick state
function DONEXTROUNDBUTTONS(ith,iforce) {  
if (arguments.length<2) iforce=0;
var ii ;
if (ith==2 || ith==4) {
  ii=document.getElementById('doNextRoundButton2');
  if (iforce==0)  {                 // always simualte click 
    ii.click(); 
  } else if (iforce==1) {           // force to be checked (i.e.;  don't click if currently checked)
     if (!ii.checked )  ii.click(); 
  } else if (iforce==-1) {           // force to be unchecked (i.e.;  don't click if currently unchecked)
     if (ii.checked )  ii.click(); 
  }

}
if (ith==1 || ith==4) {
  ii=document.getElementById('doNextRoundButton1');
  if (iforce==0)  {                 // always simualte click 
    ii.click(); 
  } else if (iforce==1) {           // force to be checked (i.e.;  don't click if currently checked)
     if (!ii.checked )  ii.click(); 
  } else if (iforce==-1) {           // force to be unchecked (i.e.;  don't click if currently unchecked)
     if (ii.checked )  ii.click(); 
  }

}
if (ith==3 || ith==4) {
  ii=document.getElementById('doNextRoundButton3');
  if (iforce==0)  {                 // always simualte click 
    ii.click(); 
  } else if (iforce==1) {           // force to be checked (i.e.;  don't click oi currently checked)
     if (!ii.checked )  ii.click(); 
  } else if (iforce==-1) {           // force to be unchecked (i.e.;  don't click if currently unchecked)
     if (ii.checked )  ii.click(); 
  }
}

return 0; 
 
}

