<template>
  <g
    class="link"
    :class="{ detailed: isDetailed, highlighted: isFocused }"
    :opacity="visuals.opacity"
  >
    <line
      v-if="isGloballyHighlighted"
      stroke="rgba(0, 0, 0, 0.06)"
      :stroke-width="20"
      :x1="source.x"
      :y1="source.y"
      :x2="target.x"
      :y2="target.y"
      class="link-line"
    />

    <line
      :id="`link-${link.uuid}`"
      :stroke="lineStrokeColor()"
      :stroke-width="lineStrokeWidth()"
      :x1="source.x"
      :y1="source.y"
      :x2="target.x"
      :y2="target.y"
      class="link-line"
      :class="{
        hovered: isHovered,
        'with-arrows': link.oriented === true,
        'user-defined': hasUserDefinedStyle,
        transferred: link.properties && link.properties.get('isTransferred'),
        'globally-highlighted': isGloballyHighlighted,
        multilink: link.properties && link.properties.get('isMultilink'),
        'source-to-target': link.oriented && link.oriented.sourceToTarget,
        'target-to-source': link.oriented && link.oriented.targetToSource,
        'non-directional': link.oriented && link.oriented.nonDirectional
      }"
    />

    <g v-if="!isNaN(pawSourcePosition.x)" id="link-paws">
      <circle
        v-for="pos in [pawSourcePosition, pawTargetPosition]"
        :key="`${pos.x}-${pos.y}`"
        :class="{
          hovered: isHovered,
          detailed: isDetailed,
          highlighted: isFocused
        }"
        :r="visuals.linkPawRadius"
        :cx="pos.x"
        :cy="pos.y"
      />
    </g>

    <text
      v-if="visuals.inDebug"
      class="label-id"
      :dy="(source.y + target.y) / 2"
      :dx="(source.x + target.x) / 2"
    >
      {{ link.uuid }}
    </text>

    <!-- MULTILINK LABEL -->
    <g v-if="isMultilink" class="label">
      <rect
        :width="20"
        height="22"
        :y="(source.y + target.y) / 2 - 20"
        :x="(source.x + target.x) / 2 - 10"
        rx="5"
        ry="5"
      />
      <text
        :dy="(source.y + target.y) / 2 - 5"
        :dx="(source.x + target.x) / 2"
        text-anchor="middle"
      >
        {{ multilinkSize }}
      </text>
    </g>

    <!-- HOVERED LABEL -->
    <g v-if="isHovered && link.properties.get('description')" class="label description">
      <rect
        :width="80"
        height="22"
        :y="(source.y + target.y) / 2 - 20"
        :x="(source.x + target.x) / 2 - 40"
        rx="5"
        ry="5"
      />
      <text
        :dy="(source.y + target.y) / 2 - 5"
        :dx="(source.x + target.x) / 2"
        text-anchor="middle"
      >
        {{ link.type }}
      </text>
    </g>

    <line
      :x1="source.x"
      :y1="source.y"
      :x2="target.x"
      :y2="target.y"
      class="support-link"
      @mouseover="$emit('link-mouseover', link.uuid)"
      @mouseover.ctrl.exact="$emit('link-highlighted', link.uuid)"
      @mouseout="
        $emit('link-mouseout', link.uuid)
        $emit('link-clear-highlight', link.uuid)
      "
      @mousedown.prevent
      @mousedown.left.exact="$emit('link-clicked', link.uuid)"
    />
  </g>
</template>

<script>
import { isMultilink } from '@/store/modules/data/helpers.js'
import { hasVisAttr } from '@/store/modules/data/helpers'

export default {
  props: {
    link: { type: Object, required: true },
    source: {
      type: Object,
      required: true,
      validator: obj => {
        for (const attr of ['uuid', 'x', 'y']) {
          if (obj.hasOwnProperty(attr) === -1) return false
        }
        return true
      }
    },
    target: {
      type: Object,
      required: true,
      validator: obj => {
        for (const attr of ['uuid', 'x', 'y']) {
          if (obj.hasOwnProperty(attr) === -1) return false
        }
        return true
      }
    },
    isHighlighted: { type: Boolean, required: true },
    isDetailed: { type: Boolean, required: true },
    isSelected: { type: Boolean, required: true },
    isHovered: { type: Boolean, required: true },
    isFocused: { type: Boolean, required: true },
    isGloballyHighlighted: { type: Boolean, default: false },
    sourceNodeType: { type: String, required: true },
    targetNodeType: { type: String, required: true },
    isSourceNodeMiniature: { type: Boolean, default: false },
    isTargetNodeMiniature: { type: Boolean, default: false },
    distanceFromFocusedNode: { type: Number, default: 0 },
    isHighlightedInDiff: { type: Boolean, default: false },
    visuals: {
      type: Object,
      required: true,
      validator: obj => {
        for (const attr of ['opacity', 'inDebug']) {
          if (obj.hasOwnProperty(attr) === -1) return false
        }
        return true
      }
    }
  },

  computed: {
    hasUserDefinedStyle() {
      return (
        hasVisAttr(this.link, 'isUserDefined') || hasVisAttr(this.link, 'aggregatesUserDefined')
      )
    },
    pawSourcePosition() {
      let radius = this.isTargetNodeMiniature ? 6 : this.visuals.baseNodeRadius
      if (this.targetNodeType === 'group') radius += this.visuals.groupNodeRadiusDifference
      return this.calculatePawPosition(this.target, this.source, radius)
    },
    pawTargetPosition() {
      let radius = this.isSourceNodeMiniature ? 6 : this.visuals.baseNodeRadius
      if (this.sourceNodeType === 'group') radius += this.visuals.groupNodeRadiusDifference
      return this.calculatePawPosition(this.source, this.target, radius)
    },
    showLabel() {
      return this.isHovered
    },
    isTransferred: {
      cache: false,
      get: function() {
        return this.link.properties && this.link.properties.get('isTransferred')
      }
    },
    isMultilink() {
      return this.link.properties && this.link.properties.get('isMultilink') === true
    },
    multilinkSize() {
      if (!isMultilink(this.link)) return ''
      return this.link.properties.get('multilinkSize')
    }
  },

  watch: {
    link: function(l) {
      if (!l && this.vm) this.vm.$destroy()
    }
  },

  methods: {
    calculatePawPosition(source, target, nodeRadius) {
      let x, y

      let x_diff = target.x - source.x
      let y_diff = target.y - source.y

      let length = Math.sqrt(Math.pow(x_diff, 2) + Math.pow(y_diff, 2))
      let x_diff_norm = x_diff / length
      let y_diff_norm = y_diff / length

      x = source.x + x_diff_norm * nodeRadius
      y = source.y + y_diff_norm * nodeRadius
      return { x, y }
    },
    lineStrokeWidth() {
      // focus-context activated
      if (!isNaN(this.distanceFromFocusedNode)) {
        if (this.isMultilink) {
          if (this.isTransferred) {
            return Math.max(1, 4.5 / (1 + Math.log(this.distanceFromFocusedNode) ** 4))
          }
          return Math.max(1, 4 / (1 + Math.log(this.distanceFromFocusedNode) ** 4))
        } else {
          return Math.max(1, 3 / (1 + Math.log(this.distanceFromFocusedNode) ** 4))
        }
      }

      if (this.isTransferred) {
        if (this.isMultilink) return 4
        return 2
      }
      return this.isMultilink ? 3 : 1
    },
    lineStrokeColor() {
      if (this.isHighlightedInDiff) return this.visuals.diffHighlightColor
      // focus-context activated
      if (!isNaN(this.distanceFromFocusedNode) && this.distanceFromFocusedNode === 1) {
        return isMultilink(this.link) ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.32)'
      }

      return isMultilink(this.link) ? 'rgba(0, 0, 0, 0.18)' : 'rgba(0, 0, 0, 0.25)'
    }
  }
}
</script>

<style lang="sass" scoped>
.link
  line
    cursor: default
    color: #ccc
    stroke-linecap: round
    &.with-arrows
      marker-end: url(#single-link-arrow)
    &.multilink
      color: #bbb
      &.source-to-target
        marker-end: url(#multi-link-arrow)
        &.non-directional
          marker-end: url(#multi-link-arrow-bar)
      &.target-to-source
        marker-start: url(#multi-link-arrow-opposite)
        &.non-directional
          marker-start: url(#multi-link-arrow-bar-opposite)
    &.hovered
      stroke: #757575
    &.user-defined
      stroke-dasharray: 7,7
      stroke-width: 1px
      &.multilink
        stroke-width: 2.5px
    &.transferred
      stroke-dasharray: 0,6
      stroke-width: 2px
      &.multilink
        stroke-width: 2.5px
    &.user-defined.transferred
      stroke-dasharray: 7, 6, 1, 6, 1, 6
      stroke-width: 1.1px // to level with user-defined singlelinks
      &.multilink
        stroke-width: 2.5px
    &.globally-highlighted
      color: red

.support-link
  stroke: rgba(0,0,0,0) // needs to have defined stroke in order to be not optimized out
  stroke-opacity: 0
  stroke-width: 18px

.label
  rect
    display: inline
    fill: rgba(230, 230, 230, 0.8)
  text
    font-size: 0.8em
    font-weight: 600
    fill: $secondary-text-color
  &.description
    rect
      fill: $netvis-graph-background
      opacity: 0.9
    text
      font-weight: 400
      fill: $secondary-text-color

text.label-id
  font-size: 11pt
  font-weight: 600
  fill: #4903fc
  user-select: none

#link-paws circle
  fill: #bbb
  &.detailed
    fill: #444
  &.hovered
    fill: #1e2dff
</style>
