Ajax.Request.prototype.dispatchException = function(e) { throw e; };

// conversion helpers
var Interval = {
  DAY_MILLIS: 86400000,
  DAY_NAMES: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
  
  toDays: function(date) {
    return (date.getTime() / Interval.DAY_MILLIS).floor();
  },
  toDate: function(days) {
    return new Date(days * Interval.DAY_MILLIS);
  },
  toShortLabel: function(days) {
    var date = Interval.toDate(days);
    return (date.getUTCMonth() + 1) + '/' + date.getUTCDate();
  }
};

Object.extend(Interval, {
  // specific interval configurations
  daily: {
    mouse_sensitivity: 0.25,
    format_tick: function(days) {
      var date = Interval.toDate(days);
      date_str = (date.getUTCMonth() + 1) + '/' + date.getUTCDate();
      date_str += '<br />' + Interval.DAY_NAMES[date.getUTCDay()];
      return date_str;
    },
    next: function(days, steps) {
      return days + (steps || 1);
    },
    nearest: function(days) {
      return days;
    }
  },
  
  weekly: {
    mouse_sensitivity: 2,
    format_tick: function(days) {
      var date = Interval.toDate(days);
      var date_str = (date.getUTCMonth() + 1) + '/' + date.getUTCDate();
      if (date.getUTCDate() < 8)
        date_str += '<br />' + date.getUTCFullYear();
      return date_str;
    },
    next: function(days, steps) {
      return days + (7 * (steps || 1));
    },
    nearest: function(days) {
      return days + 6 - ((days + 2) % 7); //-> monday-based
      //return days + 6 - ((days + 3) % 7); //-> sunday-based
    }
  }
});

var TimeGraph = Class.create(function() {
  // graph-slide effect
  var ScrollEffect = Class.create(Effect.Base, {
    initialize: function(chart, old_days, new_days) {
      this.chart = chart;
      this.old_days = old_days;
      this.new_days = new_days;
      this.delta = this.new_days - this.old_days;
      this.start({
        transition: Effect.Transitions.spring
      });
    },
    setup: function() {
      this.chart.show(this.old_days);
    },
    update: function(position) {
      this.chart.show(this.old_days + (this.delta * position));
    },
    finish: function() {
      this.chart.set(this.new_days);
    }
  });
  
  function convert_data(fields, data) {
    return fields.collect(function(field_name) {
      return {
        data: data.collect(function(point) {
          return [point.day_start, point[field_name]];
        }),
        label: field_name
      };
    });
  }
  
  function template_graph_config() {
    return {
      colors: ['#999999', '#d45500'],
      xaxis: null, // to be filled in later
      yaxis: {
        min: 0.0,
        tickDecimals: 1
      },
      lines: {
        show: true,
        lineWidth: 4
      },
      points: {
        show: true,
        lineWidth: 4
      },
      grid: {
        color: '#545454',
        tickColor: '#545454',
        labelMargin: 3
      },
      mouse: {
        track: true,
        sensibility_x: null, // ditto
        sensibility_y: 1.0,
        lineColor: '#000',
        lineWidth: 2
      },
      legend: {
        backgroundColor: '#bb9',
        labelBoxBorderColor: '#bb9',
        position: 'ne'
      }
    };
  }
  
  return {
    initialize: function(element, data, options) {
      this.element = $(element);
      this.options = Object.extend({
        fields: [],
        interval: Interval.daily,
        units: 8,
        on_hover: Prototype.emptyFunction,
        on_click: Prototype.emptyFunction
      }, options);
      this.fields = options.fields;
      this.interval = options.interval;
      
      this.data = convert_data(this.fields, data);
      this.graph_config = template_graph_config();
      this.graph_config.mouse.sensibility_x = this.interval.mouse_sensitivity;
      
      this.element.observe('flotr:hit', function(event) {
        this.options.on_hover(event.memo[0]);
      }.bindAsEventListener(this));
      this.element.observe('flotr:click', function(event) {
        this.options.on_click(event.memo[0]);
      }.bindAsEventListener(this));
    },
    
    get_xaxis_config: function(start_days, end_days) {
      var ticks = [];
      for (var days = this.interval.nearest(start_days);
            days <= end_days;
            days = this.interval.next(days)) {
        ticks.push([days, this.interval.format_tick(days)]);
      }
      return {
        min: start_days,
        max: end_days,
        ticks: ticks
      };
    },
    
    show: function(start_days) {
      var end_days = this.interval.next(start_days, this.options.units);
      this.graph_config.xaxis = this.get_xaxis_config(start_days, end_days);
      Flotr.draw(this.element, this.data, this.graph_config);
    },
    
    set: function(new_days) {
      this._start_days = new_days;
      this.show(new_days);
    },
    
    scroll: function(new_days) {
      new ScrollEffect(this, this._start_days, new_days);
    },
    
    advance: function(step) {
      this.scroll(this.interval.next(this._start_days, step));
    }
  };
}());

var PaceGraph = Class.create(function() {
  function convert_data(data) {
    if (data.size == 0) return [];
    
    var total_distance = 0.0;
    var total_time = 0.0;
    var paces = {
      data: [],
      label: 'pace'
    };
    data.each(function(point) {
      total_distance += point.distance;
      total_time += point.elapsed_time;
      paces.data.push([total_distance, point.elapsed_time / point.distance]);
    });
    
    var avg_pace = total_time / total_distance;
    return [
      {
        data: [[1, avg_pace], [total_distance, avg_pace]],
        label: 'avg pace',
        lines: {
          fill: true,
          lineWidth: 1
        }
      },
      paces
    ];
  }
  
  function format_time(val) {
    val = parseFloat(val);
    var mins = val.floor();
    var secs = ((val % 1) * 60).floor();
    secs = (secs < 10 ? "0" + secs : secs);
    return mins + ":" + secs;
  }
  
  function template_graph_config() {
    return {
      colors: ['#ddddaa', '#d45500'],
      yaxis: {
        tickFormatter: format_time
      },
      lines: {
        show: true,
        lineWidth: 4
      },
      grid: {
        color: '#545454',
        tickColor: '#545454',
        labelMargin: 3
      },
      legend: {
        backgroundColor: '#bb9',
        labelBoxBorderColor: '#bb9',
        color: '#000',
        position: 'se'
      }
    };
  }
  
  return {
    initialize: function(element, data, options) {
      this.element = $(element);
      this.options = Object.extend({}, options);
      this.data = convert_data(data);
      this.graph_config = template_graph_config();

      var avg_line = this.data[0].data;
      var total_distance = avg_line[1][0];
      this.graph_config.xaxis = {
        min: 1,
        max: total_distance,
        ticks: $R(1, total_distance).collect(function(val) {return val;}),
      };
    },
    
    show: function() {
      Flotr.draw(this.element, this.data, this.graph_config);
    }
  };
}());
