<template>
  <div class="vf" :style="{height: chartHeight + 'px'}">
    <navigate
      :show-slider="showSlider"
      :scale.sync="scale"
      :scalable="scalable"
      @move-right="handleMoveRight"
      @move-left="chartOffsetLeft += 200"
      @back-home="chartOffsetLeft = 0;">
    </navigate>
    <div class="vf-chart" :style="canvasStyle" v-el:chart>
      <div class="dimensions"><slot></slot></div>
      <connector-canvas
        :paths="allPaths"
        :outflows="allOutflows">
      </connector-canvas>
      <flow-section
        v-for="item in data"
        :class="{ 'vf-origin-section': $index === 0 }"
        :data="item"
        :index="$index"
        :total-value="totalValue"
        :relative-total-height="relativeTotalHeight"
        :scale="scale"
        @remove="changeVisibleSectionCount($index)"
        :visible="$index < visibleSectionCount"
        v-ref:sections>
        <div class="btn-add-section"
          v-if="$index === visibleSectionCount - 1"
          v-show="visibleSectionCount < data.length"
          @click="addVisibleSection()"
        >
          <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="80" height="80" viewBox="0 0 80 80">
            <path d="M 0 20 L 40 20 40 0 80 40 40 80 40 60 0 60 Z" stroke="#dddddd" stroke-opacity="1" stroke-width="1" fill="#f0f0f0" fill-opacity="0.5" stroke-dasharray="3 3" style="cursor: pointer;"></path>
          </svg>
          <span>+ 步骤</span>
        </div>
      </flow-section>
    </div>
  </div>
</template>

<style scoped>
  .btn-add-section {
    position: absolute;
    top: 50%;
    right: -120px;
    cursor: pointer;
    width: 80px;
    margin-top: -52px;
    text-align: center;
  }
  .btn-add-section span {
    color: rgb(0, 92, 156);
    font-size: 12px;
    display: block;
    margin-top: 10px;
    opacity: 0.5;
  }
  .vf {
    position: relative;
    padding-top: 65px;
    box-sizing: border-box;
    overflow-x: hidden;
    overflow-y: auto;
  }
  .vf-chart {
    margin-left: 100px;
    position: relative;
    cursor: grab;
    white-space: nowrap;
  }
  .dimensions {
    position: absolute;
    left: auto;
    top: -45px;
  }
</style>

<script type="text/ecmascript-6">
  import ConnectorCanvas from './connector-canvas.vue';
  import FlowSection from './flow-section.vue';
  import Navigate from './navigate.vue';
  import stable from 'stable';
  // import uuid from 'node-uuid';

  export default {
    props: {
      data: {
        type: Array,
        default() { return []; }
      },
      highlightData: {
        type: Array,
        default() { return []; },
        twoWay: true
      },
      chartHeight: {
        default() {
          return 600;
        }
      },
      scalable: {
        type: Boolean,
        default: true
      },
      showSlider: {
        type: Boolean,
        default: true
      }
    },

    components: {
      ConnectorCanvas,
      FlowSection,
      Navigate
    },

    computed: {
      allPaths() {
        return (this.paths || []).concat(this.highlightPaths);
      },

      allOutflows() {
        return (this.outflows || []).concat(this.highlightOutflows);
      },
      canvasStyle() {
        var width = 0;
        if (this.$refs.sections) {
          width = this.visibleSectionCount * 330 + 30;
        }

        return {
          transition: 'transform .15s',
          width: width + 'px',
          transform: `translate(${-this.chartOffsetLeft}px, 0)`
        };
      }
    },

    watch: {
      scale(value) {
        this.$nextTick(() => {
          this.outflows = [];
          this.highlightOutflows = [];
          this.paths = this.getPaths(this.outflows, this.data);
          this.highlightPaths = this.getPaths(this.highlightOutflows, this.highlightData, true);
        });
      },

      data: {
        immediate: true,
        handler(val) {
          this.$nextTick(() => {
            this.flowCache = {};
            this.outflowCache = {};
            this.outflows = [];
            this.paths = this.getPaths(this.outflows, val);
          });
          if (val.length) {
            let ret = 0;
            let section = this.data[1];
            section.forEach(block => {
              ret += block.value;
            });
            this.totalValue = ret;
            this.relativeTotalHeight = this.chartHeight - 65 - section.length * 35;
          }
        }
      },

      highlightData: {
        immediate: true,
        handler(val) {
          this.$nextTick(() => {
            this.highlightOutflows = [];
            let paths = this.paths;
            let outflows = this.outflows;
            let blocks = this.blocks;

            paths.forEach(path => {
              path.highlightBase = false;
              path.data.highlightValue = 0;
            });
            outflows.forEach(path => {
              path.highlightBase = false;
              path.data.highlightOutflow = 0;
            });
            blocks.forEach(block => {
              block.removeHighlightValue();
            });

            this.highlightPaths = this.getPaths(this.highlightOutflows, val, true);
          });
        }
      },

      visibleSectionCount(val) {
        this.$broadcast('interactive-change', val);
      }
    },

    methods: {
      /**
       * 生成 path 所需要的数据
       * @param  {Object[]}  outflows 流失流量数据
       * @param  {Object[]}  data 数据源
       * @param  {Boolean} highlight 是否高亮
       * @return {Object[]}
       */
      getPaths(outflows, data, highlight = false) {
        let paths = [];

        data.forEach((section, index) => {
          section.highlight = highlight;
          const result = this.handleSection(section, index, outflows);
          const nextSection = this.sections[index + 1];
          const to = section.to;

          paths = result ? paths.concat(result) : paths;

          if (!to || !nextSection) return;
        });

        return paths;
      },
      handleMoveRight() {
        this.chartOffsetLeft -= 200;
        this.chartOffsetLeft < 0 && (this.chartOffsetLeft = 0);
      },
      changeVisibleSectionCount(index) {
        this.visibleSectionCount = index;
        this.$nextTick(() => {
          this.chartOffsetLeft = this.$els.chart.clientWidth - this.$el.clientWidth + 100;
          this.chartOffsetLeft < 0 && (this.chartOffsetLeft = 0);
        });
      },
      handleSection(section, index, outflows) {
        let paths = [];
        const sectionComponent = this.sections[index];
        const nextSectionComponent = this.sections[index + 1];
        const minWidth = 1 / window.devicePixelRatio;
        const nextSectionFromTotal = {};

        if (!sectionComponent) return;

        section.forEach((block, blockIndex) => {
          const blockPosition = sectionComponent.getBlockPosition(block.path);
          const to = block.to;
          const baseBlock = sectionComponent.getBlock(block.path);

          // 传入所有流通block的高亮值
          if (section.highlight) {
            baseBlock.setHighlightValue(block.value);
          } else {
            this.blocks.push(baseBlock);
          }

          let toTotal = 0;
          if (to instanceof Array) {
            stable(to, (prev, cur) => prev.value < cur.value)
            .forEach((toItem, key) => {
              nextSectionFromTotal[toItem.path] = nextSectionFromTotal[toItem.path] || 0;

              const toBlockPosition = nextSectionComponent.getBlockPosition(toItem.path);
              const width = toItem.value / this.totalValue * this.relativeTotalHeight * this.scale;
              const value = width < minWidth ? minWidth : width;
              const x1 = blockPosition.left + blockPosition.width;
              const y1 = blockPosition.top + toTotal;
              const y2 = toBlockPosition.top + nextSectionFromTotal[toItem.path];

              nextSectionFromTotal[toItem.path] += width;

              var flow = {
                x1: x1,
                y1: y1,
                x2: x1 + 100,
                y2: y2,
                width: value,
                render: false,
                hidden: false,
                data: {
                  firstBlock: blockIndex === 0,
                  firstSection: index === 0,
                  from: block.path,
                  to: toItem.path,
                  value: toItem.value,
                  total: block.value,
                  sectionIndex: index,
                  highlight: !!section.highlight
                }
              };

              if (!section.highlight) {
                this.flowCache[index + '-' + flow.data.from + '-' + flow.data.to] = flow;
              } else {
                let baseFlow = this.flowCache[index + '-' + flow.data.from + '-' + flow.data.to];
                baseFlow.highlightBase = true;
                baseFlow.data.highlightValue = flow.data.value;
                flow.y1 = baseFlow.y1;
                flow.y2 = baseFlow.y2;
                flow.data.baseValue = baseFlow.data.value;
              }

              paths[key] = (paths[key] || []).concat(flow);

              toTotal += value;
              toTotal = toTotal > blockPosition.height ? blockPosition.height - 2 : toTotal;
            });
          }

          if (block.outflow) {
            let data = block;
            let width = block.outflow / this.totalValue * this.relativeTotalHeight * this.scale;

            data.sectionIndex = index;
            data.highlight = !!section.highlight;

            let outflow = {
              x1: blockPosition.left + blockPosition.width,
              y1: blockPosition.top + toTotal,
              render: false,
              hidden: false,
              width: width,
              data: block,
              highlight: !!section.highlight
            };

            if (!section.highlight) {
              this.outflowCache[data.sectionIndex + '-' + block.path] = outflow;
            } else {
              let baseOutflow = this.outflowCache[data.sectionIndex + '-' + block.path];
              baseOutflow.highlightBase = true;
              baseOutflow.data.highlightOutflow = outflow.data.outflow;
              outflow.data.baseOutflow = baseOutflow.data.outflow;
              outflow.y1 = baseOutflow.y1;
            }

            outflows.push(outflow);
          }
        });
        if (!paths.length) return paths;
        return paths.reduce((prev, cur) => prev.concat(cur));
      },
      addVisibleSection() {
        this.visibleSectionCount++;
        this.chartOffsetLeft += 350;
      },
      chartDraggableInit() {
        var chart = this.$els.chart;
        var startX;
        var startOffsetLeft;
        var origTransition;

        var onDragStart = event => {
          startX = event.clientX;
          startOffsetLeft = this.chartOffsetLeft;
          origTransition = chart.style.transition;
          chart.style.transition = '';
        };

        var onDragging = event => {
          event.preventDefault();

          var moveLength = startX - event.clientX;

          this.chartOffsetLeft = startOffsetLeft + moveLength;
          this.chartOffsetLeft < 0 && (this.chartOffsetLeft = 0);
        };

        var onDragEnd = () => {
          window.removeEventListener('mousemove', onDragging);
          chart.style.transition = origTransition;
        };

        chart.addEventListener('mousedown', function(event) {
          onDragStart(event);
          window.addEventListener('mousemove', onDragging);
          window.addEventListener('mouseup', onDragEnd);
        });
      }
    },

    ready() {
      this.chartDraggableInit();
      this.$broadcast('interactive-change', this.visibleSectionCount);
    },

    data() {
      return {
        outflows: [],
        highlightOutflows: [],
        sections: [],
        visibleSectionCount: 5,
        sectionGap: 100,
        paths: [],
        highlightPaths: [],
        chartOffsetLeft: 0,
        scale: 1,
        totalValue: 0,
        relativeTotalHeight: 0,
        outflowCache: {},
        flowCache: {},
        blocks: []
      };
    },

    events: {
      'reset'() {
        this.highlightData = [];
        this.$nextTick(_ => {
          this.$broadcast('reset-path');
        });
      },
      'select'() {
        this.$nextTick(_ => {
          this.$broadcast('blur-path');
        });
        return true;
      }
    }
  };
</script>
