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

#pragma once

#include <android/util/ProtoOutputStream.h>
#include <gtest/gtest_prod.h>
#include <kll.h>

#include <optional>

#include "MetricProducer.h"
#include "ValueMetricProducer.h"
#include "condition/ConditionTimer.h"
#include "condition/ConditionTracker.h"
#include "matchers/EventMatcherWizard.h"
#include "src/statsd_config.pb.h"
#include "stats_log_util.h"

using dist_proc::aggregation::KllQuantile;

namespace android {
namespace os {
namespace statsd {

// Uses KllQuantile to aggregate values within buckets.
//
// There are different events that might complete a bucket
// - a condition change
// - an app upgrade
// - an alarm set to the end of the bucket
class KllMetricProducer : public ValueMetricProducer<std::unique_ptr<KllQuantile>, Empty> {
public:
    KllMetricProducer(const ConfigKey& key, const KllMetric& kllMetric, const uint64_t protoHash,
                      const PullOptions& pullOptions, const BucketOptions& bucketOptions,
                      const WhatOptions& whatOptions, const ConditionOptions& conditionOptions,
                      const StateOptions& stateOptions, const ActivationOptions& activationOptions,
                      const GuardrailOptions& guardrailOptions,
                      const wp<ConfigMetadataProvider> configMetadataProvider);

    inline MetricType getMetricType() const override {
        return METRIC_TYPE_KLL;
    }

protected:
private:
    inline optional<int64_t> getConditionIdForMetric(const StatsdConfig& config,
                                                     const int configIndex) const override {
        const KllMetric& metric = config.kll_metric(configIndex);
        return metric.has_condition() ? make_optional(metric.condition()) : nullopt;
    }

    inline int64_t getWhatAtomMatcherIdForMetric(const StatsdConfig& config,
                                                 const int configIndex) const override {
        return config.kll_metric(configIndex).what();
    }

    inline ConditionLinks getConditionLinksForMetric(const StatsdConfig& config,
                                                     const int configIndex) const override {
        return config.kll_metric(configIndex).links();
    }

    // Determine whether or not a LogEvent can be skipped.
    inline bool canSkipLogEventLocked(
            const MetricDimensionKey& eventKey, bool condition, int64_t eventTimeNs,
            const std::map<int, HashableDimensionKey>& statePrimaryKeys) const override {
        // Can only skip if the condition is false.
        // We assume metric is pushed since KllMetric doesn't support pulled metrics.
        return !condition;
    }

    DumpProtoFields getDumpProtoFields() const override;

    inline std::string aggregatedValueToString(
            const std::unique_ptr<KllQuantile>& aggregate) const override {
        return std::to_string(aggregate->num_values()) + " values";
    }

    inline bool multipleBucketsSkipped(const int64_t numBucketsForward) const override {
        // Always false because we assume KllMetric is pushed only for now.
        return false;
    }

    // The KllQuantile ptr ownership is transferred to newly created PastBuckets from Intervals.
    PastBucket<std::unique_ptr<KllQuantile>> buildPartialBucket(
            int64_t bucketEndTime, std::vector<Interval>& intervals) override;

    void writePastBucketAggregateToProto(const int aggIndex,
                                         const std::unique_ptr<KllQuantile>& kll,
                                         const int sampleSize,
                                         ProtoOutputStream* const protoOutput) const override;

    size_t getAggregatedValueSize(const std::unique_ptr<KllQuantile>& kll) const override;

    bool aggregateFields(const int64_t eventTimeNs, const MetricDimensionKey& eventKey,
                         const LogEvent& event, std::vector<Interval>& intervals,
                         Empty& empty) override;

    // Internal function to calculate the current used bytes.
    size_t byteSizeLocked() const override;

    DataCorruptionSeverity determineCorruptionSeverity(int32_t atomId, DataCorruptedReason reason,
                                                       LostAtomType atomType) const override;

    FRIEND_TEST(KllMetricProducerTest, TestByteSize);
    FRIEND_TEST(KllMetricProducerTest, TestPushedEventsWithoutCondition);
    FRIEND_TEST(KllMetricProducerTest, TestPushedEventsWithCondition);
    FRIEND_TEST(KllMetricProducerTest, TestForcedBucketSplitWhenConditionUnknownSkipsBucket);

    FRIEND_TEST(KllMetricProducerTest_BucketDrop, TestInvalidBucketWhenConditionUnknown);
    FRIEND_TEST(KllMetricProducerTest_BucketDrop, TestBucketDropWhenBucketTooSmall);
    FRIEND_TEST(KllMetricProducerTest_BucketDrop, TestBucketDropWhenDataUnavailable);

    FRIEND_TEST(KllMetricProducerTest_PartialBucket, TestPushedEventsMultipleBuckets);

    FRIEND_TEST(ConfigUpdateTest, TestUpdateKllMetrics);

    FRIEND_TEST(MetricsManagerUtilDimLimitTest, TestDimLimit);

    FRIEND_TEST(ConfigUpdateDimLimitTest, TestDimLimit);
};

}  // namespace statsd
}  // namespace os
}  // namespace android
