ResponseConverters.kt

/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

@file:RestrictTo(RestrictTo.Scope.LIBRARY)
@file:RequiresApi(api = 34)

package androidx.health.connect.client.impl.platform.records

import android.health.connect.AggregateRecordsGroupedByDurationResponse
import android.health.connect.AggregateRecordsGroupedByPeriodResponse
import android.health.connect.AggregateRecordsResponse
import android.health.connect.datatypes.AggregationType
import android.health.connect.datatypes.units.Energy as PlatformEnergy
import android.health.connect.datatypes.units.Volume as PlatformVolume
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.annotation.VisibleForTesting
import androidx.health.connect.client.aggregate.AggregateMetric
import androidx.health.connect.client.aggregate.AggregationResult
import androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration
import androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod
import androidx.health.connect.client.units.Energy
import androidx.health.connect.client.units.Mass
import java.time.LocalDateTime
import java.time.ZoneOffset

fun AggregateRecordsResponse<Any>.toSdkResponse(metrics: Set<AggregateMetric<Any>>) =
    buildAggregationResult(
        metrics,
        ::get,
        ::getDataOrigins,
    )

fun AggregateRecordsGroupedByDurationResponse<Any>.toSdkResponse(
    metrics: Set<AggregateMetric<Any>>
) =
    AggregationResultGroupedByDuration(
        buildAggregationResult(metrics, ::get),
        startTime,
        endTime,
        getZoneOffset(metrics.first().toAggregationType())
            ?: ZoneOffset.systemDefault().rules.getOffset(startTime)
    )

fun AggregateRecordsGroupedByPeriodResponse<Any>.toSdkResponse(metrics: Set<AggregateMetric<Any>>) =
    AggregationResultGroupedByPeriod(buildAggregationResult(metrics, ::get), startTime, endTime)

fun AggregateRecordsGroupedByPeriodResponse<Any>.toSdkResponse(
    metrics: Set<AggregateMetric<Any>>,
    bucketStartTime: LocalDateTime,
    bucketEndTime: LocalDateTime
) =
    AggregationResultGroupedByPeriod(
        buildAggregationResult(metrics, ::get),
        bucketStartTime,
        bucketEndTime
    )

@VisibleForTesting
internal fun buildAggregationResult(
    metrics: Set<AggregateMetric<Any>>,
    aggregationValueGetter: (AggregationType<Any>) -> Any?,
    platformDataOriginsGetter: (AggregationType<Any>) -> Set<PlatformDataOrigin> = { _ ->
        emptySet()
    }
): AggregationResult {
    val metricValueMap = buildMap {
        metrics.forEach { metric ->
            aggregationValueGetter(metric.toAggregationType())?.also { this[metric] = it }
        }
    }
    return AggregationResult(
        getLongMetricValues(metricValueMap),
        getDoubleMetricValues(metricValueMap),
        metrics.flatMapTo(hashSetOf()) { metric ->
            platformDataOriginsGetter(metric.toAggregationType()).map { it.toSdkDataOrigin() }
        }
    )
}

@VisibleForTesting
internal fun getLongMetricValues(
    metricValueMap: Map<AggregateMetric<Any>, Any>
): Map<String, Long> {
    return buildMap {
        metricValueMap.forEach { (key, value) ->
            if (
                key in DURATION_AGGREGATION_METRIC_TYPE_MAP ||
                    key in LONG_AGGREGATION_METRIC_TYPE_MAP
            ) {
                this[key.metricKey] = value as Long
            }
        }
    }
}

@VisibleForTesting
internal fun getDoubleMetricValues(
    metricValueMap: Map<AggregateMetric<Any>, Any>
): Map<String, Double> {
    return buildMap {
        metricValueMap.forEach { (key, value) ->
            when (key) {
                in DOUBLE_AGGREGATION_METRIC_TYPE_MAP -> {
                    this[key.metricKey] = value as Double
                }
                in ENERGY_AGGREGATION_METRIC_TYPE_MAP -> {
                    this[key.metricKey] =
                        Energy.calories((value as PlatformEnergy).inCalories).inKilocalories
                }
                in LENGTH_AGGREGATION_METRIC_TYPE_MAP -> {
                    this[key.metricKey] = (value as PlatformLength).inMeters
                }
                in GRAMS_AGGREGATION_METRIC_TYPE_MAP -> {
                    this[key.metricKey] = (value as PlatformMass).inGrams
                }
                in KILOGRAMS_AGGREGATION_METRIC_TYPE_MAP -> {
                    this[key.metricKey] = Mass.grams((value as PlatformMass).inGrams).inKilograms
                }
                in POWER_AGGREGATION_METRIC_TYPE_MAP -> {
                    this[key.metricKey] = (value as PlatformPower).inWatts
                }
                in VOLUME_AGGREGATION_METRIC_TYPE_MAP -> {
                    this[key.metricKey] = (value as PlatformVolume).inLiters
                }
            }
        }
    }
}