/*
 * Filter classes for rrdFile
 * They implement the same interface, but changing the content
 * 
 * Part of the javascriptRRD package
 * Copyright (c) 2009 Frank Wuerthwein, fkw@ucsd.edu
 *
 * Original repository: http://javascriptrrd.sourceforge.net/
 * 
 * MIT License [http://www.opensource.org/licenses/mit-license.php]
 *
 */

/*
 * All filter classes must implement the following interface:
 *     getMinStep()
 *     getLastUpdate()
 *     getNrRRAs()
 *     getRRAInfo(rra_idx)
 *     getFilterRRA(rra_idx)
 *     getName()
 *
 * Where getFilterRRA returns an object implementing the following interface:
 *     getIdx()
 *     getNrRows()
 *     getStep()
 *     getCFName()
 *     getEl(row_idx)
 *     getElFast(row_idx)
 *
 */


// ================================================================
// Filter out a subset of DSs (identified either by idx or by name)

// Internal
function RRDRRAFilterDS(rrd_rra,ds_list) {
  this.rrd_rra=rrd_rra;
  this.ds_list=ds_list;
}
RRDRRAFilterDS.prototype.getIdx = function() {return this.rrd_rra.getIdx();}
RRDRRAFilterDS.prototype.getNrRows = function() {return this.rrd_rra.getNrRows();}
RRDRRAFilterDS.prototype.getNrDSs = function() {return this.ds_list.length;}
RRDRRAFilterDS.prototype.getStep = function() {return this.rrd_rra.getStep();}
RRDRRAFilterDS.prototype.getCFName = function() {return this.rrd_rra.getCFName();}
RRDRRAFilterDS.prototype.getEl = function(row_idx,ds_idx) {
  if ((ds_idx>=0) && (ds_idx<this.ds_list.length)) {
    var real_ds_idx=this.ds_list[ds_idx].real_ds_idx;
    return this.rrd_rra.getEl(row_idx,real_ds_idx);
  } else {
    throw RangeError("DS idx ("+ ds_idx +") out of range [0-" + this.ds_list.length +").");
  }	
}
RRDRRAFilterDS.prototype.getElFast = function(row_idx,ds_idx) {
  if ((ds_idx>=0) && (ds_idx<this.ds_list.length)) {
    var real_ds_idx=this.ds_list[ds_idx].real_ds_idx;
    return this.rrd_rra.getElFast(row_idx,real_ds_idx);
  } else {
    throw RangeError("DS idx ("+ ds_idx +") out of range [0-" + this.ds_list.length +").");
  }	
}

// --------------------------------------------------
// Public
// NOTE: This class is deprecated, use RRDFilterOp instead
function RRDFilterDS(rrd_file,ds_id_list) {
  this.rrd_file=rrd_file;
  this.ds_list=[];
  for (var i=0; i<ds_id_list.length; i++) {
    var org_ds=rrd_file.getDS(ds_id_list[i]);
    // must create a new copy, as the index has changed
    var new_ds=new RRDDS(org_ds.rrd_data,org_ds.rrd_data_idx,i);
    // then extend it to include the real RRD index
    new_ds.real_ds_idx=org_ds.my_idx;

    this.ds_list.push(new_ds);
  }
}
RRDFilterDS.prototype.getMinStep = function() {return this.rrd_file.getMinStep();}
RRDFilterDS.prototype.getLastUpdate = function() {return this.rrd_file.getLastUpdate();}

RRDFilterDS.prototype.getNrDSs = function() {return this.ds_list.length;}
RRDFilterDS.prototype.getDSNames = function() {
  var ds_names=[];
  for (var i=0; i<this.ds_list.length; i++) {
    ds_names.push(ds_list[i].getName());
  }
  return ds_names;
}
RRDFilterDS.prototype.getDS = function(id) {
  if (typeof id == "number") {
    return this.getDSbyIdx(id);
  } else {
    return this.getDSbyName(id);
  }
}

// INTERNAL: Do not call directly
RRDFilterDS.prototype.getDSbyIdx = function(idx) {
  if ((idx>=0) && (idx<this.ds_list.length)) {
    return this.ds_list[idx];
  } else {
    throw RangeError("DS idx ("+ idx +") out of range [0-" + this.ds_list.length +").");
  }	
}

// INTERNAL: Do not call directly
RRDFilterDS.prototype.getDSbyName = function(name) {
  for (var idx=0; idx<this.ds_list.length; idx++) {
    var ds=this.ds_list[idx];
    var ds_name=ds.getName()
    if (ds_name==name)
      return ds;
  }
  throw RangeError("DS name "+ name +" unknown.");
}

RRDFilterDS.prototype.getNrRRAs = function() {return this.rrd_file.getNrRRAs();}
RRDFilterDS.prototype.getRRAInfo = function(idx) {return this.rrd_file.getRRAInfo(idx);}
RRDFilterDS.prototype.getRRA = function(idx) {return new RRDRRAFilterDS(this.rrd_file.getRRA(idx),this.ds_list);}

// ================================================================
// Filter out by using a user provided filter object
// The object must implement the following interface
//   getName()               - Symbolic name give to this function
//   getDSName()             - list of DSs used in computing the result (names or indexes)
//   computeResult(val_list) - val_list contains the values of the requested DSs (in the same order) 

// If the element is a string or a number, it will just use that ds

// Example class that implements the interface:
//   function DoNothing(ds_name) { //Leaves the DS alone.
//     this.getName = function() {return ds_name;}
//     this.getDSNames = function() {return [ds_name];}
//     this.computeResult = function(val_list) {return val_list[0];}
//   }
//   function sumDS(ds1_name,ds2_name) { //Sums the two DSs.
//     this.getName = function() {return ds1_name+"+"+ds2_name;}
//     this.getDSNames = function() {return [ds1_name,ds2_name];}
//     this.computeResult = function(val_list) {return val_list[0]+val_list[1];}
//   }
//
// So to add a summed DS of your 1st and second DS: 
// var ds0_name = rrd_data.getDS(0).getName();
// var ds1_name = rrd_data.getDS(1).getName();
// rrd_data = new RRDFilterOp(rrd_data, [new DoNothing(ds0_name), 
//                DoNothing(ds1_name), sumDS(ds0_name, ds1_name]);
//
// You get the same resoult with
// rrd_data = new RRDFilterOp(rrd_data, [ds0_name,1,new sumDS(ds0_name, ds1_name)]);
////////////////////////////////////////////////////////////////////

// this implements the conceptual NoNothing above
function RRDFltOpIdent(ds_name) {
     this.getName = function() {return ds_name;}
     this.getDSNames = function() {return [ds_name];}
     this.computeResult = function(val_list) {return val_list[0];}
}

// similar to the above, but extracts the name from the index
// requires two parametes, since it it need context
function RRDFltOpIdentId(rrd_data,id) {
     this.ds_name=rrd_data.getDS(id).getName();
     this.getName = function() {return this.ds_name;}
     this.getDSNames = function() {return [this.ds_name];}
     this.computeResult = function(val_list) {return val_list[0];}
}

//Private
function RRDDSFilterOp(rrd_file,op_obj,my_idx) {
  this.rrd_file=rrd_file;
  this.op_obj=op_obj;
  this.my_idx=my_idx;
  var ds_names=op_obj.getDSNames();
  var ds_idx_list=[];
  for (var i=0; i<ds_names.length; i++) {
    ds_idx_list.push(rrd_file.getDS(ds_names[i]).getIdx());
  }
  this.ds_idx_list=ds_idx_list;
}
RRDDSFilterOp.prototype.getIdx = function() {return this.my_idx;}
RRDDSFilterOp.prototype.getName = function() {return this.op_obj.getName();}

RRDDSFilterOp.prototype.getType = function() {return "function";}
RRDDSFilterOp.prototype.getMin = function() {return undefined;}
RRDDSFilterOp.prototype.getMax = function() {return undefined;}

// These are new to RRDDSFilterOp
RRDDSFilterOp.prototype.getRealDSList = function() { return this.ds_idx_list;}
RRDDSFilterOp.prototype.computeResult = function(val_list) {return this.op_obj.computeResult(val_list);}

// ------ --------------------------------------------
//Private
function RRDRRAFilterOp(rrd_rra,ds_list) {
  this.rrd_rra=rrd_rra;
  this.ds_list=ds_list;
}
RRDRRAFilterOp.prototype.getIdx = function() {return this.rrd_rra.getIdx();}
RRDRRAFilterOp.prototype.getNrRows = function() {return this.rrd_rra.getNrRows();}
RRDRRAFilterOp.prototype.getNrDSs = function() {return this.ds_list.length;}
RRDRRAFilterOp.prototype.getStep = function() {return this.rrd_rra.getStep();}
RRDRRAFilterOp.prototype.getCFName = function() {return this.rrd_rra.getCFName();}
RRDRRAFilterOp.prototype.getEl = function(row_idx,ds_idx) {
  if ((ds_idx>=0) && (ds_idx<this.ds_list.length)) {
    var ds_idx_list=this.ds_list[ds_idx].getRealDSList();
    var val_list=[];
    for (var i=0; i<ds_idx_list.length; i++) {
      val_list.push(this.rrd_rra.getEl(row_idx,ds_idx_list[i]));
    }
    return this.ds_list[ds_idx].computeResult(val_list);
  } else {
    throw RangeError("DS idx ("+ ds_idx +") out of range [0-" + this.ds_list.length +").");
  }	
}
RRDRRAFilterOp.prototype.getElFast = function(row_idx,ds_idx) {
  if ((ds_idx>=0) && (ds_idx<this.ds_list.length)) {
    var ds_idx_list=this.ds_list[ds_idx].getRealDSList();
    var val_list=[];
    for (var i=0; i<ds_idx_list.length; i++) {
      val_list.push(this.rrd_rra.getEl(row_idx,ds_idx_list[i]));
    }
    return this.ds_list[ds_idx].computeResult(val_list);
  } else {
    throw RangeError("DS idx ("+ ds_idx +") out of range [0-" + this.ds_list.length +").");
  }	
}

// --------------------------------------------------
//Public
function RRDFilterOp(rrd_file,op_obj_list) {
  this.rrd_file=rrd_file;
  this.ds_list=[];
  for (i in op_obj_list) {
    var el=op_obj_list[i];
    var outel=null;
    if (typeof(el)=="string") {outel=new RRDFltOpIdent(el);}
    else if (typeof(el)=="number") {outel=new RRDFltOpIdentId(this.rrd_file,el);}
    else {outel=el;}
    this.ds_list.push(new RRDDSFilterOp(rrd_file,outel,i));
  }
}
RRDFilterOp.prototype.getMinStep = function() {return this.rrd_file.getMinStep();}
RRDFilterOp.prototype.getLastUpdate = function() {return this.rrd_file.getLastUpdate();}
RRDFilterOp.prototype.getNrDSs = function() {return this.ds_list.length;}
RRDFilterOp.prototype.getDSNames = function() {
  var ds_names=[];
  for (var i=0; i<this.ds_list.length; i++) {
    ds_names.push(ds_list[i].getName());
  }
  return ds_names;
}
RRDFilterOp.prototype.getDS = function(id) {
  if (typeof id == "number") {
    return this.getDSbyIdx(id);
  } else {
    return this.getDSbyName(id);
  }
}

// INTERNAL: Do not call directly
RRDFilterOp.prototype.getDSbyIdx = function(idx) {
  if ((idx>=0) && (idx<this.ds_list.length)) {
    return this.ds_list[idx];
  } else {
    throw RangeError("DS idx ("+ idx +") out of range [0-" + this.ds_list.length +").");
  }	
}

// INTERNAL: Do not call directly
RRDFilterOp.prototype.getDSbyName = function(name) {
  for (var idx=0; idx<this.ds_list.length; idx++) {
    var ds=this.ds_list[idx];
    var ds_name=ds.getName()
    if (ds_name==name)
      return ds;
  }
  throw RangeError("DS name "+ name +" unknown.");
}

RRDFilterOp.prototype.getNrRRAs = function() {return this.rrd_file.getNrRRAs();}
RRDFilterOp.prototype.getRRAInfo = function(idx) {return this.rrd_file.getRRAInfo(idx);}
RRDFilterOp.prototype.getRRA = function(idx) {return new RRDRRAFilterOp(this.rrd_file.getRRA(idx),this.ds_list);}

// ================================================================
// NOTE: This function is archaic, and will likely be deprecated in future releases
//
// Shift RRAs in rra_list by the integer shift_int (in seconds).
// Only change is getLastUpdate - this takes care of everything.
// Example: To shift the first three 3 RRAs in the file by one hour, 
//         rrd_data = new RRAFilterShift(rra_data, 3600, [0,1,2]);

function RRAFilterShift(rrd_file, shift_int, rra_list) {
  this.rrd_file = rrd_file;
  this.shift_int = shift_int;
  this.rra_list = rra_list;
  this.shift_in_seconds = this.shift_int*3600; //number of steps needed to move 1 hour
}
RRAFilterShift.prototype.getMinStep = function() {return this.rrd_file.getMinStep();}
RRAFilterShift.prototype.getLastUpdate = function() {return this.rrd_file.getLastUpdate()+this.shift_in_seconds;}
RRAFilterShift.prototype.getNrDSs = function() {return this.rrd_file.getNrDSs();}
RRAFilterShift.prototype.getDSNames = function() {return this.rrd_file.getDSNames();}
RRAFilterShift.prototype.getDS = function(id) {return this.rrd_file.getDS(id);}
RRAFilterShift.prototype.getNrRRAs = function() {return this.rra_list.length;}
RRAFilterShift.prototype.getRRAInfo = function(idx) {return this.rrd_file.getRRAInfo(idx);}
RRAFilterShift.prototype.getRRA = function(idx) {return this.rrd_file.getRRA(idx);}

// ================================================================
// Filter RRAs by using a user provided filter object
// The object must implement the following interface
//   getIdx()               - Index of RRA to use
//   getStep()              - new step size (return null to use step size of RRA specified by getIdx() 

// If a number is passed, it implies to just use the RRA as it is
// If an array is passed, it is assumed to be [rra_id,new_step_in_seconds] 
//    and a RRDRRAFltAvgOpNewStep object will be instantiated internally

/* Example classes that implements the interface:
*
*      //This RRA Filter object leaves the original RRA unchanged.
*
*      function RRADoNothing(rra_idx) {
*         this.getIdx = function() {return rra_idx;}
*         this.getStep = function() {return null;} 
*      }
*      
*      // This Filter creates a new RRA with a different step size 
*      // based on another RRA, whose data the new RRA averages. 
*      // rra_idx should be index of RRA with largest step size 
*      // that doesn't exceed new step size.  
*
*      function RRA_Avg(rra_idx,new_step_in_seconds) {
*         this.getIdx = function() {return rra_idx;}
*         this.getStep = function() {return new_step_in_seconds;}
*      }
*      //For example, if you have two RRAs, one with a 5 second step,
*      //and another with a 60 second step, and you'd like a 30 second step,
*      //rrd_data = new RRDRRAFilterAvg(rrd_data,[new RRADoNothing(0), new RRDDoNothing(1),new RRA_Avg(1,30)];)
*/

// Users can use this one directly for simple use cases
// It is equivalent to RRADoNothing and RRA_Avg above
function RRDRRAFltAvgOpNewStep(rra_idx,new_step_in_seconds) {
  this.getIdx = function() {return rra_idx;}
  this.getStep = function() {return new_step_in_seconds;}
}


//Private Function
function RRAInfoFilterAvg(rrd_file, rra, op_obj, idx) {
  this.rrd_file = rrd_file;
  this.op_obj = op_obj;
  this.base_rra = rrd_file.getRRA(this.op_obj.getIdx());
  this.rra = rra;
  this.idx = idx;
  var scaler = 1;
  if (this.op_obj.getStep()!=null) {scaler = this.op_obj.getStep()/this.base_rra.getStep();}
  this.scaler = scaler;
}
RRAInfoFilterAvg.prototype.getIdx = function() {return this.idx;}
RRAInfoFilterAvg.prototype.getNrRows = function() {return this.rra.getNrRows();} //draw info from RRAFilterAvg
RRAInfoFilterAvg.prototype.getStep = function() {return this.rra.getStep();}
RRAInfoFilterAvg.prototype.getCFName = function() {return this.rra.getCFName();}
RRAInfoFilterAvg.prototype.getPdpPerRow = function() {return this.rrd_file.getRRAInfo(this.op_obj.getIdx()).getPdpPerRow()*this.scaler;}

//---------------------------------------------------------------------------
//Private Function
function RRAFilterAvg(rrd_file, op_obj) {
  this.rrd_file = rrd_file;
  this.op_obj = op_obj;
  this.base_rra = rrd_file.getRRA(op_obj.getIdx());
  var scaler=1; 
  if (op_obj.getStep()!=null) {scaler = op_obj.getStep()/this.base_rra.getStep();}
  this.scaler = Math.floor(scaler);
  //document.write(this.scaler+",");
}
RRAFilterAvg.prototype.getIdx = function() {return this.op_obj.getIdx();}
RRAFilterAvg.prototype.getCFName = function() {return this.base_rra.getCFName();}
RRAFilterAvg.prototype.getNrRows = function() {return Math.floor(this.base_rra.getNrRows()/this.scaler);}
RRAFilterAvg.prototype.getNrDSs = function() {return this.base_rra.getNrDSs();}
RRAFilterAvg.prototype.getStep = function() {
   if(this.op_obj.getStep()!=null) {
      return this.op_obj.getStep(); 
   } else { return this.base_rra.getStep();}
}
RRAFilterAvg.prototype.getEl = function(row,ds) {
   var sum=0;
   for(var i=0;i<this.scaler;i++) {
      sum += this.base_rra.getEl((this.scaler*row)+i,ds);
   }
   return sum/this.scaler;
}
RRAFilterAvg.prototype.getElFast = function(row,ds) {
   var sum=0;
   for(var i=0;i<this.scaler;i++) {
      sum += this.base_rra.getElFast((this.scaler*row)+i,ds);
   }
   return sum/this.scaler;
}

//----------------------------------------------------------------------------
//Public function - use this one for RRA averaging
function RRDRRAFilterAvg(rrd_file, op_obj_list) {
  this.rrd_file = rrd_file;
  this.op_obj_list = new Array();
  this.rra_list=[];
  for (var i in op_obj_list) {
    var el=op_obj_list[i];
    var outel=null;
    if (Object.prototype.toString.call(el)=="[object Number]") {outel=new RRDRRAFltAvgOpNewStep(el,null);}
    else if (Object.prototype.toString.call(el)=="[object Array]") {outel=new RRDRRAFltAvgOpNewStep(el[0],el[1]);}
    else {outel=el;}
    this.op_obj_list.push(outel);
    this.rra_list.push(new RRAFilterAvg(rrd_file,outel));
  }
}
RRDRRAFilterAvg.prototype.getMinStep = function() {return this.rrd_file.getMinStep();} //other RRA steps change, not min
RRDRRAFilterAvg.prototype.getLastUpdate = function() {return this.rrd_file.getLastUpdate();}
RRDRRAFilterAvg.prototype.getNrDSs = function() {return this.rrd_file.getNrDSs();} //DSs unchanged
RRDRRAFilterAvg.prototype.getDSNames = function() {return this.rrd_file.getDSNames();}
RRDRRAFilterAvg.prototype.getDS = function(id) {return this.rrd_file.getDS(id);}
RRDRRAFilterAvg.prototype.getNrRRAs = function() {return this.rra_list.length;} 
RRDRRAFilterAvg.prototype.getRRAInfo = function(idx) {
  if ((idx>=0) && (idx<this.rra_list.length)) {
    return new RRAInfoFilterAvg(this.rrd_file, this.rra_list[idx],this.op_obj_list[idx],idx); 
  } else {return this.rrd_file.getRRAInfo(0);}
}
RRDRRAFilterAvg.prototype.getRRA = function(idx) {
  if ((idx>=0) && (idx<this.rra_list.length)) {
    return this.rra_list[idx];
  }
}