import SwiftUI
import WMF

private struct SparklineShape: Shape {

	// MARK: Private Properties

	private let data: [CGFloat]

	// MARK: Public

	init(data: [NSNumber]?) {
		self.data = data?.compactMap { CGFloat($0.doubleValue) } ?? []
	}

	// MARK: Shape

    func path(in rect: CGRect) -> Path {
        var path = Path()
        
        guard data.count > 1, let min = data.min(), let max = data.max() else {
            return path
        }

        guard min != max else {
            path.move(to: CGPoint(x: rect.minX, y: rect.midY))
            path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
            return path
        }
                        
        let minY = rect.minY
        let width = rect.width / CGFloat(data.count - 1)
        let height = rect.maxY - rect.minY
        
        var points: [CGPoint] = []
        
        for (index, dataPoint) in data.enumerated() {
            let relativeY = dataPoint - CGFloat(min)
            let normalizedY = 1 - relativeY/(CGFloat(max-min))
            let y = minY + height * normalizedY
            let x = width * CGFloat(index)
            points.append(CGPoint(x: x, y: y))
        }
        
        path.move(to: points[0])
        for (index, point) in points[1...points.count-1].enumerated() {
            let fromPoint = points[index]
            let midPoint = CGPoint.midPointFrom(fromPoint, to: point)
            let midPointControlPoint = CGPoint.quadCurveControlPointFrom(midPoint, to: fromPoint)
            path.addQuadCurve(to: midPoint, control: midPointControlPoint)
            let toPointControlPoint = CGPoint.quadCurveControlPointFrom(midPoint, to: point)
            path.addQuadCurve(to: point, control: toPointControlPoint)
        }
    
        return path
    }
    
}

struct SparklineGrid: View {
	@Environment(\.colorScheme) private var colorScheme

	// MARK: Properties

	var gridStyle: Sparkline.GridStyle

	// MARK: View

	var body: some View {
		switch gridStyle {
		case .horizontal:
			GeometryReader { proxy in
				let yOffset = proxy.size.height / 2.0

				Path { path in
					path.move(to: CGPoint(x: 0, y: yOffset))
					path.addLine(to: CGPoint(x: proxy.size.width, y: yOffset))

					path.move(to: CGPoint(x: 0, y: yOffset / 2.0))
					path.addLine(to: CGPoint(x: proxy.size.width, y: yOffset / 2.0))

					path.move(to: CGPoint(x: 0, y: yOffset * 1.5))
					path.addLine(to: CGPoint(x: proxy.size.width, y: yOffset * 1.5))
				}
				.stroke(style: StrokeStyle(lineWidth: 1.0, lineCap: .round))
			}
		case .horizontalAndVertical:
			ZStack {
				VStack {
					Spacer()
					Rectangle().frame(height: 1)
					Spacer()
					Rectangle().frame(height: 1)
					Spacer()
					Rectangle().frame(height: 1)
					Spacer()
					Rectangle().frame(height: 1)
					Spacer()
				}
				HStack {
					Spacer()
					Rectangle().frame(width: 1)
					Spacer()
					Rectangle().frame(width: 1)
					Spacer()
					Rectangle().frame(width: 1)
					Spacer()
				}
			}
		}
	}
}

struct Sparkline: View {
    @Environment(\.colorScheme) private var colorScheme

	// MARK: Nested Types

	enum Style {
		case compact
        case compactWithViewCount
		case expanded
	}
    
    enum GridStyle {
        case horizontal
        case horizontalAndVertical
    }

	// MARK: Properties

	var style: Style = .compact
    var gridStyle: GridStyle = .horizontal
	var lineWidth: CGFloat {
		return style == .expanded ? 2.25 : 1.5
	}
    
	var timeSeries: [NSNumber]? = []
	var containerBackgroundColor: Color {
		switch style {
		case .compactWithViewCount:
			return colorScheme == .dark ? Color.white.opacity(0.12) : Color(red: 248/255.0, green: 248/255.0, blue: 250/255.0, opacity: 1)
		case .compact:
			return colorScheme == .dark ? .black : .white
		case .expanded:
			return colorScheme == .dark ? .black : .white
		}
	}
    
    var gradientStartColor: Color {
        colorScheme == .light
            ? Theme.light.colors.rankGradientStart.asColor
            : Theme.dark.colors.rankGradientStart.asColor
    }

    var gradientEndColor: Color {
        colorScheme == .light
            ? Theme.light.colors.rankGradientEnd.asColor
            : Theme.dark.colors.rankGradientEnd.asColor
    }

	// MARK: View

	var body: some View {
		if style == .compact || style == .compactWithViewCount {
			HStack {
				Spacer().frame(width: 4)
				ZStack {
					SparklineGrid(gridStyle: .horizontal)
						.foregroundColor(colorScheme == .dark
							? Color(.sRGB, red: 55/255.0, green: 55/255.0, blue: 55/255.0, opacity: 1)
							: Color(.sRGB, red: 235/255.0, green: 235/255.0, blue: 235/255.0, opacity: 1)
						)
						.layoutPriority(-1)
					SparklineShape(data: timeSeries)
						.stroke(
							LinearGradient(gradient: Gradient(colors: [gradientStartColor, gradientEndColor]), startPoint: /*@START_MENU_TOKEN@*/.leading/*@END_MENU_TOKEN@*/, endPoint: /*@START_MENU_TOKEN@*/.trailing/*@END_MENU_TOKEN@*/), style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
						.frame(width: style == .compact ? 22 : 30, alignment: .leading)
						.padding([.top, .bottom, .leading, .trailing], 3)
				}
				if style == .compactWithViewCount {
					Text("\(currentViewCountOrEmpty)")
						.font(.system(size: 12))
						.fontWeight(.medium)
						.foregroundColor(Theme.light.colors.rankGradientEnd.asColor)
					}
					Spacer().frame(width: 4)
			}
			.background(containerBackgroundColor)
		} else {
			ZStack {
				SparklineGrid(gridStyle: .horizontalAndVertical)
					.foregroundColor(colorScheme == .dark
						? Color(.sRGB, red: 55/255.0, green: 55/255.0, blue: 55/255.0, opacity: 1)
						: Color(.sRGB, red: 235/255.0, green: 235/255.0, blue: 235/255.0, opacity: 1)
					)
				SparklineShape(data: timeSeries)
					.stroke(
						LinearGradient(gradient: Gradient(colors: [gradientStartColor, gradientEndColor]), startPoint: /*@START_MENU_TOKEN@*/.leading/*@END_MENU_TOKEN@*/, endPoint: /*@START_MENU_TOKEN@*/.trailing/*@END_MENU_TOKEN@*/), style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
					.padding([.top, .bottom, .leading, .trailing], 8)
			}
			.background(containerBackgroundColor)
		}
	}

	// MARK: Private

	private var currentViewCountOrEmpty: String {
		guard let currentViewCount = timeSeries?.last else {
			return "–"
		}

		return NumberFormatter.localizedThousandsStringFromNumber(currentViewCount)
	}

}
