import Fill from '../../../modules/Fill'
import Graphics from '../../../modules/Graphics'
import Series from '../../../modules/Series'

export default class Helpers {
  constructor(barCtx) {
    this.w = barCtx.w
    this.barCtx = barCtx
  }

  initVariables(series) {
    const w = this.w
    this.barCtx.series = series
    this.barCtx.totalItems = 0
    this.barCtx.seriesLen = 0
    this.barCtx.visibleI = -1 // visible Series
    this.barCtx.visibleItems = 1 // number of visible bars after user zoomed in/out

    for (let sl = 0; sl < series.length; sl++) {
      if (series[sl].length > 0) {
        this.barCtx.seriesLen = this.barCtx.seriesLen + 1
        this.barCtx.totalItems += series[sl].length
      }
      if (w.globals.isXNumeric) {
        // get max visible items
        for (let j = 0; j < series[sl].length; j++) {
          if (
            w.globals.seriesX[sl][j] > w.globals.minX &&
            w.globals.seriesX[sl][j] < w.globals.maxX
          ) {
            this.barCtx.visibleItems++
          }
        }
      } else {
        this.barCtx.visibleItems = w.globals.dataPoints
      }
    }

    if (this.barCtx.seriesLen === 0) {
      // A small adjustment when combo charts are used
      this.barCtx.seriesLen = 1
    }
    this.barCtx.zeroSerieses = []
    this.barCtx.radiusOnSeriesNumber = series.length - 1 // which series to draw ending shape on

    if (!w.globals.comboCharts) {
      this.checkZeroSeries({ series })
    }
  }

  initialPositions() {
    let w = this.w
    let x, y, yDivision, xDivision, barHeight, barWidth, zeroH, zeroW

    let dataPoints = w.globals.dataPoints
    if (this.barCtx.isRangeBar) {
      // timeline rangebar chart
      dataPoints = w.globals.labels.length
    }

    let seriesLen = this.barCtx.seriesLen
    if (w.config.plotOptions.bar.rangeBarGroupRows) {
      seriesLen = 1
    }

    if (this.barCtx.isHorizontal) {
      // height divided into equal parts
      yDivision = w.globals.gridHeight / dataPoints
      barHeight = yDivision / seriesLen

      if (w.globals.isXNumeric) {
        yDivision = w.globals.gridHeight / this.barCtx.totalItems
        barHeight = yDivision / this.barCtx.seriesLen
      }

      barHeight =
        (barHeight * parseInt(this.barCtx.barOptions.barHeight, 10)) / 100

      zeroW =
        this.barCtx.baseLineInvertedY +
        w.globals.padHorizontal +
        (this.barCtx.isReversed ? w.globals.gridWidth : 0) -
        (this.barCtx.isReversed ? this.barCtx.baseLineInvertedY * 2 : 0)

      y = (yDivision - barHeight * this.barCtx.seriesLen) / 2
    } else {
      // width divided into equal parts
      xDivision = w.globals.gridWidth / this.barCtx.visibleItems
      if (w.config.xaxis.convertedCatToNumeric) {
        xDivision = w.globals.gridWidth / w.globals.dataPoints
      }
      barWidth =
        ((xDivision / this.barCtx.seriesLen) *
          parseInt(this.barCtx.barOptions.columnWidth, 10)) /
        100

      if (w.globals.isXNumeric) {
        // max barwidth should be equal to minXDiff to avoid overlap
        let xRatio = this.barCtx.xRatio
        if (w.config.xaxis.convertedCatToNumeric) {
          xRatio = this.barCtx.initialXRatio
        }
        if (
          w.globals.minXDiff &&
          w.globals.minXDiff !== 0.5 &&
          w.globals.minXDiff / xRatio > 0
        ) {
          xDivision = w.globals.minXDiff / xRatio
        }

        barWidth =
          ((xDivision / this.barCtx.seriesLen) *
            parseInt(this.barCtx.barOptions.columnWidth, 10)) /
          100

        if (barWidth < 1) {
          barWidth = 1
        }
      }

      zeroH =
        w.globals.gridHeight -
        this.barCtx.baseLineY[this.barCtx.yaxisIndex] -
        (this.barCtx.isReversed ? w.globals.gridHeight : 0) +
        (this.barCtx.isReversed
          ? this.barCtx.baseLineY[this.barCtx.yaxisIndex] * 2
          : 0)

      x =
        w.globals.padHorizontal +
        (xDivision - barWidth * this.barCtx.seriesLen) / 2
    }

    return {
      x,
      y,
      yDivision,
      xDivision,
      barHeight,
      barWidth,
      zeroH,
      zeroW
    }
  }

  getPathFillColor(series, i, j, realIndex) {
    const w = this.w
    let fill = new Fill(this.barCtx.ctx)

    let fillColor = null
    let seriesNumber = this.barCtx.barOptions.distributed ? j : i

    if (this.barCtx.barOptions.colors.ranges.length > 0) {
      const colorRange = this.barCtx.barOptions.colors.ranges
      colorRange.map((range) => {
        if (series[i][j] >= range.from && series[i][j] <= range.to) {
          fillColor = range.color
        }
      })
    }

    if (w.config.series[i].data[j] && w.config.series[i].data[j].fillColor) {
      fillColor = w.config.series[i].data[j].fillColor
    }

    let pathFill = fill.fillPath({
      seriesNumber: this.barCtx.barOptions.distributed
        ? seriesNumber
        : realIndex,
      dataPointIndex: j,
      color: fillColor,
      value: series[i][j]
    })

    return pathFill
  }

  getStrokeWidth(i, j, realIndex) {
    let strokeWidth = 0
    const w = this.w

    if (
      typeof this.barCtx.series[i][j] === 'undefined' ||
      this.barCtx.series[i][j] === null
    ) {
      this.barCtx.isNullValue = true
    } else {
      this.barCtx.isNullValue = false
    }
    if (w.config.stroke.show) {
      if (!this.barCtx.isNullValue) {
        strokeWidth = Array.isArray(this.barCtx.strokeWidth)
          ? this.barCtx.strokeWidth[realIndex]
          : this.barCtx.strokeWidth
      }
    }
    return strokeWidth
  }

  barBackground({ j, i, x1, x2, y1, y2, elSeries }) {
    const w = this.w
    const graphics = new Graphics(this.barCtx.ctx)

    const sr = new Series(this.barCtx.ctx)
    let activeSeriesIndex = sr.getActiveConfigSeriesIndex()

    if (
      this.barCtx.barOptions.colors.backgroundBarColors.length > 0 &&
      activeSeriesIndex === i
    ) {
      if (j >= this.barCtx.barOptions.colors.backgroundBarColors.length) {
        j %= this.barCtx.barOptions.colors.backgroundBarColors.length
      }

      let bcolor = this.barCtx.barOptions.colors.backgroundBarColors[j]
      let rect = graphics.drawRect(
        typeof x1 !== 'undefined' ? x1 : 0,
        typeof y1 !== 'undefined' ? y1 : 0,
        typeof x2 !== 'undefined' ? x2 : w.globals.gridWidth,
        typeof y2 !== 'undefined' ? y2 : w.globals.gridHeight,
        this.barCtx.barOptions.colors.backgroundBarRadius,
        bcolor,
        this.barCtx.barOptions.colors.backgroundBarOpacity
      )
      elSeries.add(rect)
      rect.node.classList.add('apexcharts-backgroundBar')
    }
  }

  getColumnPaths({
    barWidth,
    barXPosition,
    yRatio,
    y1,
    y2,
    strokeWidth,
    series,
    realIndex,
    i,
    j,
    w
  }) {
    const graphics = new Graphics(this.barCtx.ctx)
    strokeWidth = Array.isArray(strokeWidth)
      ? strokeWidth[realIndex]
      : strokeWidth
    if (!strokeWidth) strokeWidth = 0

    let shapeOpts = {
      barWidth,
      strokeWidth,
      yRatio,
      barXPosition,
      y1,
      y2
    }
    let newPath = this.getRoundedBars(w, shapeOpts, series, i, j)

    const x1 = barXPosition
    const x2 = barXPosition + barWidth

    let pathTo = graphics.move(x1, y1)
    let pathFrom = graphics.move(x1, y1)

    const sl = graphics.line(x2 - strokeWidth, y1)
    if (w.globals.previousPaths.length > 0) {
      pathFrom = this.barCtx.getPreviousPath(realIndex, j, false)
    }

    pathTo =
      pathTo +
      graphics.line(x1, newPath.y2) +
      newPath.pathWithRadius +
      graphics.line(x2 - strokeWidth, newPath.y2) +
      sl +
      sl +
      'z'

    // the lines in pathFrom are repeated to equal it to the points of pathTo
    // this is to avoid weird animation (bug in svg.js)
    pathFrom =
      pathFrom +
      graphics.line(x1, y1) +
      sl +
      sl +
      sl +
      sl +
      sl +
      graphics.line(x1, y1)

    if (w.config.chart.stacked) {
      this.barCtx.yArrj.push(newPath.y2)
      this.barCtx.yArrjF.push(Math.abs(y1 - newPath.y2))
      this.barCtx.yArrjVal.push(this.barCtx.series[i][j])
    }

    return {
      pathTo,
      pathFrom
    }
  }

  getBarpaths({
    barYPosition,
    barHeight,
    x1,
    x2,
    strokeWidth,
    series,
    realIndex,
    i,
    j,
    w
  }) {
    const graphics = new Graphics(this.barCtx.ctx)
    strokeWidth = Array.isArray(strokeWidth)
      ? strokeWidth[realIndex]
      : strokeWidth
    if (!strokeWidth) strokeWidth = 0

    let shapeOpts = {
      barHeight,
      strokeWidth,
      barYPosition,
      x2,
      x1
    }

    let newPath = this.getRoundedBars(w, shapeOpts, series, i, j)

    let pathTo = graphics.move(x1, barYPosition)
    let pathFrom = graphics.move(x1, barYPosition)

    if (w.globals.previousPaths.length > 0) {
      pathFrom = this.barCtx.getPreviousPath(realIndex, j, false)
    }

    const y1 = barYPosition
    const y2 = barYPosition + barHeight

    const sl = graphics.line(x1, y2 - strokeWidth)
    pathTo =
      pathTo +
      graphics.line(newPath.x2, y1) +
      newPath.pathWithRadius +
      graphics.line(newPath.x2, y2 - strokeWidth) +
      sl +
      sl +
      'z'

    pathFrom =
      pathFrom +
      graphics.line(x1, y1) +
      sl +
      sl +
      sl +
      sl +
      sl +
      graphics.line(x1, y1)

    if (w.config.chart.stacked) {
      this.barCtx.xArrj.push(newPath.x2)
      this.barCtx.xArrjF.push(Math.abs(x1 - newPath.x2))
      this.barCtx.xArrjVal.push(this.barCtx.series[i][j])
    }
    return {
      pathTo,
      pathFrom
    }
  }

  /** getRoundedBars draws border radius for bars/columns
   * @memberof Bar
   * @param {object} w - chart context
   * @param {object} opts - consists several properties like barHeight/barWidth
   * @param {array} series - global primary series
   * @param {int} i - current iterating series's index
   * @param {int} j - series's j of i
   * @return {object} pathWithRadius - ending shape path string
   *         newY/newX - which is calculated from existing x/y based on rounded border
   **/
  getRoundedBars(w, opts, series, i, j) {
    let graphics = new Graphics(this.barCtx.ctx)
    let radius = 0

    const borderRadius = w.config.plotOptions.bar.borderRadius
    const borderRadiusIsArray = Array.isArray(borderRadius)
    if (borderRadiusIsArray) {
      const radiusIndex =
        i > borderRadius.length - 1 ? borderRadius.length - 1 : i
      radius = borderRadius[radiusIndex]
    } else {
      radius = borderRadius
    }

    if (
      w.config.chart.stacked &&
      series.length > 1 &&
      i !== this.barCtx.radiusOnSeriesNumber &&
      !borderRadiusIsArray
    ) {
      radius = 0
    }

    if (this.barCtx.isHorizontal) {
      let pathWithRadius = ''
      let x2 = opts.x2

      if (Math.abs(opts.x1 - opts.x2) < radius) {
        radius = Math.abs(opts.x1 - opts.x2)
      }

      if (typeof series[i][j] !== 'undefined' || series[i][j] !== null) {
        let inverse = this.barCtx.isReversed
          ? series[i][j] > 0
          : series[i][j] < 0

        if (inverse) radius = radius * -1

        x2 = x2 - radius

        pathWithRadius =
          graphics.quadraticCurve(
            x2 + radius,
            opts.barYPosition,
            x2 + radius,
            opts.barYPosition + (!inverse ? radius : radius * -1)
          ) +
          graphics.line(
            x2 + radius,
            opts.barYPosition +
              opts.barHeight -
              opts.strokeWidth -
              (!inverse ? radius : radius * -1)
          ) +
          graphics.quadraticCurve(
            x2 + radius,
            opts.barYPosition + opts.barHeight - opts.strokeWidth,
            x2,
            opts.barYPosition + opts.barHeight - opts.strokeWidth
          )
      }

      return {
        pathWithRadius,
        x2
      }
    } else {
      let pathWithRadius = ''
      let y2 = opts.y2

      if (Math.abs(opts.y1 - opts.y2) < radius) {
        radius = Math.abs(opts.y1 - opts.y2)
      }

      if (typeof series[i][j] !== 'undefined' || series[i][j] !== null) {
        let inverse = series[i][j] < 0

        if (inverse) radius = radius * -1

        y2 = y2 + radius

        pathWithRadius =
          graphics.quadraticCurve(
            opts.barXPosition,
            y2 - radius,
            opts.barXPosition + (!inverse ? radius : radius * -1),
            y2 - radius
          ) +
          graphics.line(
            opts.barXPosition +
              opts.barWidth -
              opts.strokeWidth -
              (!inverse ? radius : radius * -1),
            y2 - radius
          ) +
          graphics.quadraticCurve(
            opts.barXPosition + opts.barWidth - opts.strokeWidth,
            y2 - radius,
            opts.barXPosition + opts.barWidth - opts.strokeWidth,
            y2
          )
      }

      return {
        pathWithRadius,
        y2
      }
    }
  }

  checkZeroSeries({ series }) {
    let w = this.w
    for (let zs = 0; zs < series.length; zs++) {
      let total = 0
      for (
        let zsj = 0;
        zsj < series[w.globals.maxValsInArrayIndex].length;
        zsj++
      ) {
        total += series[zs][zsj]
      }
      if (total === 0) {
        this.barCtx.zeroSerieses.push(zs)
      }
    }

    // After getting all zeroserieses, we need to ensure whether radiusOnSeriesNumber is not in that zeroseries array
    for (let s = series.length - 1; s >= 0; s--) {
      if (
        this.barCtx.zeroSerieses.indexOf(s) > -1 &&
        s === this.radiusOnSeriesNumber
      ) {
        this.barCtx.radiusOnSeriesNumber -= 1
      }
    }

    for (let s = series.length - 1; s >= 0; s--) {
      if (
        w.globals.collapsedSeriesIndices.indexOf(
          this.barCtx.radiusOnSeriesNumber
        ) > -1
      ) {
        this.barCtx.radiusOnSeriesNumber -= 1
      }
    }
  }

  getXForValue(value, zeroW, zeroPositionForNull = true) {
    let xForVal = zeroPositionForNull ? zeroW : null
    if (typeof value !== 'undefined' && value !== null) {
      xForVal =
        zeroW +
        value / this.barCtx.invertedYRatio -
        (this.barCtx.isReversed ? value / this.barCtx.invertedYRatio : 0) * 2
    }
    return xForVal
  }

  getYForValue(value, zeroH, zeroPositionForNull = true) {
    let yForVal = zeroPositionForNull ? zeroH : null
    if (typeof value !== 'undefined' && value !== null) {
      yForVal =
        zeroH -
        value / this.barCtx.yRatio[this.barCtx.yaxisIndex] +
        (this.barCtx.isReversed
          ? value / this.barCtx.yRatio[this.barCtx.yaxisIndex]
          : 0) *
          2
    }
    return yForVal
  }

  getGoalValues(type, zeroW, zeroH, i, j) {
    const w = this.w

    let goals = []
    if (
      w.globals.seriesGoals[i] &&
      w.globals.seriesGoals[i][j] &&
      Array.isArray(w.globals.seriesGoals[i][j])
    ) {
      w.globals.seriesGoals[i][j].forEach((goal) => {
        goals.push({
          [type]:
            type === 'x'
              ? this.getXForValue(goal.value, zeroW, false)
              : this.getYForValue(goal.value, zeroH, false),
          attrs: goal
        })
      })
    }
    return goals
  }

  drawGoalLine({
    barXPosition,
    barYPosition,
    goalX,
    goalY,
    barWidth,
    barHeight
  }) {
    let graphics = new Graphics(this.barCtx.ctx)
    const lineGroup = graphics.group({
      className: 'apexcharts-bar-goals-groups'
    })

    let line = null
    if (this.barCtx.isHorizontal) {
      if (Array.isArray(goalX)) {
        goalX.forEach((goal) => {
          let sHeight =
            typeof goal.attrs.strokeHeight !== 'undefined'
              ? goal.attrs.strokeHeight
              : barHeight / 2
          let y = barYPosition + sHeight + barHeight / 2

          line = graphics.drawLine(
            goal.x,
            y - sHeight * 2,
            goal.x,
            y,
            goal.attrs.strokeColor ? goal.attrs.strokeColor : undefined,
            goal.attrs.strokeDashArray,
            goal.attrs.strokeWidth ? goal.attrs.strokeWidth : 2,
            goal.attrs.strokeLineCap
          )
          lineGroup.add(line)
        })
      }
    } else {
      if (Array.isArray(goalY)) {
        goalY.forEach((goal) => {
          let sWidth =
            typeof goal.attrs.strokeWidth !== 'undefined'
              ? goal.attrs.strokeWidth
              : barWidth / 2
          let x = barXPosition + sWidth + barWidth / 2

          line = graphics.drawLine(
            x - sWidth * 2,
            goal.y,
            x,
            goal.y,
            goal.attrs.strokeColor ? goal.attrs.strokeColor : undefined,
            goal.attrs.strokeDashArray,
            goal.attrs.strokeHeight ? goal.attrs.strokeHeight : 2,
            goal.attrs.strokeLineCap
          )
          lineGroup.add(line)
        })
      }
    }

    return lineGroup
  }
}
