import CoreUtils from '../modules/CoreUtils'
import Bar from './Bar'
import Fill from '../modules/Fill'
import Graphics from '../modules/Graphics'
import Utils from '../utils/Utils'

/**
 * ApexCharts BarStacked Class responsible for drawing both Stacked Columns and Bars.
 *
 * @module BarStacked
 * The whole calculation for stacked bar/column is different from normal bar/column,
 * hence it makes sense to derive a new class for it extending most of the props of Parent Bar
 **/

class BarStacked extends Bar {
  draw(series, seriesIndex) {
    let w = this.w
    this.graphics = new Graphics(this.ctx)
    this.fill = new Fill(this.ctx)
    this.bar = new Bar(this.ctx, this.xyRatios)

    const coreUtils = new CoreUtils(this.ctx, w)
    series = coreUtils.getLogSeries(series)
    this.yRatio = coreUtils.getLogYRatios(this.yRatio)

    this.initVariables(series)

    if (w.config.chart.stackType === '100%') {
      series = w.globals.seriesPercent.slice()
    }

    this.series = series

    this.totalItems = 0

    this.prevY = [] // y position on chart
    this.prevX = [] // x position on chart
    this.prevYF = [] // y position including shapes on chart
    this.prevXF = [] // x position including shapes on chart
    this.prevYVal = [] // y values (series[i][j]) in columns
    this.prevXVal = [] // x values (series[i][j]) in bars

    this.xArrj = [] // xj indicates x position on graph in bars
    this.xArrjF = [] // xjF indicates bar's x position + endingshape's positions in bars
    this.xArrjVal = [] // x val means the actual series's y values in horizontal/bars
    this.yArrj = [] // yj indicates y position on graph in columns
    this.yArrjF = [] // yjF indicates bar's y position + endingshape's positions in columns
    this.yArrjVal = [] // y val means the actual series's y values in columns

    for (let sl = 0; sl < series.length; sl++) {
      if (series[sl].length > 0) {
        this.totalItems += series[sl].length
      }
    }

    let ret = this.graphics.group({
      class: 'apexcharts-bar-series apexcharts-plot-series'
    })

    let x = 0
    let y = 0

    for (let i = 0, bc = 0; i < series.length; i++, bc++) {
      let pathTo, pathFrom
      let xDivision // xDivision is the GRIDWIDTH divided by number of datapoints (columns)
      let yDivision // yDivision is the GRIDHEIGHT divided by number of datapoints (bars)
      let zeroH // zeroH is the baseline where 0 meets y axis
      let zeroW // zeroW is the baseline where 0 meets x axis

      let xArrValues = []
      let yArrValues = []

      let realIndex = w.globals.comboCharts ? seriesIndex[i] : i

      if (this.yRatio.length > 1) {
        this.yaxisIndex = realIndex
      }

      this.isReversed =
        w.config.yaxis[this.yaxisIndex] &&
        w.config.yaxis[this.yaxisIndex].reversed

      // el to which series will be drawn
      let elSeries = this.graphics.group({
        class: `apexcharts-series`,
        seriesName: Utils.escapeString(w.globals.seriesNames[realIndex]),

        rel: i + 1,
        'data:realIndex': realIndex
      })

      // eldatalabels
      let elDataLabelsWrap = this.graphics.group({
        class: 'apexcharts-datalabels'
      })

      let strokeWidth = 0
      let barHeight = 0
      let barWidth = 0

      let initPositions = this.initialPositions(
        x,
        y,
        xDivision,
        yDivision,
        zeroH,
        zeroW
      )
      y = initPositions.y
      barHeight = initPositions.barHeight
      yDivision = initPositions.yDivision
      zeroW = initPositions.zeroW

      x = initPositions.x
      barWidth = initPositions.barWidth
      xDivision = initPositions.xDivision
      zeroH = initPositions.zeroH

      this.yArrj = []
      this.yArrjF = []
      this.yArrjVal = []
      this.xArrj = []
      this.xArrjF = []
      this.xArrjVal = []

      // if (!this.horizontal) {
      // this.xArrj.push(x + barWidth / 2)
      // }

      for (let j = 0; j < w.globals.dataPoints; j++) {
        if (w.config.stroke.show) {
          if (this.isNullValue) {
            strokeWidth = 0
          } else {
            strokeWidth = Array.isArray(this.strokeWidth)
              ? this.strokeWidth[realIndex]
              : this.strokeWidth
          }
        }

        let paths = null
        if (this.isHorizontal) {
          paths = this.drawBarPaths({
            indexes: { i, j, realIndex, bc },
            barHeight,
            strokeWidth,
            pathTo,
            pathFrom,
            zeroW,
            x,
            y,
            yDivision,
            elSeries
          })
          barWidth = this.series[i][j] / this.invertedYRatio
        } else {
          paths = this.drawColumnPaths({
            indexes: { i, j, realIndex, bc },
            x,
            y,
            xDivision,
            pathTo,
            pathFrom,
            barWidth,
            zeroH,
            strokeWidth,
            elSeries
          })
          barHeight = this.series[i][j] / this.yRatio[this.yaxisIndex]
        }

        pathTo = paths.pathTo
        pathFrom = paths.pathFrom
        y = paths.y
        x = paths.x

        xArrValues.push(x)
        yArrValues.push(y)

        let pathFill = this.bar.getPathFillColor(series, i, j, realIndex)

        elSeries = this.renderSeries({
          realIndex,
          pathFill,
          j,
          i,
          pathFrom,
          pathTo,
          strokeWidth,
          elSeries,
          x,
          y,
          series,
          barHeight,
          barWidth,
          elDataLabelsWrap,
          type: 'bar',
          visibleSeries: 0
        })
      }

      // push all x val arrays into main xArr
      w.globals.seriesXvalues[realIndex] = xArrValues
      w.globals.seriesYvalues[realIndex] = yArrValues

      // push all current y values array to main PrevY Array
      this.prevY.push(this.yArrj)
      this.prevYF.push(this.yArrjF)
      this.prevYVal.push(this.yArrjVal)
      this.prevX.push(this.xArrj)
      this.prevXF.push(this.xArrjF)
      this.prevXVal.push(this.xArrjVal)

      ret.add(elSeries)
    }

    return ret
  }

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

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

      barHeight =
        (barHeight * parseInt(w.config.plotOptions.bar.barHeight)) / 100

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

      // initial y position is half of barHeight * half of number of Bars
      y = (yDivision - barHeight) / 2
    } else {
      // width divided into equal parts
      xDivision = w.globals.gridWidth / w.globals.dataPoints

      barWidth = xDivision

      if (w.globals.isXNumeric) {
        xDivision = w.globals.minXDiff / this.xRatio
        barWidth = (xDivision * parseInt(this.barOptions.columnWidth)) / 100
      } else {
        barWidth =
          (barWidth * parseInt(w.config.plotOptions.bar.columnWidth)) / 100
      }

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

      // initial x position is one third of barWidth
      x = w.globals.padHorizontal + (xDivision - barWidth) / 2
    }
    return {
      x,
      y,
      yDivision,
      xDivision,
      barHeight,
      barWidth,
      zeroH,
      zeroW
    }
  }

  drawBarPaths({
    indexes,
    barHeight,
    strokeWidth,
    pathTo,
    pathFrom,
    zeroW,
    x,
    y,
    yDivision,
    elSeries
  }) {
    let w = this.w
    let barYPosition = y
    let barXPosition
    let i = indexes.i
    let j = indexes.j
    let realIndex = indexes.realIndex
    let bc = indexes.bc

    let prevBarW = 0
    for (let k = 0; k < this.prevXF.length; k++) {
      prevBarW = prevBarW + this.prevXF[k][j]
    }

    if (i > 0) {
      let bXP = zeroW

      if (this.prevXVal[i - 1][j] < 0) {
        if (this.series[i][j] >= 0) {
          bXP =
            this.prevX[i - 1][j] +
            prevBarW -
            (this.isReversed ? prevBarW : 0) * 2
        } else {
          bXP = this.prevX[i - 1][j]
        }
      } else if (this.prevXVal[i - 1][j] >= 0) {
        if (this.series[i][j] >= 0) {
          bXP = this.prevX[i - 1][j]
        } else {
          bXP =
            this.prevX[i - 1][j] -
            prevBarW +
            (this.isReversed ? prevBarW : 0) * 2
        }
      }

      barXPosition = bXP
    } else {
      // the first series will not have prevX values
      barXPosition = zeroW
    }

    if (this.series[i][j] === null) {
      x = barXPosition
    } else {
      x =
        barXPosition +
        this.series[i][j] / this.invertedYRatio -
        (this.isReversed ? this.series[i][j] / this.invertedYRatio : 0) * 2
    }

    let endingShapeOpts = {
      barHeight,
      strokeWidth,
      invertedYRatio: this.invertedYRatio,
      barYPosition,
      x
    }
    let endingShape = this.bar.barEndingShape(
      w,
      endingShapeOpts,
      this.series,
      i,
      j
    )

    if (this.series.length > 1 && i !== this.endingShapeOnSeriesNumber) {
      // revert back to flat shape if not last series
      endingShape.path = this.graphics.line(
        endingShape.newX,
        barYPosition + barHeight - strokeWidth
      )
    }

    this.xArrj.push(endingShape.newX)
    this.xArrjF.push(Math.abs(barXPosition - endingShape.newX))
    this.xArrjVal.push(this.series[i][j])

    pathTo = this.graphics.move(barXPosition, barYPosition)
    pathFrom = this.graphics.move(barXPosition, barYPosition)

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

    pathTo =
      pathTo +
      this.graphics.line(endingShape.newX, barYPosition) +
      endingShape.path +
      this.graphics.line(barXPosition, barYPosition + barHeight - strokeWidth) +
      this.graphics.line(barXPosition, barYPosition)
    pathFrom =
      pathFrom +
      this.graphics.line(barXPosition, barYPosition) +
      this.graphics.line(barXPosition, barYPosition + barHeight - strokeWidth) +
      this.graphics.line(barXPosition, barYPosition + barHeight - strokeWidth) +
      this.graphics.line(barXPosition, barYPosition + barHeight - strokeWidth) +
      this.graphics.line(barXPosition, barYPosition)

    if (
      w.config.plotOptions.bar.colors.backgroundBarColors.length > 0 &&
      i === 0
    ) {
      if (bc >= w.config.plotOptions.bar.colors.backgroundBarColors.length) {
        bc = 0
      }

      let bcolor = w.config.plotOptions.bar.colors.backgroundBarColors[bc]
      let rect = this.graphics.drawRect(
        0,
        barYPosition,
        w.globals.gridWidth,
        barHeight,
        0,
        bcolor,
        w.config.plotOptions.bar.colors.backgroundBarOpacity
      )
      elSeries.add(rect)
      rect.node.classList.add('apexcharts-backgroundBar')
    }

    y = y + yDivision

    return {
      pathTo,
      pathFrom,
      x,
      y
    }
  }

  drawColumnPaths({
    indexes,
    x,
    y,
    xDivision,
    pathTo,
    pathFrom,
    barWidth,
    zeroH,
    strokeWidth,
    elSeries
  }) {
    let w = this.w
    let i = indexes.i
    let j = indexes.j
    let realIndex = indexes.realIndex
    let bc = indexes.bc

    if (w.globals.isXNumeric) {
      let seriesVal = w.globals.seriesX[i][j]
      if (!seriesVal) seriesVal = 0
      x = (seriesVal - w.globals.minX) / this.xRatio - barWidth / 2
    }

    let barXPosition = x
    let barYPosition

    let prevBarH = 0
    for (let k = 0; k < this.prevYF.length; k++) {
      prevBarH = prevBarH + this.prevYF[k][j]
    }

    if (
      (i > 0 && !w.globals.isXNumeric) ||
      (i > 0 &&
        w.globals.isXNumeric &&
        w.globals.seriesX[i - 1][j] === w.globals.seriesX[i][j])
    ) {
      let bYP
      let prevYValue = this.prevY[i - 1][j]

      if (this.prevYVal[i - 1][j] < 0) {
        if (this.series[i][j] >= 0) {
          bYP = prevYValue - prevBarH + (this.isReversed ? prevBarH : 0) * 2
        } else {
          bYP = prevYValue
        }
      } else {
        if (this.series[i][j] >= 0) {
          bYP = prevYValue
        } else {
          bYP = prevYValue + prevBarH - (this.isReversed ? prevBarH : 0) * 2
        }
      }

      barYPosition = bYP
    } else {
      // the first series will not have prevY values, also if the prev index's series X doesn't matches the current index's series X, then start from zero
      barYPosition = w.globals.gridHeight - zeroH
    }

    y =
      barYPosition -
      this.series[i][j] / this.yRatio[this.yaxisIndex] +
      (this.isReversed ? this.series[i][j] / this.yRatio[this.yaxisIndex] : 0) *
        2

    let endingShapeOpts = {
      barWidth,
      strokeWidth,
      yRatio: this.yRatio[this.yaxisIndex],
      barXPosition,
      y
    }
    let endingShape = this.bar.barEndingShape(
      w,
      endingShapeOpts,
      this.series,
      i,
      j
    )

    this.yArrj.push(endingShape.newY)
    this.yArrjF.push(Math.abs(barYPosition - endingShape.newY))
    this.yArrjVal.push(this.series[i][j])

    pathTo = this.graphics.move(barXPosition, barYPosition)
    pathFrom = this.graphics.move(barXPosition, barYPosition)
    if (w.globals.previousPaths.length > 0) {
      pathFrom = this.bar.getPathFrom(realIndex, j, false)
    }

    pathTo =
      pathTo +
      this.graphics.line(barXPosition, endingShape.newY) +
      endingShape.path +
      this.graphics.line(barXPosition + barWidth - strokeWidth, barYPosition) +
      this.graphics.line(barXPosition - strokeWidth / 2, barYPosition)
    pathFrom =
      pathFrom +
      this.graphics.line(barXPosition, barYPosition) +
      this.graphics.line(barXPosition + barWidth - strokeWidth, barYPosition) +
      this.graphics.line(barXPosition + barWidth - strokeWidth, barYPosition) +
      this.graphics.line(barXPosition + barWidth - strokeWidth, barYPosition) +
      this.graphics.line(barXPosition - strokeWidth / 2, barYPosition)

    if (
      w.config.plotOptions.bar.colors.backgroundBarColors.length > 0 &&
      i === 0
    ) {
      if (bc >= w.config.plotOptions.bar.colors.backgroundBarColors.length) {
        bc = 0
      }
      let bcolor = w.config.plotOptions.bar.colors.backgroundBarColors[bc]
      let rect = this.graphics.drawRect(
        barXPosition,
        0,
        barWidth,
        w.globals.gridHeight,
        0,
        bcolor,
        w.config.plotOptions.bar.colors.backgroundBarOpacity
      )
      elSeries.add(rect)
      rect.node.classList.add('apexcharts-backgroundBar')
    }

    x = x + xDivision

    return {
      pathTo,
      pathFrom,
      x: w.globals.isXNumeric ? x - xDivision : x,
      y
    }
  }

  /*
   * When user clicks on legends, the collapsed series will be filled with [0,0,0,...,0]
   * We need to make sure, that the last series is not [0,0,0,...,0]
   * as we need to draw shapes on the last series (for stacked bars/columns only)
   * Hence, we are collecting all inner arrays in series which has [0,0,0...,0]
   **/

  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.zeroSerieses.push(zs)
      }
    }

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

export default BarStacked
