import Foundation

extension Double {

    /**
     Move to a different value by the given ratio of their distance.
     */
    func move(_ ratio: Double, to other: Double) -> Double {
        self + (other - self) * ratio
    }
}

extension Collection where Element == Double {

    func sum() -> Double {
        reduce(0, +)
    }

    func average() -> Double {
        sum() / Double(count)
    }
}

extension Collection where Element == Double, Index == Int {

    func floatingMeanFiltered(windowSize: Int) -> [Double] {
        guard windowSize > 1 else {
            return self.map { $0 }
        }
        guard count >= windowSize else {
            return .init(repeating: average(), count: count)
        }

        let firstHalf = windowSize / 2
        let secondHalf = windowSize - firstHalf
        var minSpeed: Double = 0
        var maxSpeed: Double = 0
        let averageScale = 1.0 / Double(windowSize)

        // First calculate the filtered speeds in the normal unit
        var currentAverage = self[0..<windowSize].average()
        var result: [Double] = .init(repeating: currentAverage, count: firstHalf + 1)
        for index in firstHalf..<count-firstHalf-1 { // Index in self
            let removed = self[index-firstHalf]
            let added = self[index+secondHalf]
            currentAverage += (added - removed) * averageScale
            result.append(currentAverage)
            if currentAverage < minSpeed { minSpeed = currentAverage }
            else if currentAverage > maxSpeed { maxSpeed = currentAverage }
        }
        result.append(contentsOf: [Double](repeating: currentAverage, count: secondHalf))
        return result
    }

    func fittingAxisLimits(desiredNumSteps: Int = 5, minLimit: Double? = nil, maxLimit: Double? = nil) -> RouteProfile {
        let (dataMin, dataMax) = minMax(minLimit: minLimit, maxLimit: maxLimit)

        let dataRange = dataMax - dataMin
        let roughStep = dataRange / Double(desiredNumSteps)

        let exponent = floor(log10(roughStep))
        let base = pow(10.0, exponent)

        let step: Double
        if roughStep <= base {
            step = base
        } else if roughStep <= 2 * base {
            step = 2 * base
        } else if roughStep <= 5 * base {
            step = 5 * base
        } else {
            step = 10 * base
        }

        let graphMin = floor(dataMin / step) * step
        let graphMax = ceil(dataMax / step) * step
        let numTicks = Int((graphMax - graphMin) / step)
        return .init(min: graphMin, max: graphMax, ticks: numTicks)
    }

    func minMax(minLimit: Double? = nil, maxLimit: Double? = nil) -> (min: Double, max: Double) {
        var dataMin = self.min() ?? 0
        var dataMax = self.max() ?? 1
        if let minLimit {
            dataMin = Swift.max(dataMin, minLimit)
        }
        if let maxLimit {
            dataMax = Swift.min(dataMax, maxLimit)
        }
        return (dataMin, dataMax)
    }

    func fittingTimeAxisLimits(minTicks: Int = 3, maxTicks: Int = 7, minLimit: Double? = nil, maxLimit: Double? = nil) -> RouteProfile {
        let (dataMin, dataMax) = minMax(minLimit: minLimit, maxLimit: maxLimit)

        let dataRange = dataMax - dataMin
        let niceSteps: [Double] = [15, 30, 60, 120, 300, 600, 900, 1800, 3600] // in seconds

        // Find the step size that gives a nice number of ticks
        var chosenStep = niceSteps.last! // fallback to largest if none fit
        for step in niceSteps {
            let numTicks = dataRange / step
            if Double(minTicks) <= numTicks && numTicks <= Double(maxTicks) {
                chosenStep = step
                break
            }
        }

        let graphMin = floor(dataMin / chosenStep) * chosenStep
        let graphMax = ceil(dataMax / chosenStep) * chosenStep
        let numTicks = Int((graphMax-graphMin) / chosenStep)
        return .init(min: graphMin, max: graphMax, ticks: numTicks)
    }

}
