//
//  ChartAxisValuesStaticGenerator.swift
//  LoopUI
//
//  Created by Nathaniel Hamming on 2020-09-08.
//  Copyright © 2020 LoopKit Authors. All rights reserved.
//

import SwiftCharts
import UIKit

extension ChartAxisValuesStaticGenerator {
    // This is the same as SwiftChart ChartAxisValuesStaticGenerator.generateAxisValuesWithChartPoints(...) with the exception that the `currentMultiple` is calculated linearly instead of quadratically
    static func generateYAxisValuesUsingLinearSegmentStep(chartPoints: [ChartPoint],
                                                          minSegmentCount: Double,
                                                          maxSegmentCount: Double,
                                                          multiple: Double,
                                                          axisValueGenerator: ChartAxisValueStaticGenerator,
                                                          addPaddingSegmentIfEdge: Bool) -> [ChartAxisValue]
    {
        precondition(multiple > 0, "Invalid multiple: \(multiple)")
        
        let sortedChartPoints = chartPoints.sorted {(obj1, obj2) in
            return obj1.y.scalar < obj2.y.scalar
        }
        
        if let firstChartPoint = sortedChartPoints.first, let lastChartPoint = sortedChartPoints.last {
            let first = firstChartPoint.y.scalar
            let lastPar = lastChartPoint.y.scalar
            
            guard lastPar >=~ first else {fatalError("Invalid range generating axis values")}
            
            let last = lastPar =~ first ? lastPar + 1 : lastPar
            
            /// The first axis value will be less than or equal to the first scalar value, aligned with the desired multiple
            var firstValue = first - (first.truncatingRemainder(dividingBy: multiple))
            /// The last axis value will be greater than or equal to the last scalar value, aligned with the desired multiple
            let remainder = last.truncatingRemainder(dividingBy: multiple)
            var lastValue = remainder == 0 ? last : last + (multiple - remainder)
            var segmentSize = multiple
            
            /// If there should be a padding segment added when a scalar value falls on the first or last axis value, adjust the first and last axis values
            if firstValue =~ first && addPaddingSegmentIfEdge {
               firstValue = firstValue - segmentSize
            }
            
            // do not allow the first label to be displayed as -0
            while firstValue < 0 && firstValue.rounded() == -0 {
                firstValue = firstValue - segmentSize
            }
            
            if lastValue =~ last && addPaddingSegmentIfEdge {
                lastValue = lastValue + segmentSize
            }
            
            let distance = lastValue - firstValue
            var currentMultiple = multiple
            var segmentCount = distance / currentMultiple
            var potentialSegmentValues = stride(from: firstValue, to: lastValue, by: currentMultiple)

            /// Find the optimal number of segments and segment width
            /// If the number of segments is greater than desired, make each segment wider
            /// ensure no label of -0 will be displayed on the axis
            while segmentCount > maxSegmentCount ||
                !potentialSegmentValues.filter({ $0 < 0 && $0.rounded() == -0 }).isEmpty
            {
                currentMultiple += multiple
                segmentCount = distance / currentMultiple
                potentialSegmentValues = stride(from: firstValue, to: lastValue, by: currentMultiple)
            }
            segmentCount = ceil(segmentCount)
            
            /// Increase the number of segments until there are enough as desired
            while segmentCount < minSegmentCount {
                segmentCount += 1
            }
            segmentSize = currentMultiple
            
            /// Generate axis values from the first value, segment size and number of segments
            let offset = firstValue
            return (0...Int(segmentCount)).map {segment in
                var scalar = offset + (Double(segment) * segmentSize)
                // a value that could be displayed as 0 should truly be 0 to have the zero-line drawn correctly.
                if scalar != 0,
                    scalar.rounded() == 0
                {
                    scalar = 0
                }
                return axisValueGenerator(scalar)
            }
        } else {
            print("Trying to generate Y axis without datapoints, returning empty array")
            return []
        }
    }
}

fileprivate func =~ (a: Double, b: Double) -> Bool {
    return fabs(a - b) < Double.ulpOfOne
}

fileprivate func >=~ (a: Double, b: Double) -> Bool {
    return a =~ b || a > b
}
