import supportDom from '../decorators/supportDom' import chartCommon from '../decorators/chartCommon' import isFn from '../utils/isFn' import isDef from '../utils/isDef' import isUndef from '../utils/isUndef' import { throttle } from '../utils' import { DEFAULT_CHART_STYLES } from '../consts'

@supportDom @chartCommon export default class PieChart {

constructor(dom, options = {}) {
  this.dom = dom
  this.data = []
  this.total = 0

  this.options = options
  this.labelVisible = isDef(options.labelVisible) ? options.labelVisible : true
  this.height = options.height
  this.width = options.width
  this.padding = isDef(options.padding) ? options.padding : 30
  this.styles = options.styles || DEFAULT_CHART_STYLES
  this.bg = options.bg || '#fff'

  this.init()
}

init() {
  this.setDpr()
  this.setDomSizeIfNeeded()
  this.setCanvas()
  this.setLabelBox()
  this.clear()
  this.bindMedia()
  this.bindPointMouseOver()
}

get x() {
  return this.width / 2
}

get y() {
  return this.height / 2
}

get radius() {
  return this.contentWidth / 2
}

get pieWidth() {
  return this.radius * .3
}

get centerCircleRadius() {
  return this.radius - this.pieWidth
}

get contentWidth() {
  return this.width - (this.padding * 2)
}

get contentHeight() {
  return this.height - (this.padding * 2)
}

bindPointMouseOver() {
  if (isUndef(this.options.onPieMouseOver)) {
    return
  }
  if (! ('onmousemove' in this.canvas)) {
    return
  }
  this.addLayer()
  const canvas = this.getHighestCanvas()
  this.addEvent(canvas, 'mousemove', throttle(this.handleMouseMove.bind(this), 30))
}

draw() {
  this.clear()
  this.drawPie()
}

drawPie() {
  const { x, y, radius, centerCircleRadius, ctx, total } = this

  let distance = 0

  this.data.forEach((row, i) => {

    const ratio = (row.value / total)
    const startAngle = Math.PI * (-.5 + 2 * distance)
    const endAngle = Math.PI * (-.5 + 2 * (distance + ratio))

    const options = {
      style: this.styles[i]
    }
    this.fillArc(ctx, x, y, radius, startAngle, endAngle, options)
    distance += ratio
  })

  this.fillCircle(ctx, x, y, centerCircleRadius, '#fff')
}

handleDprChange() {
  this.setDpr()
  this.refresh()
}

getPosAngle(x1, y1, x2, y2) {

  let x = x2
  let y = y2

  if (x1 >= 0) {
    x -= x1
  }
  if (y1 >= 0) {
    y -= y1
  }
  if (x1 < 0) {
    x += x1
  }
  if (y2 < 0) {
    y += y2
  }

  let angle = Math.atan2(y, x) * 180 / Math.PI
  if (angle < 0) {
    angle = 180 + (180 + angle)
  }
  return (angle + 90) % 360
}

handleMouseMove(event) {

  const { x, y } = this
  const canvasMousePos = this.getMousePosInCanvas(event)
  const mousePos = this.getMousePos(canvasMousePos)
  const mouseX = canvasMousePos.x
  const mouseY = canvasMousePos.y

  const distanceToCenterPoint = Math.sqrt(Math.pow(mouseX - x, 2) +
    Math.pow(mouseY - y, 2))

  const inCenterCircle = distanceToCenterPoint <= this.centerCircleRadius

  this.clearSliceGlow()

  if (inCenterCircle) {
    return this.options.onPieMouseOver(mousePos, null)
  }

  const inPieCircle = distanceToCenterPoint <= this.radius
  if (! inPieCircle) {
    return this.options.onPieMouseOver(mousePos, null)
  }
  const angle = this.getPosAngle(x, y, mouseX, mouseY)
  const matchedRow = this.data.find(row => {
    return (row.startAngle <= angle) && (angle <= row.endAngle)
  })
  if (matchedRow) {
    this.drawSliceGlow(matchedRow)
    this.options.onPieMouseOver(mousePos, matchedRow)
  }
}

drawSliceGlow(row) {
  const index = this.data.findIndex(r => r === row)
  this.clearSliceGlow()
  const { x, y, radius, centerCircleRadius } = this
  const ctx = this.firstLayer.canvas.getContext('2d')

  const delta = 90 * Math.PI / 180
  const startAngle = (row.startAngle * Math.PI / 180) - delta
  const endAngle = (row.endAngle * Math.PI / 180) - delta

  const options = {
    style: this.styles[index],
    alpha: .3
  }
  const radiusDelta = (radius - centerCircleRadius) * .3
  this.fillArc(ctx, x, y, radius + radiusDelta, startAngle, endAngle, options)
  this.fillCircle(this.firstLayer.ctx, x, y, centerCircleRadius, '#fff')
}

clearSliceGlow() {
  const ctx = this.firstLayer.canvas.getContext('2d')
  ctx.clearRect(0, 0, this.width, this.height)
}

refresh() {
  this.raf(() => {
    this.clearCanvasSize(this.canvas)
    this.layers.forEach(layer => this.clearCanvasSize(layer.canvas))
    this.setDomSizeIfNeeded()
    this.setCanvasSize(this.canvas)
    this.layers.forEach(layer => this.setCanvasSize(layer.canvas))
    this.draw()
  })
}

setAngles(data) {
  const { total } = this
  let startAngle = 0
  return data.map(row => {
    const endAngle = startAngle + ((row.value / total) * 360)
    const nextRow = Object.assign({}, row, { startAngle, endAngle })
    startAngle = endAngle
    return nextRow
  })
}

handleLabelMouseOver(event, index) {
  const row = this.data[index]
  this.drawSliceGlow(row)

  if (isFn(this.options.onLabelMouseOver)) {
    const canvasMousePos = this.getMousePosInCanvas(event)
    const mousePos = this.getMousePos(canvasMousePos)
    this.options.onLabelMouseOver(mousePos, row)
  }
}

handleLabelMouseLeave(event, index) {
  this.clearSliceGlow()

  if (isFn(this.options.onLabelMouseOver)) {
    const canvasMousePos = this.getMousePosInCanvas(event)
    const mousePos = this.getMousePos(canvasMousePos)
    this.options.onLabelMouseOver(mousePos)
  }
}

setData(arr) {
  const data = arr || []
  this.total = data.reduce((t, row) => t + row.value, 0)
  this.data = this.setAngles(data)
  this.raf(() => {
    if (this.labelVisible) {
      const labels = this.data.map(row => row.label)
      this.drawLabels(labels, this.styles)
    }
    this.draw()
  })
}

destroy() {
  const { dom, canvas } = this

  this.unbindMedia()
  this.removeAllLayers()

  if (dom.contains(canvas)) {
    dom.removeChild(canvas)
    dom.style.removeProperty('position')
  }
}

}