/*
 * Support library aimed at providing commonly used functions and classes
 * that may be used while plotting RRD files with Flot
 *
 * 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]
 *
 */

/*
 *
 * Flot is a javascript plotting library developed and maintained by
 * Ole Laursen [http://www.flotcharts.org/]
 *
 */

// Return a Flot-like data structure
// Since Flot does not properly handle empty elements, min and max are returned, too
function rrdDS2FlotSeries(rrd_file,ds_id,rra_idx,want_rounding) {
  var ds=rrd_file.getDS(ds_id);
  var ds_name=ds.getName();
  var ds_idx=ds.getIdx();
  var rra=rrd_file.getRRA(rra_idx);
  var rra_rows=rra.getNrRows();
  var last_update=rrd_file.getLastUpdate();
  var step=rra.getStep();

  if (want_rounding!=false) {
    // round last_update to step
    // so that all elements are sync
    last_update-=(last_update%step); 
  }

  var first_el=last_update-(rra_rows-1)*step;
  var timestamp=first_el;
  var flot_series=[];
  for (var i=0;i<rra_rows;i++) {
    var el=rra.getEl(i,ds_idx);
    if (el!=undefined) {
      flot_series.push([timestamp*1000.0,el]);
    }
    timestamp+=step;
  } // end for

  return {label: ds_name, data: flot_series, min: first_el*1000.0, max:last_update*1000.0};
}

// return an object with an array containing Flot elements, one per DS
// min and max are also returned
function rrdRRA2FlotObj(rrd_file,rra_idx,ds_list,want_ds_labels,want_rounding) {
  var rra=rrd_file.getRRA(rra_idx);
  var rra_rows=rra.getNrRows();
  var last_update=rrd_file.getLastUpdate();
  var step=rra.getStep();
  if (want_rounding!=false) {
    // round last_update to step
    // so that all elements are sync
    last_update-=(last_update%step); 
  }

  var first_el=last_update-(rra_rows-1)*step;

  var out_el={data:[], min:first_el*1000.0, max:last_update*1000.0};

  var ds_list_len = ds_list.length;
  for (var ds_list_idx=0; ds_list_idx<ds_list_len; ++ds_list_idx) { 
    var ds_id=ds_list[ds_list_idx];
    var ds=rrd_file.getDS(ds_id);
    var ds_name=ds.getName();
    var ds_idx=ds.getIdx();

    var timestamp=first_el;
    var flot_series=[];
    for (var i=0;i<rra_rows;i++) {
      var el=rra.getEl(i,ds_idx);
      if (el!=undefined) {
	flot_series.push([timestamp*1000.0,el]);
      }
      timestamp+=step;
    } // end for
    
    var flot_el={data:flot_series};
    if (want_ds_labels!=false) {
      var ds_name=ds.getName();
      flot_el.label= ds_name;
    }
    out_el.data.push(flot_el);
  } //end for ds_list_idx
  return out_el;
}

// return an object with an array containing Flot elements
//  have a positive and a negative stack of DSes, plus DSes with no stacking
// min and max are also returned
// If one_undefined_enough==true, a whole stack is invalidated if a single element
//  of the stack is invalid
function rrdRRAStackFlotObj(rrd_file,rra_idx,
			    ds_positive_stack_list,ds_negative_stack_list,ds_single_list,
                            timestamp_shift, want_ds_labels,want_rounding,one_undefined_enough) {
  var rra=rrd_file.getRRA(rra_idx);
  var rra_rows=rra.getNrRows();
  var last_update=rrd_file.getLastUpdate();
  var step=rra.getStep();
  if (want_rounding!=false) {
    // round last_update to step
    // so that all elements are sync
    last_update-=(last_update%step); 
  }
  if (one_undefined_enough!=true) { // make sure it is a boolean
    one_undefined_enough=false;
  }

  var first_el=last_update-(rra_rows-1)*step;

  var out_el={data:[], min:(first_el+timestamp_shift)*1000.0, max:(last_update+timestamp_shift)*1000.0};

  // first the stacks stack
  var stack_els=[ds_positive_stack_list,ds_negative_stack_list];
  var stack_els_len = stack_els.length;
  for (var stack_list_id=0; stack_list_id<stack_els_len; ++stack_list_id) {
    var stack_list=stack_els[stack_list_id];
    var tmp_flot_els=[];
    var tmp_ds_ids=[];
    var tmp_nr_ids=stack_list.length;
    var stack_list_len = stack_list.length;
    for (var ds_list_idx=0; ds_list_idx<stack_list_len; ++ds_list_idx) {
      var ds_id=stack_list[ds_list_idx];
      var ds=rrd_file.getDS(ds_id);
      var ds_name=ds.getName();
      var ds_idx=ds.getIdx();
      tmp_ds_ids.push(ds_idx); // getting this is expensive, call only once
      
      // initialize
      var flot_el={data:[]}
      if (want_ds_labels!=false) {
	var ds_name=ds.getName();
	flot_el.label= ds_name;
      }
      tmp_flot_els.push(flot_el);
    }

    var timestamp=first_el;
    for (var row=0;row<rra_rows;row++) {
      var ds_vals=[];
      var all_undef=true;
      var all_def=true;
      for (var id=0; id<tmp_nr_ids; id++) {
	var ds_idx=tmp_ds_ids[id];
	var el=rra.getEl(row,ds_idx);
	if (el!=undefined) {
	  all_undef=false;
	  ds_vals.push(el);
	} else {
	  all_def=false;
	  ds_vals.push(0);
	}
      } // end for id
      if (!all_undef) { // if all undefined, skip
	if (all_def || (!one_undefined_enough)) {
	  // this is a valid column, do the math
	  for (var id=1; id<tmp_nr_ids; id++) {
	    ds_vals[id]+=ds_vals[id-1]; // both positive and negative stack use a +, negative stack assumes negative values
	  }
	  // fill the flot data
	  for (var id=0; id<tmp_nr_ids; id++) {
	    tmp_flot_els[id].data.push([(timestamp+timestamp_shift)*1000.0,ds_vals[id]]);
	  }
	}
      } // end if

      timestamp+=step;
    } // end for row
    
    // put flot data in output object
    // reverse order so higher numbers are behind
    for (var id=0; id<tmp_nr_ids; id++) {
      out_el.data.push(tmp_flot_els[tmp_nr_ids-id-1]);
    }
  } //end for stack_list_id

  var ds_single_list_len = ds_single_list.length;
  for (var ds_list_idx=0; ds_list_idx<ds_single_list_len; ++ds_list_idx) { 
    var ds_id=ds_single_list[ds_list_idx];
    var ds=rrd_file.getDS(ds_id);
    var ds_name=ds.getName();
    var ds_idx=ds.getIdx();

    var timestamp=first_el;
    var flot_series=[];
    for (var i=0;i<rra_rows;i++) {
      var el=rra.getEl(i,ds_idx);
      if (el!=undefined) {
	flot_series.push([(timestamp+timestamp_shift)*1000.0,el]);
      }
      timestamp+=step;
    } // end for
    
    var flot_el={data:flot_series};
    if (want_ds_labels!=false) {
      var ds_name=ds.getName();
      flot_el.label= ds_name;
    }
    out_el.data.push(flot_el);
  } //end for ds_list_idx

  return out_el;
}

// return an object with an array containing Flot elements, one per RRD
// min and max are also returned
function rrdRRAMultiStackFlotObj(rrd_files, // a list of [rrd_id,rrd_file] pairs, all rrds must have the same step
				 rra_idx,ds_id,
				 want_rrd_labels,want_rounding,
				 one_undefined_enough) { // If true, a whole stack is invalidated if a single element of the stack is invalid

  var reference_rra=rrd_files[0][1].getRRA(rra_idx); // get the first one, all should be the same
  var rows=reference_rra.getNrRows();
  var step=reference_rra.getStep();
  var ds_idx=rrd_files[0][1].getDS(ds_id).getIdx(); // this can be expensive, do once (all the same)

  // rrds can be slightly shifted, calculate range
  var max_ts=null;
  var min_ts=null;

  // initialize list of rrd data elements
  var tmp_flot_els=[];
  var tmp_rras=[];
  var tmp_last_updates=[];
  var tmp_nr_ids=rrd_files.length;
  for (var id=0; id<tmp_nr_ids; id++) {
    var rrd_file=rrd_files[id][1];
    var rrd_rra=rrd_file.getRRA(rra_idx);

    var rrd_last_update=rrd_file.getLastUpdate();
    if (want_rounding!=false) {
      // round last_update to step
      // so that all elements are sync
      rrd_last_update-=(rrd_last_update%step); 
    }
    tmp_last_updates.push(rrd_last_update);

    var rrd_min_ts=rrd_last_update-(rows-1)*step;
    if ((max_ts==null) || (rrd_last_update>max_ts)) {
      max_ts=rrd_last_update;
    }
    if ((min_ts==null) || (rrd_min_ts<min_ts)) {
      min_ts=rrd_min_ts;
    }
    
    tmp_rras.push(rrd_rra);
      
    // initialize
    var flot_el={data:[]}
    if (want_rrd_labels!=false) {
	var rrd_name=rrd_files[id][0];
	flot_el.label= rrd_name;
    }
    tmp_flot_els.push(flot_el);
  }

  var out_el={data:[], min:min_ts*1000.0, max:max_ts*1000.0};

  for (var ts=min_ts;ts<=max_ts;ts+=step) {
      var rrd_vals=[];
      var all_undef=true;
      var all_def=true;
      for (var id=0; id<tmp_nr_ids; id++) {
        var rrd_rra=tmp_rras[id];
        var rrd_last_update=tmp_last_updates[id];
	var row_delta=Math.round((rrd_last_update-ts)/step);
	var el=undefined; // if out of range
        if ((row_delta>=0) && (row_delta<rows)) {
          el=rrd_rra.getEl(rows-row_delta-1,ds_idx);
        }
	if (el!=undefined) {
	  all_undef=false;
	  rrd_vals.push(el);
	} else {
	  all_def=false;
	  rrd_vals.push(0);
	}
      } // end for id
      if (!all_undef) { // if all undefined, skip
	if (all_def || (!one_undefined_enough)) {
	  // this is a valid column, do the math
	  for (var id=1; id<tmp_nr_ids; id++) {
	    rrd_vals[id]+=rrd_vals[id-1]; 
	  }
	  // fill the flot data
	  for (var id=0; id<tmp_nr_ids; id++) {
	    tmp_flot_els[id].data.push([ts*1000.0,rrd_vals[id]]);
	  }
	}
      } // end if
  } // end for ts
    
  // put flot data in output object
  // reverse order so higher numbers are behind
  for (var id=0; id<tmp_nr_ids; id++) {
    out_el.data.push(tmp_flot_els[tmp_nr_ids-id-1]);
  }
  
  return out_el;
}

// ======================================
// Helper class for handling selections
// =======================================================
function rrdFlotSelection() {
  this.selection_min=null;
  this.selection_max=null;
};

// reset to a state where ther is no selection
rrdFlotSelection.prototype.reset = function() {
  this.selection_min=null;
  this.selection_max=null;
};

// given the selection ranges, set internal variable accordingly
rrdFlotSelection.prototype.setFromFlotRanges = function(ranges) {
  this.selection_min=ranges.xaxis.from;
  this.selection_max=ranges.xaxis.to;
};

// Return a Flot ranges structure that can be promptly used in setSelection
rrdFlotSelection.prototype.getFlotRanges = function() {
  return { xaxis: {from: this.selection_min, to: this.selection_max}};
};

// return true is a selection is in use
rrdFlotSelection.prototype.isSet = function() {
  return this.selection_min!=null;
};

// Given an array of flot lines, limit to the selection
rrdFlotSelection.prototype.trim_flot_data = function(flot_data) {
  var out_data=[];
  for (var i=0; i<flot_data.length; i++) {
    var data_el=flot_data[i];
    out_data.push({label : data_el.label, data:this.trim_data(data_el.data), color:data_el.color, lines:data_el.lines, yaxis:data_el.yaxis});
  }
  return out_data;
};

// Limit to selection the flot series data element
rrdFlotSelection.prototype.trim_data = function(data_list) {
  if (this.selection_min==null) return data_list; // no selection => no filtering

  var out_data=[];
  for (var i=0; i<data_list.length; i++) {
    
    if (data_list[i]==null) continue; // protect
    //data_list[i][0]+=3550000*5;
    var nr=data_list[i][0]; //date in unix time
    if ((nr>=this.selection_min) && (nr<=this.selection_max)) {
      out_data.push(data_list[i]);
    }
  }
  return out_data;
};


// Given an array of flot lines, limit to the selection
rrdFlotSelection.prototype.trim_flot_timezone_data = function(flot_data,shift) {
  var out_data=[];
  for (var i=0; i<flot_data.length; i++) {
    var data_el=flot_data[i];
    out_data.push({label : data_el.label, data:this.trim_timezone_data(data_el.data,shift), color:data_el.color, lines:data_el.lines, yaxis:data_el.yaxis});
  }
  return out_data;
};

// Limit to selection the flot series data element
rrdFlotSelection.prototype.trim_timezone_data = function(data_list,shift) {
  if (this.selection_min==null) return data_list; // no selection => no filtering

  var out_data=[];
  for (var i=0; i<data_list.length; i++) {
    if (data_list[i]==null) continue; // protect
    var nr=data_list[i][0]+shift;
    if ((nr>=this.selection_min) && (nr<=this.selection_max)) {
      out_data.push(data_list[i]);
    }
  }
  return out_data;
};


// ======================================
// Miscelaneous helper functions
// ======================================

function rfs_format_time(s) {
  if (s<120) {
    return s+"s";
  } else {
    var s60=s%60;
    var m=(s-s60)/60;
    if ((m<10) && (s60>9)) {
      return m+":"+s60+"min";
    } if (m<120) {
      return m+"min";
    } else {
      var m60=m%60;
      var h=(m-m60)/60;
      if ((h<12) && (m60>9)) {
	return h+":"+m60+"h";
      } if (h<48) {
	return h+"h";
      } else {
	var h24=h%24;
	var d=(h-h24)/24;
	if ((d<7) && (h24>0)) {
	  return d+" days "+h24+"h";
	} if (d<60) {
	  return d+" days";
	} else {
	  var d30=d%30;
	  var mt=(d-d30)/30;
	  return mt+" months";
	}
      }
    }
    
  }
}