<template>
  <div class="annotation-overlay" :style="position"
       @touchstart="press" @touchmove="track" @touchend="release"
       @mousedown="press" @mousemove="track" @mouseup="release"
       @dragstart.stop>

    <annotation-box v-for="(box, index) in boundingBoxes"
                    v-bind:key="box.identifier"
                    ref="box"
                    :box="box"
                    :position="box.data"
                    :draggable="interaction === 'move-box'"
                    :deletable="interaction === 'remove-box'"
                    :taggable="interaction === 'label-box' ? label : false"
                    :confirmable="interaction === 'confirm-box'"
                    :zoomable="interaction === 'zoom-box'"
                    :croppable="interaction === 'info-box'"
                    :labels="labels"
                    :shine="crop && box.identifier === crop.identifier"
                    @move="move"
                    @resize="resize"
                    @crop="$emit('crop', $event)"
                    @zoom="zoomBox(index, $event)"/>

    <annotation-box v-if="current"
                    :position="current"/>

    <div v-if="imageLabelResult"
         class="image-label"
         :class="{
           user: imageLabelResult.origin === 'user',
           pipeline: imageLabelResult.origin === 'pipeline'
         }">
      {{ imageLabelObject.name }}
    </div>
  </div>
</template>

<script>
import AnnotationBox from "@/components/media/annotation-box";

export default {
  name: "annotation-overlay",
  components: {AnnotationBox},
  props: [
    'file',
    'position',
    'size',
    'interaction',
    'filter',
    'label',
    'video',
    'results',
    'labels',
    'crop',
    'zoom'
  ],
  data: function () {
    return {
      callback: false,
      start: false,
      fixed: false,
      current: false,
      mousedown: false,
      zoomed: false
    }
  },
  computed: {
    boundingBoxes: function () {
      return this.results.filter(b => {
        return b.type === 'bounding-box' && this.filter.includes(b.origin)
            && (!('frame' in b.data) || b.data.frame === this.video.frame)
      });
    },
    imageLabelResult: function () {
      for (let result of this.results)
        if (result.type === 'labeled-image')
          return result;

      return false;
    },
    imageLabelObject: function () {
      if (!this.imageLabelResult)
        return false;

      for (let label of this.labels)
        if (label.identifier === this.imageLabelResult.label)
          return label;

      return 'unknown label';
    }
  },
  methods: {
    getEventCoordinates: function (event) {
      if (!('clientX' in event)) {
        if ('touches' in event && event.touches.length > 0)
          event = event.touches[0];
        else if ('changedTouches' in event && event.changedTouches.length > 0)
          event = event.changedTouches[0];
      }

      return {
        x: Math.max(0, Math.min(1, (event.clientX - this.size.left) / this.size.width)),
        y: Math.max(0, Math.min(1, (event.clientY - this.size.top) / this.size.height))
      }
    },
    buildRectangle: function (start, end) {
      let lx, hx, ly, hy;

      if (this.fixed && 'offsetX' in this.fixed && 'offsetY' in this.fixed) {
        lx = Math.max(end.x + this.fixed.offsetX, 0);
        hx = Math.min(end.x + this.fixed.offsetX + this.fixed.w, 1);
        ly = Math.max(end.y + this.fixed.offsetY, 0);
        hy = Math.min(end.y + this.fixed.offsetY + this.fixed.h, 1);
      } else {
        lx = this.fixed && 'lx' in this.fixed ? this.fixed.lx : Math.max(Math.min(start.x, end.x), 0);
        hx = this.fixed && 'hx' in this.fixed ? this.fixed.hx : Math.min(Math.max(start.x, end.x), 1);
        ly = this.fixed && 'ly' in this.fixed ? this.fixed.ly : Math.max(Math.min(start.y, end.y), 0);
        hy = this.fixed && 'hy' in this.fixed ? this.fixed.hy : Math.min(Math.max(start.y, end.y), 1);
      }

      if (this.file.type === 'image') {
        return {
          x: lx,
          y: ly,
          w: hx - lx,
          h: hy - ly
        }
      } else {
        return {
          x: lx,
          y: ly,
          w: hx - lx,
          h: hy - ly,
          frame: this.video.frame
        }
      }
    },
    press: function (event) {
      this.mousedown = true;

      if (this.interaction === 'draw-box') {
        this.start = this.getEventCoordinates(event);
      }
      if (this.interaction === 'extreme-clicking') {
        const coordinates = this.getEventCoordinates(event);

        if (!this.start) {
          this.start = coordinates;
        } else if (!this.current) {
          this.current = this.buildRectangle(this.start, coordinates);
        } else {
          this.current = this.buildRectangle({
            x: Math.min(coordinates.x, this.current.x),
            y: Math.min(coordinates.y, this.current.y)
          }, {
            x: Math.max(coordinates.x, this.current.x + this.current.w),
            y: Math.max(coordinates.y, this.current.y + this.current.h)
          });
        }
      }
      if (this.interaction === 'label-box') {
        this.$root.socket.post(`/data/${this.file.identifier}/results`, {
          type: 'labeled-image',
          label: this.label.identifier
        });
      }
      if (this.interaction === 'confirm-box') {
        if (this.imageLabelResult) {
          this.$root.socket.post(`/results/${this.imageLabelResult.identifier}/confirm`, {
            confirm: true
          });
        }
      }
      if (this.interaction === 'remove-box') {
        if (this.imageLabelResult) {
          this.$root.socket.post(`/results/${this.imageLabelResult.identifier}/remove`, {
            remove: true
          });
        }
      }
    },
    track: function (event) {
      if (this.interaction === 'extreme-clicking') {
        if (this.mousedown)
          this.press(event);

        return;
      }

      if (this.start) {
        const coordinates = this.getEventCoordinates(event);
        this.current = this.buildRectangle(this.start, coordinates);
      }
    },
    release: function () {
      this.mousedown = false;

      if (this.interaction === 'extreme-clicking')
        return;

      if (this.current) {
        if (this.callback)
          this.callback(this.current);
        else
            // TODO then / error
          this.$root.socket.post(`/data/${this.file.identifier}/results`, {
            type: 'bounding-box',
            data: this.current
          });
      }

      this.callback = false;
      this.start = false;
      this.fixed = false;
      this.current = false;
    },
    move: function (event, position, callback) {
      if (this.interaction !== 'move-box')
        return;

      this.callback = callback;
      const coordinates = this.getEventCoordinates(event);

      this.start = position;
      this.fixed = {
        w: position.w,
        h: position.h,
        offsetX: position.x - coordinates.x,
        offsetY: position.y - coordinates.y
      };
    },
    resize: function (mode, position, callback) {
      if (this.interaction !== 'move-box')
        return;

      this.callback = callback;
      switch (mode) {
        case 'nw':
          this.start = {x: position.x + position.w, y: position.y + position.h};
          this.fixed = false;
          break;
        case 'ne':
          this.start = {x: position.x, y: position.y + position.h};
          this.fixed = false;
          break;
        case 'sw':
          this.start = {x: position.x + position.w, y: position.y};
          this.fixed = false;
          break;
        case 'se':
          this.start = {x: position.x, y: position.y};
          this.fixed = false;
          break;
        case 'nn':
          this.start = {x: position.x, y: position.y + position.h};
          this.fixed = {lx: position.x, hx: position.x + position.w};
          break;
        case 'ww':
          this.start = {x: position.x + position.w, y: position.y};
          this.fixed = {ly: position.y, hy: position.y + position.h};
          break;
        case 'ee':
          this.start = {x: position.x, y: position.y};
          this.fixed = {ly: position.y, hy: position.y + position.h};
          break;
        case 'ss':
          this.start = {x: position.x, y: position.y};
          this.fixed = {lx: position.x, hx: position.x + position.w};
          break;
      }
    },
    zoomBox: function (index) {
      if (this.boundingBoxes.length === 0) {
        this.zoomed = false;
        return;
      }
      if (index < 0) {
        index = this.boundingBoxes.length - 1;
      } else if (index >= this.boundingBoxes.length) {
        index = 0;
      }

      this.zoomed = index;
      this.$emit('zoom', this.boundingBoxes[index].data);
    },
    prevZoom: function () {
      this.zoomBox(this.zoomed - 1);
    },
    nextZoom: function () {
      this.zoomBox(this.zoomed + 1);
    }
  },
  watch: {
    interaction: function (newVal, oldVal) {
      if (oldVal === 'extreme-clicking')
        this.release();
    }
  }
}
</script>

<style scoped>
.annotation-overlay {
  position: absolute;
}

.image-label {
  position: absolute;
  right: 0;
  bottom: 0;

  padding: 0.7rem 1rem;
  color: whitesmoke;
  border-top-left-radius: 1rem;
}

.image-label.user {
  background-color: rgba(255, 0, 0, 0.4);
}

.image-label.pipeline {
  background-color: rgba(0, 0, 255, 0.4);
}
</style>
