<template>
  <g
    class="flow-node"
    :class="{
      'flow-node--dragging':dragging,
      'flow-node--selected': selected
    }"
    :status="status"
    @mousedown.stop.prevent="$emit('nodePointerDown',$event)"
    @dblclick="$emit('nodeDoubleClick',$event)"
  >

    <!-- selection square -->
    <rect
      class="flow-node__selection"
      stroke-dasharray="7,3"
      :x="bodyProps.x-4"
      :y="bodyProps.y-4"
      :width="bodyProps.width+8"
      :height="bodyProps.height+8"
    />

    <!-- shape -->
    <template>
      <svg
        v-if="style.shape == 'thing'"
        ref="body"
        viewBox="0 0 100 100"
        preserveAspectRatio="XMinYMin"
        class="flow-node__body"
        :class="{'flow-node__body--dragging':dragging}"
        v-bind="bodyProps"
      >
        <path d=" M 0 0 l 90 0 l 10 50 l -10 50 l -90 0 c 10 -20, 10 -80, 0 -100 Z " />
      </svg>
      <circle
        v-else-if="style.shape == 'circle'"
        ref="body"
        class="flow-node__body"
        :class="{'flow-node__body--dragging':dragging}"
        v-bind="bodyProps"
      />
      <rect
        v-else
        ref="body"
        class="flow-node__body"
        :class="{'flow-node__body--dragging':dragging}"
        v-bind="bodyProps"
      />
    </template>

    <!-- label -->
    <text
      ref="label"
      class="flow-node__label"
      v-bind="labelProps"
      text-anchor="middle">
      <tspan
        :key="'label-wrap' + i"
        x="0"
        dy="1em"
        v-for="(s,i) in labelWrap" >
        {{ s }}
      </tspan>
    </text>

    <!-- Sockets -->
    <!-- input -->
    <g
      v-for="(inp,i) in inputs"
      :key="'in'+i"
      :data-nodeid="id"
      :data-in="i"
      v-bind="inputProps(i)"
      @mousedown.stop.prevent="socketPointerDown($event, {in:i})"
      class="flow-node__socket flow-node__socket--inputs"
      :class="{'flow-node__socket--match': match.in && (inp.type == match.in || match.in == 'interface {}' || inp.type=='interface {}')}"
    >
      <circle r="5" />
      <text
        text-anchor="end"
        :x="-18"
        :y="4"
        class="flow-node__socket-detail"
      >{{ inputLabel(i) }}</text>

    </g>
    <!-- output -->
    <g
      v-if="output"
      v-bind="outputProps(0)"
      :data-nodeid="id"
      :data-out="0"
      :key="'out'+0"
      @mousedown.stop.prevent="socketPointerDown($event, {out:0})"
      class="flow-node__socket flow-node__socket--outputs"
      :class="{ 'flow-node__socket--match': match.out && (output.type == match.out || match.out == 'interface {}' || output.type == 'interface {}'), }"
    >
      <circle r="5" />
      <text
        class="flow-node__socket-detail"
        :x="18"
        :y="4">
        {{ outputLabel(0) }}
      </text>
    </g>

    <!-- socket labels -->

    <flow-node-activity
      v-if="activity"
      :activity="activity"
      :transform="'translate('+bodyProps.width/2 +','+ -bodyProps.height/2 +')'"/>
  </g>
</template>

<script>
import FlowNodeActivity from './node-activity'

import utils from '@/utils/utils'
const shapeOpts = {
  'circle': {
    textWrap: 'any'
  },
  default: {
    textWrap: 'white-space'
  }
}
export default {
  name: 'FlowNode',
  components: {FlowNodeActivity},
  props: {
    'id': {type: String, required: true},
    'label': {type: String, default: ''},
    'inputs': {type: Array, default: () => []},
    'output': {type: Object, default: ''},
    'match': {type: Object, default: () => {}},
    'dragging': {type: Boolean, default: false},
    'selected': {type: Boolean, default: false},
    'activity': {type: Object, default: () => {}},

    'nodeStyle': {type: Object, default: () => {}}
  },
  data () {
    return {
      // maintain reference here?
      labelRect: {x: 0, y: 0, width: 0, height: 0},
      bodyRect: {x: 0, y: 0, width: 0, height: 0}
    }
  },
  computed: {
    style () {
      return this.nodeStyle || {}
    },
    status () {
      return this.activity && (this.activity.status || '')
    },
    labelWrap () {
      let wrapThreshold = 8 // initial wrap threshold
      const opt = shapeOpts[this.style.shape] || shapeOpts.default
      return utils.textWrap(this.label, wrapThreshold, opt.textWrap)
    },
    labelProps () {
      return {
        x: 0,
        y: 0,
        fill: this.textColor,
        // transform: `translate(${-this.labelRect.width / 2},${-this.labelRect.height / 2})`
        transform: `translate(0,${-this.labelRect.height / 2})`
      }
    },
    bodyProps () {
      let width = this.labelRect.width + 46
      let height = Math.max(this.labelRect.height + 20, 60, this.inputs.length * 25)
      if (this.style.shape === 'circle') {
        width = height = Math.max(width, height)
      }

      const rect = {
        x: -width / 2,
        y: -height / 2,
        width: width,
        height: height,
        stroke: this.style.color || '#777',
        fill: this.style.color || '#777'
      }
      if (this.style.shape === 'circle') {
        rect.r = Math.max(width / 2, height / 2)
      }
      return rect
    },
    inputProps () {
      return (i) => {
        const {x, y} = this.inputPos(i)
        return {
          transform: `translate(${x} ${y})`
        }
      }
    },
    outputProps () {
      return (i) => {
        const {x, y} = this.outputPos(i)
        return {
          transform: `translate(${x} ${y})`,
          r: 5
        }
      }
    },
    inputLabel () {
      return (i) => {
        let input = ''
        if (this.inputs[i].name) {
          input += this.inputs[i].name + ':'
        }
        input += this.inputs[i].type
        return input
      }
    },
    outputLabel () {
      return (i) => {
        var output = ''
        if (this.output.name) {
          output += this.output.name + ':'
        }
        output += this.output.type

        return output
      }
    }
  },
  watch: {
    // the only thing now that affects geometry
    'label' () {
      this.$nextTick(() => {
        this.labelRect = this.$refs.label.getBBox()
      })
    }
  },
  mounted () {
    // After render
    this.$nextTick(() => { // after mount we reupdate with new values
      if (!this.$refs.label) return
      this.labelRect = this.$refs.label.getBBox()
      this.$forceUpdate()
    })
  },
  methods: {
    inputPos (i) {
      const ilen = this.inputs.length
      if (ilen === 0) return {}
      const d = this.bodyProps.height / (ilen * 2)
      return {
        x: this.bodyProps.x + 7,
        y: this.bodyProps.y + d + (i * 2 * d)
      }
    },

    outputPos (i) {
      const rect = this.bodyProps
      return {
        x: rect.x + rect.width - 7,
        y: 0
      }
    },
    socketPointerDown (ev, socket) {
      this.$emit('socketPointerDown', ev, socket)
    }

  }
}
</script>
<style>

.flow-view:not(.activity) .flow-node:hover,
.flow-node--dragging {
  cursor:move;
}

.flow-node__socket {
  pointer-events: none;
  stroke-width:1;
  opacity:0;
  transition: all var(--transition-speed);
}

.flow-node__body {
  transition: all var(--transition-speed);
}

.flow-node[status=running] .flow-node__body{
  stroke: yellow !important;
}

.flow-node[status=error] .flow-node__body{
  stroke: red !important;
}

.flow-node[status=finished] .flow-node__body{
  stroke: green !important;
}

.flow-view:not(.activity) .flow-node__socket:hover {
  stroke-width:10;
  cursor:pointer;
}

.flow-node__socket-detail {
  opacity:1;
  stroke-width:0 !important;
  fill: #fff !default;
  transition: all var(--transition-speed);
}

.flow-node__socket--match {
  cursor:pointer;
  stroke-width:10;
}

/*
Override flow-node
for hidden
 */
.flow-linking .flow-node__socket {
  opacity:1;
  pointer-events: inherit;
}

.flow-linking .flow-node__socket--match,
.flow-node__socket--match {
  opacity:1;
  pointer-events: inherit;
}

.flow-node__label {
  stroke:none;
  pointer-events:none;
  user-select:none;
  fill:#333 !default;
}

.flow-node .flow-node__selection {
  opacity:0;
  stroke-width:1;
  pointer-events:none;
  transition: all var(--transition-speed);
}

.flow-node--selected .flow-node__selection {
  opacity:1;
}

</style>
