import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { guidFor } from '@ember/object/internals';

export default class TopoVizNode extends Component {
  @tracked data = { cpu: [], memory: [] };
  @tracked dimensionsWidth = 0;
  @tracked padding = 5;
  @tracked activeAllocation = null;

  get height() {
    return this.args.heightScale
      ? this.args.heightScale(this.args.node.memory)
      : 15;
  }

  get labelHeight() {
    return this.height / 2;
  }

  get paddingLeft() {
    const labelWidth = 20;
    return this.padding + labelWidth;
  }

  // Since strokes are placed centered on the perimeter of fills, The width of the stroke needs to be removed from
  // the height of the fill to match unstroked height and avoid clipping.
  get selectedHeight() {
    return this.height - 1;
  }

  // Since strokes are placed centered on the perimeter of fills, half the width of the stroke needs to be added to
  // the yOffset to match heights with unstroked shapes.
  get selectedYOffset() {
    return this.height + 2.5;
  }

  get yOffset() {
    return this.height + 2;
  }

  get maskHeight() {
    return this.height + this.yOffset;
  }

  get totalHeight() {
    return this.maskHeight + this.padding * 2;
  }

  get maskId() {
    return `topo-viz-node-mask-${guidFor(this)}`;
  }

  get count() {
    return this.allocations.length;
  }

  get allocations() {
    // Sort by the delta between memory and cpu percent. This creates the least amount of
    // drift between the positional alignment of an alloc's cpu and memory representations.
    return this.args.node.allocations
      .filterBy('allocation.isScheduled')
      .sort((a, b) => {
        const deltaA = Math.abs(a.memoryPercent - a.cpuPercent);
        const deltaB = Math.abs(b.memoryPercent - b.cpuPercent);
        return deltaA - deltaB;
      });
  }

  @action
  async reloadNode() {
    if (this.args.node.isPartial) {
      await this.args.node.reload();
      this.data = this.computeData(this.dimensionsWidth);
    }
  }

  @action
  render(svg) {
    this.dimensionsWidth = svg.clientWidth - this.padding - this.paddingLeft;
    this.data = this.computeData(this.dimensionsWidth);
  }

  @action
  updateRender(svg) {
    // Only update all data when the width changes
    const newWidth = svg.clientWidth - this.padding - this.paddingLeft;
    if (newWidth !== this.dimensionsWidth) {
      this.dimensionsWidth = newWidth;
      this.data = this.computeData(this.dimensionsWidth);
    }
  }

  @action
  highlightAllocation(allocation, { target }) {
    this.activeAllocation = allocation;
    this.args.onAllocationFocus &&
      this.args.onAllocationFocus(allocation, target);
  }

  @action
  allocationBlur() {
    this.args.onAllocationBlur && this.args.onAllocationBlur();
  }

  @action
  clearHighlight() {
    this.activeAllocation = null;
  }

  @action
  selectNode() {
    if (this.args.isDense && this.args.onNodeSelect) {
      this.args.onNodeSelect(this.args.node.isSelected ? null : this.args.node);
    }
  }

  @action
  selectAllocation(allocation) {
    if (this.args.onAllocationSelect) this.args.onAllocationSelect(allocation);
  }

  containsActiveTaskGroup() {
    return this.args.node.allocations.some(
      (allocation) =>
        allocation.taskGroupName === this.args.activeTaskGroup &&
        allocation.belongsTo('job').id() === this.args.activeJobId
    );
  }

  computeData(width) {
    const allocations = this.allocations;
    let cpuOffset = 0;
    let memoryOffset = 0;

    const cpu = [];
    const memory = [];
    for (const allocation of allocations) {
      const { cpuPercent, memoryPercent, isSelected } = allocation;
      const isFirst = allocation === allocations[0];

      let cpuWidth = cpuPercent * width - 1;
      let memoryWidth = memoryPercent * width - 1;
      if (isFirst) {
        cpuWidth += 0.5;
        memoryWidth += 0.5;
      }
      if (isSelected) {
        cpuWidth--;
        memoryWidth--;
      }

      cpu.push({
        allocation,
        offset: cpuOffset * 100,
        percent: cpuPercent * 100,
        width: Math.max(cpuWidth, 0),
        x: cpuOffset * width + (isFirst ? 0 : 0.5) + (isSelected ? 0.5 : 0),
        className: allocation.allocation.clientStatus,
      });
      memory.push({
        allocation,
        offset: memoryOffset * 100,
        percent: memoryPercent * 100,
        width: Math.max(memoryWidth, 0),
        x: memoryOffset * width + (isFirst ? 0 : 0.5) + (isSelected ? 0.5 : 0),
        className: allocation.allocation.clientStatus,
      });

      cpuOffset += cpuPercent;
      memoryOffset += memoryPercent;
    }

    const cpuRemainder = {
      x: cpuOffset * width + 0.5,
      width: Math.max(width - cpuOffset * width, 0),
    };
    const memoryRemainder = {
      x: memoryOffset * width + 0.5,
      width: Math.max(width - memoryOffset * width, 0),
    };

    return {
      cpu,
      memory,
      cpuRemainder,
      memoryRemainder,
      cpuLabel: { x: -this.paddingLeft / 2, y: this.height / 2 + this.yOffset },
      memoryLabel: { x: -this.paddingLeft / 2, y: this.height / 2 },
    };
  }
}
