<template>
  <div ref="item" class="vue-grid-layout" :style="mergedStyle">
    <slot></slot>
    <grid-item
      class="vue-grid-placeholder"
      v-show="isDragging"
      :x="placeholder.x"
      :y="placeholder.y"
      :w="placeholder.w"
      :h="placeholder.h"
      :i="placeholder.i"
      :type="placeholder.type"
      :HPosition="placeholder.HPosition"
    ></grid-item>
    <!-- :HPosition="placeholder.type=='screen'" -->
  </div>
</template>
<style scoped>
.vue-grid-layout {
  position: relative;
  transition: height 200ms ease;
}
</style>
<script>
import Vue from 'vue'
var elementResizeDetectorMaker = require('element-resize-detector')

import {
  bottom,
  compact,
  getLayoutItem,
  moveElement,
  validateLayout,
  cloneLayout,
  getAllCollisions
} from '../helpers/utils'
import {
  getBreakpointFromWidth,
  getColsFromBreakpoint,
  findOrGenerateResponsiveLayout
} from '../helpers/responsiveUtils'
//var eventBus = require('./eventBus');

import GridItem from './GridItem.vue'
import {
  addWindowEventListener,
  removeWindowEventListener
} from '../helpers/DOM'

export default {
  name: 'GridLayout',
  provide() {
    return {
      eventBus: null
    }
  },
  components: {
    GridItem
  },
  props: {
    formId: {
      // 真实环境下
      type: String,
      default: ''
    },
    // If true, the container height swells and contracts to fit contents
    autoSize: {
      type: Boolean,
      default: true
    },
    colNum: {
      type: Number,
      default: 12
    },
    rowHeight: {
      type: Number,
      default: 150
    },
    maxRows: {
      type: Number,
      default: Infinity
    },
    margin: {
      type: Array,
      default: function() {
        return [10, 10]
      }
    },
    isDraggable: {
      type: Boolean,
      default: true
    },
    isResizable: {
      type: Boolean,
      default: true
    },
    isMirrored: {
      type: Boolean,
      default: false
    },
    useCssTransforms: {
      type: Boolean,
      default: true
    },
    verticalCompact: {
      type: Boolean,
      default: true
    },
    layout: {
      type: Array,
      required: true
    },
    responsive: {
      type: Boolean,
      default: false
    },
    responsiveLayouts: {
      type: Object,
      default: function() {
        return {}
      }
    },
    breakpoints: {
      type: Object,
      default: function() {
        return { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }
      }
    },
    cols: {
      type: Object,
      default: function() {
        return { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }
      }
    },
    preventCollision: {
      type: Boolean,
      default: false
    }
  },
  data: function() {
    return {
      width: null,
      height: null,
      mergedStyle: {},
      lastLayoutLength: 0,
      isDragging: false,
      placeholder: {
        x: 0,
        y: 0,
        w: 0,
        h: 0,
        i: -1
      },
      layouts: {}, // array to store all layouts from different breakpoints
      lastBreakpoint: null, // store last active breakpoint
      originalLayout: null // store original Layout
    }
  },
  created() {
    const self = this
    self.resizeEventHandler = function(eventType, i, x, y, h, w) {
      self.resizeEvent(eventType, i, x, y, h, w)
    }
    self.dragEventHandler = function(eventType, i, x, y, h, w) {
      self.dragEvent(eventType, i, x, y, h, w)
    }
    self._provided.eventBus = new Vue()
    self.eventBus = self._provided.eventBus
    self.eventBus.$on('resizeEvent', self.resizeEventHandler)
    self.eventBus.$on('dragEvent', self.dragEventHandler)
    self.$emit('layout-created', self.layout)
  },
  beforeDestroy: function() {
    this.eventBus.$off('resizeEvent', this.resizeEventHandler)
    this.eventBus.$off('dragEvent', this.dragEventHandler)
    this.eventBus.$destroy()
    removeWindowEventListener('resize', this.onWindowResize)
    this.erd ? this.erd.uninstall(this.$refs.item) : ''
  },
  beforeMount: function() {
    this.$emit('layout-before-mount', this.layout)
  },
  mounted: function() {
    this.$emit('layout-mounted', this.layout)
    this.$nextTick(function() {
      validateLayout(this.layout)
      this.originalLayout = this.layout
      const self = this
      this.$nextTick(function() {
        self.onWindowResize()
        self.initResponsiveFeatures()
        //self.width = self.$el.offsetWidth;
        addWindowEventListener('resize', self.onWindowResize)
        compact(self.layout, self.verticalCompact, this)
        self.$emit('layout-updated', self.layout)
        self.updateHeight()
        self.$nextTick(function() {
          this.erd = elementResizeDetectorMaker({
            strategy: 'scroll', //<- For ultra performance.
            // See https://github.com/wnr/element-resize-detector/issues/110 about callOnAdd.
            callOnAdd: false
          })
          if (self.$refs.item) {
            this.erd.listenTo(self.$refs.item, function() {
              self.onWindowResize()
            })
          }
        })
      })
    })
  },
  watch: {
    width: function(newval, oldval) {
      const self = this
      this.$nextTick(function() {
        this.eventBus.$emit('updateWidth', this.width)
        if (oldval === null) {
          this.$nextTick(() => {
            this.$emit('layout-ready', self.layout)
          })
        }
        this.updateHeight()
      })
    },
    height: function(newval, oldval) {
      const self = this
      this.$nextTick(function() {
        if (
          newval - oldval != this.rowHeight &&
          oldval - newval != this.rowHeight // 防止到达底部时，出现抖动的情况
        ) {
          this.eventBus.$emit('updateHeight' + this.formId, this.height)
        }

        // if (oldval === null) {
        //   this.$nextTick(() => {
        //     this.$emit('layout-ready', self.layout)
        //   })
        // } else {
        //   this.eventBus.$emit('updateHeight' + this.formId, this.height)
        // }
        // this.updateHeight()
      })
    },
    layout: function() {
      this.layoutUpdate()
    },
    colNum: function(val) {
      this.eventBus.$emit('setColNum', val)
    },
    rowHeight: function() {
      this.eventBus.$emit('setRowHeight', this.rowHeight)
    },
    isDraggable: function() {
      this.eventBus.$emit('setDraggable', this.isDraggable)
    },
    isResizable: function() {
      this.eventBus.$emit('setResizable', this.isResizable)
    },
    responsive() {
      if (!this.responsive) {
        this.$emit('update:layout', this.originalLayout)
        this.eventBus.$emit('setColNum', this.colNum)
      }
      this.onWindowResize()
    },
    maxRows: function() {
      this.eventBus.$emit('setMaxRows', this.maxRows)
    },
    margin() {
      this.updateHeight()
    }
  },
  methods: {
    layoutUpdate() {
      if (this.layout !== undefined && this.originalLayout !== null) {
        if (this.layout.length !== this.originalLayout.length) {
          let diff = this.findDifference(this.layout, this.originalLayout)
          if (diff.length > 0) {
            if (this.layout.length > this.originalLayout.length) {
              this.originalLayout = this.originalLayout.concat(diff)
            } else {
              this.originalLayout = this.originalLayout.filter(obj => {
                return !diff.some(obj2 => {
                  return obj.i === obj2.i
                })
              })
            }
          }
          this.lastLayoutLength = this.layout.length
          this.initResponsiveFeatures()
        }
        compact(this.layout, this.verticalCompact, this)
        // this.eventBus.$emit('updateHeight' + this.formId, this.height)
        this.eventBus.$emit('updateWidth', this.width)
        this.updateHeight()

        this.$emit('layout-updated', this.layout)
      }
    },
    updateHeight: function() {
      this.mergedStyle = {
        height: this.containerHeight()
      }
    },
    // 绝对定位H转成相对定位
    HPositionToRelativeH(h, i, i1) {
      if (!this.height) {
        this.height = this.$el.offsetHeight
      }
      const scaleHeight = (this.height - this.margin[0] * (100 + 1)) / 100 // 比例
      let height = Math.round(10 * h + Math.max(0, h - 1) * this.margin[0])
      h = Math.round((height + this.margin[0]) / (scaleHeight + this.margin[0]))
      return h
    },
    onWindowResize: function() {
      if (
        this.$refs !== null &&
        this.$refs.item !== null &&
        this.$refs.item !== undefined
      ) {
        this.width = this.$refs.item.offsetWidth
        // if (this.$refs.item.offsetHeight!=null&&this.height - this.$refs.item.offsetHeight!=1) {
        this.height = this.$refs.item.offsetHeight
        // }
      }
      this.eventBus.$emit('resizeEvent')
    },
    containerHeight: function() {
      if (!this.autoSize) return
      const containerHeight =
        bottom(this.layout, this) * (this.rowHeight + this.margin[1]) +
        this.margin[1] +
        'px'
      return containerHeight
    },
    // 改变y和h,兼容旧版高度
    changeYH: function(eventName, id, x, y, h, w) {
      for (let item of this.layout) {
        if (item.i == id) {
          item.y = y
          item.h = h
          break
        }
      }
      compact(this.layout, this.verticalCompact, this)
      this.eventBus.$emit('compact')
      this.updateHeight()
      this.$emit('changeYH', this.layout)
    },
    dragEvent: function(eventName, id, x, y, h, w) {
      let l = getLayoutItem(this.layout, id)
      if (l === undefined || l === null) {
        l = { x: 0, y: 0 }
      }
      if (eventName === 'dragmove' || eventName === 'dragstart') {
        this.placeholder.i = id
        this.placeholder.x = l.x
        this.placeholder.y = l.y
        this.placeholder.w = w
        this.placeholder.h = h
        this.placeholder.type = l.type
        this.placeholder.HPosition = l.HPosition
        this.$nextTick(function() {
          this.isDragging = true
        })
        //this.$broadcast("updateWidth", this.width);
        this.eventBus.$emit('updateWidth', this.width)
        // this.eventBus.$emit('updateHeight' + this.formId, this.height)
      } else {
        this.$nextTick(function() {
          this.isDragging = false
        })
      }
      // Move the element to the dragged location.
      this.layout = moveElement(
        this.layout,
        l,
        x,
        y,
        true,
        this.preventCollision
      )
      compact(this.layout, this.verticalCompact, this)
      // // needed because vue can't detect changes on array element properties
      this.eventBus.$emit('compact')
      this.updateHeight()
      if (eventName === 'dragend') this.$emit('layout-updated', this.layout)
    },
    resizeEvent: function(eventName, id, x, y, h, w) {
      let l = getLayoutItem(this.layout, id)
      if (l === undefined || l === null) {
        l = { h: 1, w: 1 }
      }
      let hasCollisions
      if (this.preventCollision) {
        const collisions = getAllCollisions(this.layout, { ...l, w, h }).filter(
          layoutItem => layoutItem.i !== l.i
        )
        hasCollisions = collisions.length > 0

        // If we're colliding, we need adjust the placeholder.
        if (hasCollisions) {
          // adjust w && h to maximum allowed space
          let leastX = Infinity,
            leastY = Infinity
          collisions.forEach(layoutItem => {
            if (layoutItem.x > l.x) leastX = Math.min(leastX, layoutItem.x)
            if (layoutItem.y > l.y) leastY = Math.min(leastY, layoutItem.y)
          })

          if (Number.isFinite(leastX)) l.w = leastX - l.x
          if (Number.isFinite(leastY)) l.h = leastY - l.y
        }
      }

      if (!hasCollisions) {
        // Set new width and height.
        l.w = w
        l.h = h
      }
      // if (l.type == 'screen') {
      //   // 默认20像素不让拖动
      //   l.h = 2
      // }
      if (eventName === 'resizestart' || eventName === 'resizemove') {
        this.placeholder.i = id
        this.placeholder.x = x
        this.placeholder.y = y
        this.placeholder.w = l.w
        this.placeholder.h = l.h
        this.placeholder.HPosition = l.HPosition
        this.$nextTick(function() {
          this.isDragging = true
        })
        //this.$broadcast("updateWidth", this.width);
        // this.eventBus.$emit('updateHeight' + this.formId, this.height)
        this.eventBus.$emit('updateWidth', this.width)
      } else {
        this.$nextTick(function() {
          this.isDragging = false
        })
      }

      if (this.responsive) this.responsiveGridLayout()

      compact(this.layout, this.verticalCompact, this)
      this.eventBus.$emit('compact')
      this.updateHeight()

      if (eventName === 'resizeend') this.$emit('layout-updated', this.layout)
    },

    // finds or generates new layouts for set breakpoints
    responsiveGridLayout() {
      let newBreakpoint = getBreakpointFromWidth(this.breakpoints, this.width)
      let newCols = getColsFromBreakpoint(newBreakpoint, this.cols)

      // save actual layout in layouts
      if (this.lastBreakpoint != null && !this.layouts[this.lastBreakpoint])
        this.layouts[this.lastBreakpoint] = cloneLayout(this.layout)

      // Find or generate a new layout.
      let layout = findOrGenerateResponsiveLayout(
        this.originalLayout,
        this.layouts,
        this.breakpoints,
        newBreakpoint,
        this.lastBreakpoint,
        newCols,
        this.verticalCompact
      )

      // Store the new layout.
      this.layouts[newBreakpoint] = layout

      if (this.lastBreakpoint !== newBreakpoint) {
        this.$emit('breakpoint-changed', newBreakpoint, layout)
      }
      this.$emit('update:layout', layout)
      this.lastBreakpoint = newBreakpoint
      this.eventBus.$emit(
        'setColNum',
        getColsFromBreakpoint(newBreakpoint, this.cols)
      )
    },

    // clear all responsive layouts
    initResponsiveFeatures() {
      // clear layouts
      this.layouts = Object.assign({}, this.responsiveLayouts)
    },

    // find difference in layouts
    findDifference(layout, originalLayout) {
      //Find values that are in result1 but not in result2
      let uniqueResultOne = layout.filter(function(obj) {
        return !originalLayout.some(function(obj2) {
          return obj.i === obj2.i
        })
      })

      //Find values that are in result2 but not in result1
      let uniqueResultTwo = originalLayout.filter(function(obj) {
        return !layout.some(function(obj2) {
          return obj.i === obj2.i
        })
      })

      //Combine the two arrays of unique entries#
      return uniqueResultOne.concat(uniqueResultTwo)
    }
  }
}
</script>
