import * as React from 'react'
import * as _ from 'lodash'
import { View, StyleSheet, LayoutChangeEvent } from 'react-native'
import Svg, { Path, Defs } from 'react-native-svg'
import { scaleTime, scaleLinear, ScaleLinear, ScaleTime } from 'd3-scale'
import * as shape from 'd3-shape'
import { LineData } from '../types/LineData'

interface DataPoint {
  x: number
  y: number
}

interface GraphProps {
  data: LineData[]
}

const strokeWidth = 2
const padding = 10

const getDomain = (domain: number[]) => [
  Math.min(...domain),
  Math.max(...domain),
]

const getArea = (
  yValues: number[],
  scaleX: ScaleTime<number, number>,
  scaleY: ScaleLinear<number, number>,
  defined: boolean[]
): string =>
  shape
    .area<DataPoint>()
    .x((p) => scaleX(p.x))
    .y1((p) => scaleY(p.y))
    .y0(scaleY(0))
    .defined((d, i) => defined[i])
    .curve(shape.curveNatural)(yValues.map((y, x) => ({ y, x })))

export class StackedAreaGraph extends React.Component<
  GraphProps,
  { width: number; height: number }
> {
  state = {
    width: 0,
    height: 0,
  }

  measureView(event: LayoutChangeEvent): void {
    this.setState({
      width: event.nativeEvent.layout.width,
      height: event.nativeEvent.layout.height,
    })
  }

  render() {
    const { data } = this.props
    const { width, height } = this.state

    const length = data[0].yValues.length

    const sumValues = (xs: number[], ys: number[]) =>
      _.zipWith(xs, ys, (x: number, y: number) => x + y)

    //Compute stack of cumulative sums
    const sums = _.reverse(
      data.reduce((acc: LineData[], curr: LineData) => {
        const prevItem = _.last(acc)
        const prevValues = prevItem ? prevItem.yValues : Array(length).fill(0)
        const item = {
          yValues: sumValues(prevValues, curr.yValues),
          strokeColor: curr.strokeColor,
        }
        return acc.concat(item)
      }, [] as LineData[])
    )
    const defined = data.reduce(
      (acc: boolean[], curr: LineData) =>
        _.zipWith(
          acc,
          curr.yValues,
          (x: boolean, y: number) => x && y !== null
        ),
      Array(length).fill(true) as boolean[]
    )

    const scaleX = scaleTime()
      .domain(getDomain(sums[0].yValues.map((_, i) => i)))
      .range([0, width])
    const scaleY = scaleLinear()
      .domain(getDomain([0, ...sums.flatMap((d) => d.yValues)]))
      .range([height, padding])

    const areas = sums.map(({ yValues, strokeColor }) => ({
      fillColor: strokeColor,
      d: getArea(yValues, scaleX, scaleY, defined),
    }))

    return (
      <View style={{ flex: 1 }} onLayout={(event) => this.measureView(event)}>
        <Svg style={StyleSheet.absoluteFill}>
          <Defs></Defs>
          {areas.map(({ d, fillColor }, i) => (
            <Path key={i} fill={fillColor} {...{ d, strokeWidth }} />
          ))}
        </Svg>
      </View>
    )
  }
}
