Function.prototype.bind = function(object) {
  var method = this;
  return function() {
    return method.apply(object, arguments);
  };
};

var viewModel = {
  timeWindow:  ko.observable("1.hour"),
  clusters: ko.observableArray([]),
  servers: ko.observableArray([]),
  metrics: ko.observableArray([]),
  selectedCluster: ko.observable(""),
  selectedServer: ko.observable(""),
  selectedMetric: ko.observable(""),
  currentGraph: ko.observableArray([]),
  savedGraphs: ko.observableArray([]),
  graphTitle: ko.observable(""),
  savingSavedGraphs: ko.observable(false),
  selectSavedGraph: function(i) {
    var graph = this.savedGraphs()[i];
    this.clearGraph();
    this.graphTitle(graph.name);
    _.each(graph.metrics, function(metric) {
      this.add(metric);
    }.bind(this));
  },
  saveCurrentGraph: function() {
    this.savedGraphs.push({name: this.graphTitle(), metrics: this.currentGraph()});
    this.drawGraphIfReady();
  },
  removeSavedGraph: function(i) {
    if (confirm("Are you sure?")) {
      var graph = this.savedGraphs()[i];
      this.savedGraphs.remove(graph);
    }
  },
  clearGraph: function() {
    for(var i = 0; i < this.currentGraph().length; i++)
      this.remove(i);
  },
  scale: function(i) {
    var metric   = this.currentGraph()[i];
    metric.scale = !metric.scale;
    this.currentGraph.valueHasMutated();

    this.drawGraphIfReady();
  },
  remove: function(i) {
    var metric = this.currentGraph()[i];
    this.currentGraph.remove(metric);
    this.drawGraphIfReady(); 
  },
  fetchClusters: function() {
    $.getJSON("/clusters.json", function(data) {
      $.each(data, function(i, e) { this.clusters.push(e); }.bind(this));
    }.bind(this));
  },
  clusterSelected: function() {
    var cluster = this.selectedCluster();

    $.getJSON("/" + cluster + "/servers.json", function(data) {
      $.each(data, function(i, e) { this.servers.push(e); }.bind(this));
    }.bind(this));
  },
  serverSelected: function() {
    var cluster = this.selectedCluster();
    var server  = this.selectedServer();

    $.getJSON("/" + cluster + "/" + server + "/metrics.json", function(data) {
      $.each(data, function(i, e) { this.metrics.push(e); }.bind(this));
    }.bind(this));
  },
  addToGraph: function() {
    var name = [this.selectedCluster(),
                this.selectedServer(),
                this.selectedMetric()].join("/");
    this.add({name: name});
  },
  getMetricByName: function(name) {
    return _.detect(this.currentGraph(), function(metric) {
      return metric.name == metric;
    });
  },
  add: function(metric) {
    if (!this.getMetricByName(metric.name)) {
      this.currentGraph.push(metric);
      this.fetchData(metric);
    }
  },
  fetchData: function(metric) {
    if (metric.data) {
      this.drawGraph();
    } else {
      $.getJSON("/" + metric.name + ".json?time_window=" + this.timeWindow(), function(data) {
        metric.data = data;
        this.drawGraphIfReady();
      }.bind(this));
    }
  },
  timeChanged: function() {
    this.resetChartIfExists();

    _.each(this.currentGraph(), function(metric) {
      delete metric.data;
      this.fetchData(metric);
    }.bind(this));
  },
  readyToDrawGraph: function() {
    return _.all(this.currentGraph(), function(metric) {
      return metric.data;
    }.bind(this));
  },
  drawGraphIfReady: function() {
    if (this.readyToDrawGraph()) {
      this.drawGraph();
    }
  },
  resetChartIfExists: function() {
    if (this.chart) {
      this.chart.destroy();
      this.chart = null;
    }
  },
  drawGraph: function() {
    this.resetChartIfExists();

    if (this.currentGraph().length == 0) return;

    var series = [];
    _.each(this.currentGraph(), function(metric) {
      var data     = metric.data;
      var interval = data[1][0] - data[0][0];

      if (metric.scale) {
        data = $.map(data, function(point) {
          return [[point[0], point[1] * 60]];
        });
      }

      series.push({
        type: 'area',
        name: metric.name,
        pointInterval: interval * 1000,
        pointStart: data[0][0] * 1000,
        data: $.map(data, function(e) { return e[1]; })
      });
    });
    
    this.chart = new Highcharts.Chart({
      chart: {
         renderTo: 'chart',
         zoomType: 'x',
         spacingRight: 20
      },
       title: {
         text: this.graphTitle()
      },
       subtitle: {
         text: document.ontouchstart === undefined ?
            'Click and drag in the plot area to zoom in' :
            'Drag your finger over the plot to zoom in'
      },
      xAxis: {
         type: 'datetime',
         maxZoom: 360,
         title: {
            text: null
         }
      },
      yAxis: {
         title: {
            text: ''
         },
         startOnTick: false,
         showFirstLabel: false
      },
      tooltip: {
         shared: true               
      },
      legend: {
         enabled: false
      },
      series: series
    });
  },
  savedGraphsChanged: function() {
    this.savingSavedGraphs(true);
    var graphs = _.map(this.savedGraphs(), function(graph) {
      return {name: graph.name, metrics: _.map(graph.metrics, function(metric) {
        return {
          name: metric.name,
          scale: metric.scale
        }
      })};
    });

    $.ajax({
      type: "post",
      dataType: "json",
      url: "/graphs.json",
      data: {"json": JSON.stringify(graphs)},
      success: function() {
        this.savingSavedGraphs(false);
      }.bind(this)
    });
  }
};

$(function() {
  ko.applyBindings(viewModel);

  viewModel.selectedCluster.subscribe(viewModel.clusterSelected, viewModel);
  viewModel.selectedServer.subscribe(viewModel.serverSelected, viewModel);
  viewModel.timeWindow.subscribe(viewModel.timeChanged, viewModel);
  viewModel.savedGraphs.subscribe(viewModel.savedGraphsChanged, viewModel);

  $.getJSON("/graphs.json", function(graphs) {
    _.each(graphs, function(graph) {
      viewModel.savedGraphs.push(graph);
    });
  });

  viewModel.fetchClusters();
});