// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

base.require('base.sorted_array_utils');
base.require('tracing.tracks.container_track');
base.require('ui');

base.exportTo('tracing.tracks', function() {

  /**
   * A track that displays a SliceGroup.
   * @constructor
   * @extends {ContainerTrack}
   */

  var SliceGroupTrack = ui.define(
      'slice-group-track', tracing.tracks.ContainerTrack);

  SliceGroupTrack.prototype = {

    __proto__: tracing.tracks.ContainerTrack.prototype,

    decorate: function(viewport) {
      tracing.tracks.ContainerTrack.prototype.decorate.call(this, viewport);
      this.classList.add('slice-group-track');
      this.tooltip_ = '';
      this.heading_ = '';
    },

    get group() {
      return this.group_;
    },

    set group(g) {
      this.group_ = g;
      this.updateContents_();
    },

    get heading() {
      return this.heading_;
    },

    set heading(h) {
      this.heading_ = h;
      this.updateContents_();
    },

    get tooltip() {
      return this.tooltip_;
    },

    set tooltip(t) {
      this.tooltip_ = t;
      this.updateContents_();
    },

    addSliceTrack_: function(slices) {
      var track = new tracing.tracks.SliceTrack(this.viewport);
      track.slices = slices;
      this.appendChild(track);
      return track;
    },

    get subRows() {
      return base.asArray(this.children).map(function(sliceTrack) {
        return sliceTrack.slices;
      });
    },

    get hasVisibleContent() {
      return this.children.length > 0;
    },

    updateContents_: function() {
      if (!this.group_) {
        this.updateHeadingAndTooltip_();
        return;
      }

      var slices = this.group_.slices;
      if (this.areArrayContentsSame_(this.filteredSlices_, slices)) {
        this.updateHeadingAndTooltip_();
        return;
      }

      this.filteredSlices_ = slices;

      this.detach();
      if (!slices.length)
        return;
      var subRows = this.buildSubRows_(slices);
      for (var srI = 0; srI < subRows.length; srI++) {
        var subRow = subRows[srI];
        if (!subRow.length)
          continue;
        this.addSliceTrack_(subRow);
      }
      this.updateHeadingAndTooltip_();
    },

    updateHeadingAndTooltip_: function() {
      if (!this.firstChild)
        return;
      this.firstChild.heading = this.heading_;
      this.firstChild.tooltip = this.tooltip_;
    },

    /**
     * Breaks up the list of slices into N rows, each of which is a list of
     * slices that are non overlapping.
     */
    buildSubRows_: function(slices) {
      // This function works by walking through slices by start time.
      //
      // The basic idea here is to insert each slice as deep into the subrow
      // list as it can go such that every subSlice is fully contained by its
      // parent slice.
      //
      // Visually, if we start with this:
      //  0:  [    a       ]
      //  1:    [  b  ]
      //  2:    [c][d]
      //
      // To place this slice:
      //               [e]
      // We first check row 2's last item, [d]. [e] wont fit into [d] (they dont
      // even intersect). So we go to row 1. That gives us [b], and [d] wont fit
      // into that either. So, we go to row 0 and its last slice, [a]. That can
      // completely contain [e], so that means we should add [e] as a subchild
      // of [a]. That puts it on row 1, yielding:
      //  0:  [    a       ]
      //  1:    [  b  ][e]
      //  2:    [c][d]
      //
      // If we then get this slice:
      //                      [f]
      // We do the same deepest-to-shallowest walk of the subrows trying to fit
      // it. This time, it doesn't fit in any open slice. So, we simply append
      // it to row 0:
      //  0:  [    a       ]  [f]
      //  1:    [  b  ][e]
      //  2:    [c][d]
      if (!slices.length)
        return [];

      var ops = [];
      for (var i = 0; i < slices.length; i++) {
        if (slices[i].subSlices)
          slices[i].subSlices.splice(0,
                                     slices[i].subSlices.length);
        ops.push(i);
      }

      ops.sort(function(ix, iy) {
        var x = slices[ix];
        var y = slices[iy];
        if (x.start != y.start)
          return x.start - y.start;

        // Elements get inserted into the slices array in order of when the
        // slices end.  Because slices must be properly nested, we break
        // start-time ties by assuming that the elements appearing earlier in
        // the slices array (and thus ending earlier) start later.
        return iy - ix;
      });

      var subRows = [[]];
      this.badSlices_ = [];  // TODO(simonjam): Connect this again.

      for (var i = 0; i < ops.length; i++) {
        var op = ops[i];
        var slice = slices[op];

        // Try to fit the slice into the existing subrows.
        var inserted = false;
        for (var j = subRows.length - 1; j >= 0; j--) {
          if (subRows[j].length == 0)
            continue;

          var insertedSlice = subRows[j][subRows[j].length - 1];
          if (slice.start < insertedSlice.start) {
            this.badSlices_.push(slice);
            inserted = true;
          }
          if (slice.start >= insertedSlice.start &&
              slice.end <= insertedSlice.end) {
            // Insert it into subRow j + 1.
            while (subRows.length <= j + 1)
              subRows.push([]);
            subRows[j + 1].push(slice);
            if (insertedSlice.subSlices)
              insertedSlice.subSlices.push(slice);
            inserted = true;
            break;
          }
        }
        if (inserted)
          continue;

        // Append it to subRow[0] as a root.
        subRows[0].push(slice);
      }

      return subRows;
    },

    areArrayContentsSame_: function(a, b) {
      if (!a || !b)
        return false;
      if (!a.length || !b.length)
        return false;
      if (a.length != b.length)
        return false;
      for (var i = 0; i < a.length; ++i) {
        if (a[i] != b[i])
          return false;
      }
      return true;
    }
  };

  return {
    SliceGroupTrack: SliceGroupTrack
  };
});
