import sortBy from 'lodash/sortBy';

class Event {
  constructor(data, { accessors, slotMetrics }) {
    const {
      start,
      startDate,
      end,
      endDate,
      left,
      width,
      height,
    } = slotMetrics.getRange(accessors.start(data), accessors.end(data));

    this.start = start;
    this.end = end;
    this.startMs = +startDate;
    this.endMs = +endDate;
    this.left = left;
    this.width = width;
    this.height = height;
    this.data = data;
  }

  /**
   * The event's width without any overlap.
   */
  get _height() {
    // // The container event's width is determined by the maximum number of
    // // events in any of its rows.
    // if (this.rows) {
    //   const columns =
    //     this.rows.reduce(
    //       (max, row) => Math.max(max, row.leaves.length + 1), // add itself
    //       0
    //     ) + 1 // add the container

    //   return 100 / columns
    // }

    // const availableWidth = 100 - this.container._height

    // // The row event's width is the space left by the container, divided
    // // among itself and its leaves.
    // if (this.leaves) {
    //   return availableWidth / (this.leaves.length + 1)
    // }

    // // The leaf event's width is determined by its row's width
    // return this.row._height
    return 50;
  }

  // /**
  //  * The event's calculated width, possibly with extra width added for
  //  * overlapping effect.
  //  */
  // get width() {
  //   const noOverlap = this._height
  //   const overlap = Math.min(100, this._height * 1.7)

  //   // Containers can always grow.
  //   if (this.rows) {
  //     return overlap
  //   }

  //   // Rows can grow if they have leaves.
  //   if (this.leaves) {
  //     return this.leaves.length > 0 ? overlap : noOverlap
  //   }

  //   // Leaves can grow unless they're the last item in a row.
  //   const { leaves } = this.row
  //   const index = leaves.indexOf(this)
  //   return index === leaves.length - 1 ? noOverlap : overlap
  // }

  get yOffset() {
    // Containers have no offset.
    if (this.rows) return 0;

    // Rows always start where their container ends.
    if (this.leaves) return this.container._height;

    // Leaves are spread out evenly on the space left by its row.
    const { leaves, yOffset, _height } = this.row;
    const index = leaves.indexOf(this) + 1;
    return yOffset + (index * _height);
  }
}

/**
 * Return true if event a and b is considered to be on the same row.
 */
function onSameRow(a, b, minimumStartDifference) {
  return (
    // Occupies the same start slot.
    Math.abs(b.start - a.start) < minimumStartDifference ||
    // A's start slot overlaps with b's end slot.
    (b.start > a.start && b.start < a.end)
  );
}

function sortByRender(events) {
  const sortedByTime = sortBy(events, ['startMs', e => -e.endMs]);

  const sorted = [];
  while (sortedByTime.length > 0) {
    const event = sortedByTime.shift();
    sorted.push(event);

    for (let i = 0; i < sortedByTime.length; i += 1) {
      const test = sortedByTime[i];

      // Still inside this event, look for next.
      if (event.endMs > test.startMs) continue;

      // We've found the first event of the next event group.
      // If that event is not right next to our current event, we have to
      // move it here.
      if (i > 0) {
        const event = sortedByTime.splice(i, 1)[0];
        sorted.push(event);
      }

      // We've already found the next event group, so stop looking.
      break;
    }
  }

  return sorted;
}

export default function getStyledEvents({
  events,
  minimumStartDifference,
  slotMetrics,
  accessors,
}) {
  // Create proxy events and order them so that we don't have
  // to fiddle with z-indexes.
  const proxies = events.map(
    event => new Event(event, { slotMetrics, accessors })
  );

  const eventsInRenderOrder = sortByRender(proxies);

  // Group overlapping events, while keeping order.
  // Every event is always one of: container, row or leaf.
  // Containers can contain rows, and rows can contain leaves.
  const containerEvents = [];
  for (let i = 0; i < eventsInRenderOrder.length; i += 1) {
    const event = eventsInRenderOrder[i];

    // Check if this event can go into a container event.
    const container = containerEvents.find(
      c =>
        c.end > event.start ||
        Math.abs(event.start - c.start) < minimumStartDifference,
    );
    // Couldn't find a container — that means this event is a container.
    if (!container) {
      event.rows = [];
      containerEvents.push(event);
      continue;
    }

    // Found a container for the event.
    event.container = container;

    // Check if the event can be placed in an existing row.
    // Start looking from behind.
    let row = null;
    for (let j = container.rows.length - 1; !row && j >= 0; j -= 1) {
      if (onSameRow(container.rows[j], event, minimumStartDifference)) {
        row = container.rows[j];
      }
    }

    if (row) {
      // Found a row, so add it.
      row.leaves.push(event);
      event.row = row;
    }
    else {
      // Couldn't find a row – that means this event is a row.
      event.leaves = [];
      container.rows.push(event);
    }
  }

  // Return the original events, along with their styles.
  return eventsInRenderOrder.map(event => ({
    event: event.data,
    style: {
      top: `${event.yOffset}px`,
      left: event.left,
      height: `${event._height}px`,
      width: event.width,
      yOffset: Math.max(0, event.yOffset) + event._height,
    },
  }));
}
