/*
 * Copyright (C) 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.
 */

package com.android.server.healthconnect.fitness.utils;

import static com.android.server.healthconnect.fitness.recordhelpers.ActiveCaloriesBurnedRecordHelper.ACTIVE_CALORIES_BURNED_RECORD_TABLE_NAME;
import static com.android.server.healthconnect.fitness.recordhelpers.ActiveCaloriesBurnedRecordHelper.ENERGY_COLUMN_NAME;
import static com.android.server.healthconnect.fitness.recordhelpers.BasalMetabolicRateRecordHelper.BASAL_METABOLIC_RATE_COLUMN_NAME;
import static com.android.server.healthconnect.fitness.recordhelpers.BasalMetabolicRateRecordHelper.BASAL_METABOLIC_RATE_RECORD_TABLE_NAME;
import static com.android.server.healthconnect.fitness.recordhelpers.InstantRecordHelper.LOCAL_DATE_TIME_COLUMN_NAME;
import static com.android.server.healthconnect.fitness.recordhelpers.InstantRecordHelper.TIME_COLUMN_NAME;
import static com.android.server.healthconnect.fitness.recordhelpers.IntervalRecordHelper.LOCAL_DATE_TIME_START_TIME_COLUMN_NAME;
import static com.android.server.healthconnect.fitness.recordhelpers.IntervalRecordHelper.START_TIME_COLUMN_NAME;
import static com.android.server.healthconnect.storage.utils.WhereClauses.LogicalOperator.AND;

import android.database.Cursor;
import android.util.Pair;

import com.android.server.healthconnect.storage.TransactionManager;
import com.android.server.healthconnect.storage.request.ReadTableRequest;
import com.android.server.healthconnect.storage.utils.OrderByClause;
import com.android.server.healthconnect.storage.utils.WhereClauses;

import java.time.Instant;
import java.util.List;
import java.util.Objects;

/**
 * Helper class for deriving TotalCaloriesBurned aggregate from {@link
 * android.health.connect.datatypes.BasalMetabolicRateRecord} and {@link
 * android.health.connect.datatypes.ActiveCaloriesBurnedRecord}
 *
 * @hide
 */
public final class DeriveTotalCaloriesBurnedUtil {
    private final long mStartTime;
    private final long mEndTime;
    private final List<Long> mPriority;
    private Cursor mActiveCaloriesBurnedCursor;
    private Cursor mBasalCaloriesBurnedCursor;
    private MergeDataUtil mMergeDataUtil;
    private DeriveBasalCaloriesBurnedUtil mBasalCaloriesBurnedHelper;
    private final TransactionManager mTransactionManager;

    private final String mInstantRecordTimeColumnName;

    private final String mIntervalStartTimeColumnName;

    private final boolean mUseLocalTime;

    public DeriveTotalCaloriesBurnedUtil(
            long startTime,
            long endTime,
            List<Long> priorityList,
            boolean useLocaleTime,
            TransactionManager transactionManager) {
        Objects.requireNonNull(priorityList);
        mStartTime = startTime;
        mEndTime = endTime;
        mPriority = priorityList;
        mUseLocalTime = useLocaleTime;
        if (useLocaleTime) {
            mInstantRecordTimeColumnName = LOCAL_DATE_TIME_COLUMN_NAME;
            mIntervalStartTimeColumnName = LOCAL_DATE_TIME_START_TIME_COLUMN_NAME;
        } else {
            mInstantRecordTimeColumnName = TIME_COLUMN_NAME;
            mIntervalStartTimeColumnName = START_TIME_COLUMN_NAME;
        }
        mTransactionManager = transactionManager;
        inititalizeCursors();
    }

    private void inititalizeCursors() {
        mActiveCaloriesBurnedCursor =
                mTransactionManager.read(
                        new ReadTableRequest(ACTIVE_CALORIES_BURNED_RECORD_TABLE_NAME)
                                .setWhereClause(
                                        new WhereClauses(AND)
                                                .addWhereBetweenTimeClause(
                                                        mIntervalStartTimeColumnName,
                                                        mStartTime,
                                                        mEndTime))
                                .setOrderBy(
                                        new OrderByClause()
                                                .addOrderByClause(
                                                        mIntervalStartTimeColumnName, true)));
        mBasalCaloriesBurnedCursor =
                mTransactionManager.read(
                        new ReadTableRequest(BASAL_METABOLIC_RATE_RECORD_TABLE_NAME)
                                .setWhereClause(
                                        new WhereClauses(AND)
                                                .addWhereBetweenTimeClause(
                                                        mInstantRecordTimeColumnName,
                                                        mStartTime,
                                                        mEndTime))
                                .setOrderBy(
                                        new OrderByClause()
                                                .addOrderByClause(
                                                        mInstantRecordTimeColumnName, true)));
        mMergeDataUtil =
                new MergeDataUtil(mPriority, ENERGY_COLUMN_NAME, Double.class, mUseLocalTime);
        mBasalCaloriesBurnedHelper =
                new DeriveBasalCaloriesBurnedUtil(
                        mBasalCaloriesBurnedCursor,
                        BASAL_METABOLIC_RATE_COLUMN_NAME,
                        mInstantRecordTimeColumnName,
                        mTransactionManager);
    }

    /** Close the cursors created */
    public void closeCursors() {
        if (mActiveCaloriesBurnedCursor != null) {
            mActiveCaloriesBurnedCursor.close();
        }
        if (mBasalCaloriesBurnedCursor != null) {
            mBasalCaloriesBurnedCursor.close();
        }
    }

    /**
     * Calculates and returns total derived calories for the empty interval time gaps where there is
     * no entry in {@link android.health.connect.datatypes.TotalCaloriesBurnedRecord}
     */
    public double getDerivedCalories(List<Pair<Instant, Instant>> emptyIntervalList) {
        double totalDerivedCalories = 0.0;
        for (Pair<Instant, Instant> instantInstantPair : emptyIntervalList) {
            long intervalStartTime = instantInstantPair.first.toEpochMilli();
            long intervalEndTime = instantInstantPair.second.toEpochMilli();
            totalDerivedCalories +=
                    mMergeDataUtil
                                    .readCursor(
                                            mActiveCaloriesBurnedCursor,
                                            intervalStartTime,
                                            intervalEndTime)
                                    .getTotal()
                            + mBasalCaloriesBurnedHelper.getBasalCaloriesBurned(
                                    intervalStartTime, intervalEndTime);
        }
        return totalDerivedCalories;
    }
}
