//
//  ScatterChartRenderer.swift
//  Charts
//
//  Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
//  A port of MPAndroidChart for iOS
//  Licensed under Apache License 2.0
//
//  https://github.com/danielgindi/Charts
//

import CoreGraphics
import Foundation

open class ScatterChartRenderer: LineScatterCandleRadarRenderer {
  @objc open weak var dataProvider: ScatterChartDataProvider?

  @objc public init(dataProvider: ScatterChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler)
  {
    super.init(animator: animator, viewPortHandler: viewPortHandler)

    self.dataProvider = dataProvider
  }

  override open func drawData(context: CGContext) {
    guard let scatterData = dataProvider?.scatterData else { return }

    // If we redraw the data, remove and repopulate accessible elements to update label values and frames
    accessibleChartElements.removeAll()

    if let chart = dataProvider as? ScatterChartView {
      // Make the chart header the first element in the accessible elements array
      let element = createAccessibleHeader(usingChart: chart,
                                           andData: scatterData,
                                           withDefaultDescription: "Scatter Chart")
      accessibleChartElements.append(element)
    }

    // TODO: Due to the potential complexity of data presented in Scatter charts, a more usable way
    // for VO accessibility would be to use axis based traversal rather than by dataset.
    // Hence, accessibleChartElements is not populated below. (Individual renderers guard against dataSource being their respective views)
    for i in 0..<scatterData.dataSetCount {
      guard let set = scatterData.getDataSetByIndex(i) else { continue }

      if set.isVisible {
        if !(set is IScatterChartDataSet) {
          fatalError("Datasets for ScatterChartRenderer must conform to IScatterChartDataSet")
        }

        drawDataSet(context: context, dataSet: set as! IScatterChartDataSet)
      }
    }
  }

  private var _lineSegments = [CGPoint](repeating: CGPoint(), count: 2)

  @objc open func drawDataSet(context: CGContext, dataSet: IScatterChartDataSet) {
    guard let dataProvider = dataProvider else { return }

    let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency)

    let phaseY = animator.phaseY

    let entryCount = dataSet.entryCount

    var point = CGPoint()

    let valueToPixelMatrix = trans.valueToPixelMatrix

    if let renderer = dataSet.shapeRenderer {
      context.saveGState()

      for j in 0..<Int(min(ceil(Double(entryCount) * animator.phaseX), Double(entryCount))) {
        guard let e = dataSet.entryForIndex(j) else { continue }

        point.x = CGFloat(e.x)
        point.y = CGFloat(e.y * phaseY)
        point = point.applying(valueToPixelMatrix)

        if !viewPortHandler.isInBoundsRight(point.x) {
          break
        }

        if !viewPortHandler.isInBoundsLeft(point.x) ||
          !viewPortHandler.isInBoundsY(point.y)
        {
          continue
        }

        renderer.renderShape(context: context, dataSet: dataSet, viewPortHandler: viewPortHandler, point: point, color: dataSet.color(atIndex: j))
      }

      context.restoreGState()
    } else {
      print("There's no IShapeRenderer specified for ScatterDataSet", terminator: "\n")
    }
  }

  override open func drawValues(context: CGContext) {
    guard
      let dataProvider = dataProvider,
      let scatterData = dataProvider.scatterData
    else { return }

    // if values are drawn
    if isDrawingValuesAllowed(dataProvider: dataProvider) {
      guard let dataSets = scatterData.dataSets as? [IScatterChartDataSet] else { return }

      let phaseY = animator.phaseY

      var pt = CGPoint()

      for i in 0..<scatterData.dataSetCount {
        let dataSet = dataSets[i]
        guard let
          formatter = dataSet.valueFormatter,
          shouldDrawValues(forDataSet: dataSet)
        else { continue }

        let valueFont = dataSet.valueFont

        let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency)
        let valueToPixelMatrix = trans.valueToPixelMatrix

        let iconsOffset = dataSet.iconsOffset

        let shapeSize = dataSet.scatterShapeSize
        let lineHeight = valueFont.lineHeight

        _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator)

        for j in _xBounds {
          guard let e = dataSet.entryForIndex(j) else { break }

          pt.x = CGFloat(e.x)
          pt.y = CGFloat(e.y * phaseY)
          pt = pt.applying(valueToPixelMatrix)

          if !viewPortHandler.isInBoundsRight(pt.x) {
            break
          }

          // make sure the lines don't do shitty things outside bounds
          if !viewPortHandler.isInBoundsLeft(pt.x)
            || !viewPortHandler.isInBoundsY(pt.y)
          {
            continue
          }

          let text = formatter.stringForValue(e.y,
                                              entry: e,
                                              dataSetIndex: i,
                                              viewPortHandler: viewPortHandler)

          if dataSet.isDrawValuesEnabled {
            ChartUtils.drawText(context: context,
                                text: text,
                                point: CGPoint(x: pt.x,
                                               y: pt.y - shapeSize - lineHeight),
                                align: .center,
                                attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: dataSet.valueTextColorAt(j)])
          }

          if let icon = e.icon, dataSet.isDrawIconsEnabled {
            ChartUtils.drawImage(context: context,
                                 image: icon,
                                 x: pt.x + iconsOffset.x,
                                 y: pt.y + iconsOffset.y,
                                 size: icon.size)
          }
        }
      }
    }
  }

  override open func drawExtras(context: CGContext) {}

  override open func drawHighlighted(context: CGContext, indices: [Highlight]) {
    guard
      let dataProvider = dataProvider,
      let scatterData = dataProvider.scatterData
    else { return }

    context.saveGState()

    for high in indices {
      guard
        let set = scatterData.getDataSetByIndex(high.dataSetIndex) as? IScatterChartDataSet,
        set.isHighlightEnabled
      else { continue }

      guard let entry = set.entryForXValue(high.x, closestToY: high.y) else { continue }

      if !isInBoundsX(entry: entry, dataSet: set) { continue }

      context.setStrokeColor(set.highlightColor.cgColor)
      context.setLineWidth(set.highlightLineWidth)
      if set.highlightLineDashLengths != nil {
        context.setLineDash(phase: set.highlightLineDashPhase, lengths: set.highlightLineDashLengths!)
      } else {
        context.setLineDash(phase: 0.0, lengths: [])
      }

      let x = entry.x // get the x-position
      let y = entry.y * Double(animator.phaseY)

      let trans = dataProvider.getTransformer(forAxis: set.axisDependency)

      let pt = trans.pixelForValues(x: x, y: y)

      high.setDraw(pt: pt)

      // draw the lines
      drawHighlightLines(context: context, point: pt, set: set)
    }

    context.restoreGState()
  }
}
