//
//  LineView.swift
//  LineChart
//
//  Created by András Samu on 2019. 09. 02..
//  Copyright © 2019. András Samu. All rights reserved.
//
import SwiftUI

public struct LineView: View {
    @ObservedObject var data: ChartData
    public var xAxisLables: [String]?
    public var title: String?
    public var legend: String?
    public var showOrigin: Bool?
    public var style: ChartStyle
    public var darkModeStyle: ChartStyle
    public var valueSpecifier: String
    
    @Environment(\.colorScheme) var colorScheme: ColorScheme
    @State private var showLegend = false
    @State private var dragLocation: CGPoint = .zero
    @State private var indicatorLocation: CGPoint = .zero
    @State private var closestPoint: CGPoint = .zero
    @State private var opacity: Double = 0
    @State private var currentDataNumber: Double = 0
    @State private var currentDataLabel: String = ""
    @State private var hideHorizontalLines: Bool = false
    
    public init(data: [Double],
                xAxisLables: [String]? = [],
                title: String? = nil,
                legend: String? = nil,
                showOrigin: Bool? = true,
                style: ChartStyle = Styles.lineChartStyleOne,
                valueSpecifier: String? = "%.1f") {
        self.data = ChartData(points: data)
        self.xAxisLables = xAxisLables
        self.title = title
        self.legend = legend
        self.showOrigin = showOrigin
        self.style = style
        self.valueSpecifier = valueSpecifier!
        self.darkModeStyle = style.darkModeStyle != nil ? style.darkModeStyle! : Styles.lineViewDarkMode
    }
    
    public var body: some View {
        GeometryReader { geometry in
            VStack(alignment: .leading, spacing: 8) {
                Group {
                    if self.title != nil {
                        Text(self.title!)
                            .font(.title)
                            .bold().foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.textColor : self.style.textColor)
                    }
                    if self.legend != nil {
                        Text(self.legend!)
                            .font(.callout)
                            .foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.legendTextColor : self.style.legendTextColor)
                    }
                }.offset(x: 20, y: 0)
                ZStack {
                    GeometryReader { reader in
                        Rectangle()
                            .foregroundColor(self.colorScheme == .dark ? self.darkModeStyle.backgroundColor : self.style.backgroundColor)
                        if self.showLegend {
                            Legend(data: self.data,
                                   frame: .constant(reader.frame(in: .local)), hideHorizontalLines: self.$hideHorizontalLines, showOrigin: self.showOrigin)
                                .transition(.opacity)
                                .animation(Animation.easeOut(duration: 1).delay(1))
                        }
                        Line(data: self.data,
                             frame: .constant(CGRect(x: 0, y: 0, width: reader.frame(in: .local).width - 32, height: reader.frame(in: .local).height)),
                             touchLocation: self.$indicatorLocation,
                             showIndicator: self.$hideHorizontalLines,
                             minDataValue: .constant(nil),
                             maxDataValue: .constant(nil),
                             showBackground: false)
                            .offset(x: 32, y: 0)
                            .onAppear {
                                self.showLegend = true
                        }
                        .onDisappear {
                            self.showLegend = false
                        }
                    }
                    .frame(width: geometry.frame(in: .local).size.width, height: 240)
                    MagnifierRect(currentNumber: self.$currentDataNumber, currentDataLabel: self.$currentDataLabel, valueSpecifier: self.valueSpecifier)
                        .opacity(self.opacity)
                        .offset(x: self.dragLocation.x - geometry.frame(in: .local).size.width / 2, y: 32)
                }
                .frame(width: geometry.frame(in: .local).size.width, height: 240)
                .gesture(DragGesture()
                .onChanged { value in
                    self.dragLocation = value.location
                    self.indicatorLocation = CGPoint(x: max(value.location.x - 30, 0), y: 32)
                    self.opacity = 1
                    self.closestPoint = self.getClosestDataPoint(toPoint: value.location, width: geometry.frame(in: .local).size.width - 30, height: 240)
                    self.hideHorizontalLines = true
                }
                .onEnded { _ in
                    self.opacity = 0
                    self.hideHorizontalLines = false
                    }
                )
                if self.xAxisLables != nil {
                    HStack{
                        ForEach(self.xAxisLables!, id: \.self) { xAxisLable in
                            Text(xAxisLable)
                                .foregroundColor(Colors.LegendText)
                                .font(.caption)
                        }
                    }
                    .padding(.trailing, 32)
                    .offset(x: 32, y: self.showOrigin == true ? 26 : 0)
                }
            }
        }
    }
    
    func getClosestDataPoint(toPoint: CGPoint, width: CGFloat, height: CGFloat) -> CGPoint {
        let points = self.data.onlyPoints()
        let stepWidth: CGFloat = width / CGFloat(points.count - 1)
        let stepHeight: CGFloat = height / CGFloat(points.max()! + points.min()!)
        
        let index: Int = Int(floor((toPoint.x - 15) / stepWidth))
        if let xAxisLables = xAxisLables, index >= 0 && index < xAxisLables.count {
            self.currentDataLabel = xAxisLables[index]
        }
        if index >= 0 && index < points.count {
            self.currentDataNumber = points[index]
            return CGPoint(x: CGFloat(index) * stepWidth, y: CGFloat(points[index]) * stepHeight)
        }
        return .zero
    }
}

struct LineView_Previews: PreviewProvider {
    static var previews: some View {
        LineView(data: [8, 23, 54, 32, 12, 37, 7, 23, 43], xAxisLables: [], title: "Full chart", legend: "Full chart", showOrigin: true, style: Styles.lineChartStyleOne)
    }
}
