/* eslint-disable */
const PROPERTIES = ['hover-class', 'hover-start-time', 'space', 'src']
const COMPUTED_STYLE = [
  'color',
  'font-size',
  'font-weight',
  'font-family',
  'backgroundColor',
  'border',
  'border-radius',
  'box-sizing',
  'line-height',
]
const DEFAULT_BORDER = '0px none rgb(0, 0, 0)'
const DEFAULT_BORDER_RADIUS = '0px'

// default z-index??
const DEFAULT_RANK = {
  view: 0,
  image: 1,
  text: 2,
}

const drawWrapper = (context, data) => {
  const { backgroundColor, width, height } = data
  context.setFillStyle(backgroundColor)
  context.fillRect(0, 0, width, height)
}

// todo: do more for different language
const strLen = str => {
  let count = 0
  for (let i = 0, len = str.length; i < len; i++) {
    count += str.charCodeAt(i) < 256 ? 1 : 2
  }
  return count / 2
}

const isMuitlpleLine = (data, text) => {
  const { 'font-size': letterWidth, width } = data
  const length = strLen(text)
  const rowlineLength = length * parseInt(letterWidth, 10)
  return rowlineLength > width
}

const drawMutipleLine = (context, data, text) => {
  const {
    'font-size': letterWidth,
    width,
    left,
    top,
    'line-height': lineHeightAttr,
  } = data
  const lineHieght = lineHeightAttr === 'normal' ? Math.round(1.2 * letterWidth) : lineHeightAttr
  const rowLetterCount = Math.floor(width / parseInt(letterWidth, 10))
  const length = strLen(text)
  for (let i = 0; i < length; i += rowLetterCount) {
    const lineText = text.substring(i, i + rowLetterCount)
    const rowNumber = Math.floor(i / rowLetterCount)
    const rowTop = top + rowNumber * parseInt(lineHieght, 10)
    context.fillText(lineText, left, rowTop)
  }
}

// enable color, font, for now only support chinese
const drawText = (context, data) => {
  const {
    dataset: { text },
    left,
    top,
    color,
    'font-weight': fontWeight,
    'font-size': fontSize,
    'font-family': fontFamily,
  } = data
  const canvasText = Array.isArray(text) ? text[0] : text
  context.font = `${fontWeight} ${Math.round(
    parseFloat(fontSize),
  )}px ${fontFamily}`
  context.setFillStyle(color)
  if (isMuitlpleLine(data, canvasText)) {
    drawMutipleLine(context, data, canvasText)
  } else {
    context.fillText(canvasText, left, top)
  }
  context.restore()
}

const getImgInfo = src =>
  new Promise((resolve, reject) => {
    wx.getImageInfo({
      src,
      success(res) {
        resolve(res)
      },
    })
  })

const hasBorder = border => border !== DEFAULT_BORDER
const hasBorderRadius = borderRadius => borderRadius !== DEFAULT_BORDER_RADIUS

const getBorderAttributes = border => {
  let borderColor, borderStyle
  let borderWidth = 0

  if (hasBorder) {
    borderWidth = parseInt(border.split(/\s/)[0], 10)
    borderStyle = border.split(/\s/)[1]
    borderColor = border.match(/(rgb).*/gi)[0]
  }
  return {
    borderWidth,
    borderStyle,
    borderColor,
  }
}

const getImgRect = (imgData, borderWidth) => {
  const { width, height, left, top } = imgData
  const imgWidth = width - 2 * borderWidth
  const imgHeight = height - 2 * borderWidth
  const imgLeft = left + borderWidth
  const imgTop = top + borderWidth
  return {
    imgWidth,
    imgHeight,
    imgLeft,
    imgTop,
  }
}

const getArcCenterPosition = imgData => {
  const { width, height, left, top } = imgData
  const coordX = width / 2 + left
  const coordY = height / 2 + top
  return {
    coordX,
    coordY,
  }
}

const getArcRadius = (imgData, borderWidth = 0) => {
  const { width } = imgData
  return width / 2 - borderWidth / 2
}

const getCalculatedImagePosition = (imgData, naturalWidth, naturalHeight) => {
  const { border } = imgData
  const { borderWidth } = getBorderAttributes(border)
  const { imgWidth, imgHeight, imgLeft, imgTop } = getImgRect(
    imgData,
    borderWidth,
  )
  const ratio = naturalWidth / naturalHeight
  // tweak for real width and position => center center
  const realWidth = ratio > 0 ? imgWidth : imgHeight * ratio
  const realHeight = ratio > 0 ? imgWidth * (1 / ratio) : imgHeight
  const offsetLeft = ratio > 0 ? 0 : (imgWidth - realWidth) / 2
  const offsetTop = ratio > 0 ? (imgHeight - realHeight) / 2 : 0
  return {
    realWidth,
    realHeight,
    left: imgLeft + offsetLeft,
    top: imgTop + offsetTop,
  }
}

const drawArcImage = (context, imgData) => {
  const { src } = imgData
  const { coordX, coordY } = getArcCenterPosition(imgData)
  return getImgInfo(src).then(res => {
    const { width: naturalWidth, height: naturalHeight } = res
    const arcRadius = getArcRadius(imgData)
    context.save()
    context.beginPath()
    context.arc(coordX, coordY, arcRadius, 0, 2 * Math.PI)
    context.closePath()
    context.clip()
    const { left, top, realWidth, realHeight } = getCalculatedImagePosition(
      imgData,
      naturalWidth,
      naturalHeight,
    )
    context.drawImage(
      src,
      0,
      0,
      naturalWidth,
      naturalHeight,
      left,
      top,
      realWidth,
      realHeight,
    )
    context.restore()
  })
}

const drawRectImage = (context, imgData) => {
  const { src, width, height, left, top } = imgData

  return getImgInfo(src).then(res => {
    const { width: naturalWidth, height: naturalHeight } = res
    context.save()
    context.beginPath()
    context.rect(left, top, width, height)
    context.closePath()
    context.clip()

    const {
      left: realLeft,
      top: realTop,
      realWidth,
      realHeight,
    } = getCalculatedImagePosition(imgData, naturalWidth, naturalHeight)
    context.drawImage(
      src,
      0,
      0,
      naturalWidth,
      naturalHeight,
      realLeft,
      realTop,
      realWidth,
      realHeight,
    )
    context.restore()
  })
}

const drawArcBorder = (context, imgData) => {
  const { border } = imgData
  const { coordX, coordY } = getArcCenterPosition(imgData)
  const { borderWidth, borderColor } = getBorderAttributes(border)
  const arcRadius = getArcRadius(imgData, borderWidth)
  context.save()
  context.beginPath()
  context.setLineWidth(borderWidth)
  context.setStrokeStyle(borderColor)
  context.arc(coordX, coordY, arcRadius, 0, 2 * Math.PI)
  context.stroke()
  context.restore()
}

const drawRectBorder = (context, imgData) => {
  const { border } = imgData
  const { left, top, width, height } = imgData
  const { borderWidth, borderColor } = getBorderAttributes(border)

  const correctedBorderWidth = borderWidth + 1 // draw may cause empty 0.5 space
  context.save()
  context.beginPath()
  context.setLineWidth(correctedBorderWidth)
  context.setStrokeStyle(borderColor)

  context.rect(
    left + borderWidth / 2,
    top + borderWidth / 2,
    width - borderWidth,
    height - borderWidth,
  )
  context.stroke()
  context.restore()
}

// image, enable border-radius: 50%, border, bgColor
const drawImage = (context, imgData) => {
  const { border, 'border-radius': borderRadius } = imgData
  let drawImagePromise
  if (hasBorderRadius(borderRadius)) {
    drawImagePromise = drawArcImage(context, imgData)
  } else {
    drawImagePromise = drawRectImage(context, imgData)
  }

  return drawImagePromise.then(() => {
    if (hasBorder(border)) {
      if (hasBorderRadius(borderRadius)) {
        return drawArcBorder(context, imgData)
      } else {
        return drawRectBorder(context, imgData)
      }
    }
    return Promise.resolve()
  })
}

// e.g. 10%, 4px
const getBorderRadius = imgData => {
  const { width, height, 'border-radius': borderRadiusAttr } = imgData
  const borderRadius = parseInt(borderRadiusAttr, 10)
  if (borderRadiusAttr.indexOf('%') !== -1) {
    const borderRadiusX = parseInt(borderRadius / 100 * width, 10)
    const borderRadiusY = parseInt(borderRadius / 100 * height, 10)
    return {
      isCircle: borderRadiusX === borderRadiusY,
      borderRadius: borderRadiusX,
      borderRadiusX,
      borderRadiusY,
    }
  } else {
    return {
      isCircle: true,
      borderRadius,
    }
  }
}

const drawViewArcBorder = (context, imgData) => {
  const { width, height, left, top, backgroundColor, border } = imgData
  const { borderRadius } = getBorderRadius(imgData)
  const { borderWidth, borderColor } = getBorderAttributes(border)
  // console.log('🐞-imgData', imgData)
  context.beginPath()
  context.moveTo(left + borderRadius, top)
  context.lineTo(left + width - borderRadius, top)
  context.arcTo(
    left + width,
    top,
    left + width,
    top + borderRadius,
    borderRadius,
  )
  context.lineTo(left + width, top + height - borderRadius)
  context.arcTo(
    left + width,
    top + height,
    left + width - borderRadius,
    top + height,
    borderRadius,
  )
  context.lineTo(left + borderRadius, top + height)
  context.arcTo(
    left,
    top + height,
    left,
    top + height - borderRadius,
    borderRadius,
  )
  context.lineTo(left, top + borderRadius)
  context.arcTo(left, top, left + borderRadius, top, borderRadius)
  context.closePath()
  if (backgroundColor) {
    context.setFillStyle(backgroundColor)
    context.fill()
  }
  if (borderColor && borderWidth) {
    context.setLineWidth(borderWidth)
    context.setStrokeStyle(borderColor)
    context.stroke()
  }
}

const drawViewBezierBorder = (context, imgData) => {
  const { width, height, left, top, backgroundColor, border } = imgData
  const { borderWidth, borderColor } = getBorderAttributes(border)
  const { borderRadiusX, borderRadiusY } = getBorderRadius(imgData)
  context.beginPath()
  context.moveTo(left + borderRadiusX, top)
  context.lineTo(left + width - borderRadiusX, top)
  context.quadraticCurveTo(left + width, top, left + width, top + borderRadiusY)
  context.lineTo(left + width, top + height - borderRadiusY)
  context.quadraticCurveTo(
    left + width,
    top + height,
    left + width - borderRadiusX,
    top + height,
  )
  context.lineTo(left + borderRadiusX, top + height)
  context.quadraticCurveTo(
    left,
    top + height,
    left,
    top + height - borderRadiusY,
  )
  context.lineTo(left, top + borderRadiusY)
  context.quadraticCurveTo(left, top, left + borderRadiusX, top)
  context.closePath()
  if (backgroundColor) {
    context.setFillStyle(backgroundColor)
    context.fill()
  }
  if (borderColor && borderWidth) {
    context.setLineWidth(borderWidth)
    context.setStrokeStyle(borderColor)
    context.stroke()
  }
}

// enable border, border-radius, bgColor, position
const drawView = (context, imgData) => {
  const { isCircle } = getBorderRadius(imgData)
  if (isCircle) {
    drawViewArcBorder(context, imgData)
  } else {
    drawViewBezierBorder(context, imgData)
  }
}

const isTextElement = item => {
  const { dataset: { text }, type } = item
  return Boolean(text) || type === 'text'
}

const isImageElement = item => {
  const { src, type } = item
  return Boolean(src) || type === 'image'
}

const isViewElement = item => {
  const { type } = item
  return type === 'view'
}

const formatElementData = elements =>
  elements.map(element => {
    if (isTextElement(element)) {
      element.type = 'text'
      element.rank = DEFAULT_RANK.text
    } else if (isImageElement(element)) {
      element.type = 'image'
      element.rank = DEFAULT_RANK.image
    } else {
      element.type = 'view'
      element.rank = DEFAULT_RANK.view
    }
    return element
  })

// todo: use z-index as order to draw??
const getSortedElementsData = elements =>
  elements.sort((a, b) => {
    if (a.rank < b.rank) {
      return -1
    } else if (a.rank > b.rank) {
      return 1
    }
    return 0
  })

const drawElements = (context, storeItems) => {
  const itemPromise = []
  storeItems.forEach(item => {
    if (isTextElement(item)) {
      const text = drawText(context, item)
      itemPromise.push(text)
    } else if (isImageElement(item)) {
      const image = drawImage(context, item)
      itemPromise.push(image)
    } else {
      const view = drawView(context, item)
      itemPromise.push(view)
    }
  })
  return itemPromise
}

// storeObject: { 0: [...], 1: [...] }
// chain call promise based on Object key
const drawElementBaseOnIndex = (context, storeObject, key = 0, drawPromise) => {
  if (typeof drawPromise === 'undefined') {
    drawPromise = Promise.resolve()
  }
  const objectKey = key // note: key is changing when execute promise then
  const chainPromise = drawPromise.then(() => {
    const nextPromise = storeObject[objectKey]
      ? Promise.all(drawElements(context, storeObject[objectKey]))
      : Promise.resolve()
    return nextPromise
  })

  if (key >= Object.keys(storeObject).length) {
    return chainPromise
  } else {
    return drawElementBaseOnIndex(context, storeObject, key + 1, chainPromise)
  }
}

const drawCanvas = (canvasId, wrapperData, innerData) => {
  const context = wx.createCanvasContext(canvasId)
  context.setTextBaseline('top')

  // todo: use this after weixin fix stupid clip can't work bug in fillRect
  // for now, just set canvas background as a compromise
  drawWrapper(context, wrapperData[0])

  const storeObject = {}

  const sortedElementData = getSortedElementsData(formatElementData(innerData)) // fake z-index

  sortedElementData.forEach(item => {
    if (!storeObject[item.rank]) {
      // initialize
      storeObject[item.rank] = []
    }
    if (isTextElement(item) || isImageElement(item) || isViewElement(item)) {
      storeObject[item.rank].push(item)
    }
  })
  // note: draw is async
  return drawElementBaseOnIndex(context, storeObject).then(
    () =>
      new Promise((resolve, reject) => {
        context.draw(true, () => {
          resolve()
        })
      }),
  )
}

const wxSelectorQuery = element =>
  new Promise((resolve, reject) => {
    try {
      wx
        .createSelectorQuery()
        .selectAll(element)
        .fields(
          {
            dataset: true,
            size: true,
            rect: true,
            properties: PROPERTIES,
            computedStyle: COMPUTED_STYLE,
          },
          res => {
            resolve(res)
          },
        )
        .exec()
    } catch (error) {
      reject(error)
    }
  })

const wxml2canvas = (wrapperId, elementsClass, canvasId) => {
  const getWrapperElement = wxSelectorQuery(wrapperId)
  const getInnerElements = wxSelectorQuery(elementsClass)

  return Promise.all([getWrapperElement, getInnerElements]).then(data => {
    return drawCanvas(canvasId, data[0], data[1])
  })
}

// export default wxml2canvas
module.exports = wxml2canvas