// 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.

#pragma once

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <ostream>

#include "frameworks/proto_logging/stats/atoms.pb.h"
#include "frameworks/proto_logging/stats/attribution_node.pb.h"
#include "packages/modules/StatsD/statsd/src/shell/shell_data.pb.h"
#include "packages/modules/StatsD/statsd/src/stats_log.pb.h"

namespace android {
namespace os {
namespace statsd {

using PackageInfo = UidMapping_PackageInfoSnapshot_PackageInfo;

// clang-format off

#define PROPERTY_MATCHER(typename, propname, matcher)                               \
    testing::Property(#propname, &typename::propname, matcher(expected.propname()))

#define REPEATED_PROPERTY_MATCHER(typename, propname, matcher)                      \
    testing::Property(#propname, &typename::propname,                               \
            testing::Pointwise(matcher(), expected.propname()))

#define PROPERTY_EQ(typename, propname) PROPERTY_MATCHER(typename, propname, testing::Eq)

#define REPEATED_PROPERTY_EQ(typename, propname)               \
    REPEATED_PROPERTY_MATCHER(typename, propname, testing::Eq)

/**
 * Generate a GoogleTest equality matcher for a custom type specified by typename with the given
 * properties.
 *
 * Example: For the protos:
 *     message Bar {
 *         optional int32 aa = 1;
 *     }
 *     message Foo {
 *         optional int32 a = 1;
 *         repeated float b = 2;
 *         optional Bar bar = 3;
 *         repeated Bar repeated_bar = 4;
 *     }
 * This will generate equality matchers for them called EqBar() and EqFoo():
 *      EQ_MATCHER(Bar, PROPERTY_EQ(Bar, aa));
 *      EQ_MATCHER(Foo,
 *              PROPERTY_EQ(Foo, a),
 *              PROPERTY_EQ(Foo, b),
 *              PROPERTY_MATCHER(Foo, bar, EqBar),
 *              REPEATED_PROPERTY_MATCHER(Foo, repeated_bar, EqBar)
 *      );
 */
#define EQ_MATCHER(typename, properties...)                                                   \
    MATCHER(Eq##typename, " ") {                                                              \
        return testing::Matches(Eq##typename(std::get<1>(arg)))(std::get<0>(arg));            \
    }                                                                                         \
    MATCHER_P(Eq##typename, expected, testing::PrintToString(expected)) {                     \
        return testing::ExplainMatchResult(testing::AllOf(properties), arg, result_listener); \
    }

#define PROPERTY_PRINT(propname)                                                    \
    if (obj.has_##propname()) {                                                     \
        *os << #propname << ": " << testing::PrintToString(obj.propname()) << ", "; \
    }

#define REPEATED_PROPERTY_PRINT(propname)                                           \
    if (obj.propname##_size() > 0) {                                                \
        *os << #propname << ": " << testing::PrintToString(obj.propname()) << ", "; \
    }

/**
 * Generates a method that teaches GoogleTest how to print a custom type specified by typename
 * with the given properties.
 * The equality matcher generated by EQ_MATCHER for the given typename will use this generated
 * printer method to print the object when the matcher fails.
 *
 * Example: For the protos:
 *     message Bar {
 *         optional int32 aa = 1;
 *     }
 *     message Foo {
 *         optional int32 a = 1;
 *         repeated float b = 2;
 *         optional Bar bar = 3;
 *         repeated Bar repeated_bar = 4;
 *     }
 * This will generate printer methods for them:
 *      TYPE_PRINTER(Bar, PROPERTY_PRINT(aa));
 *      TYPE_PRINTER(Foo,
 *              PROPERTY_PRINT(a)
 *              PROPERTY_PRINT(b)
 *              PROPERTY_PRINT(bar)
 *              REPEATED_PROPERTY_PRINT(repeated_bar)
 *      );
 */
#define TYPE_PRINTER(typename, properties)                       \
    inline void PrintTo(const typename& obj, std::ostream* os) { \
        *os << #typename << ": { ";                              \
        properties                                               \
        *os << "}";                                              \
    }

EQ_MATCHER(PackageInfo,
        PROPERTY_EQ(PackageInfo, version),
        PROPERTY_EQ(PackageInfo, uid),
        PROPERTY_EQ(PackageInfo, deleted),
        PROPERTY_EQ(PackageInfo, truncated_certificate_hash),
        PROPERTY_EQ(PackageInfo, name_hash),
        PROPERTY_EQ(PackageInfo, version_string_hash),
        PROPERTY_EQ(PackageInfo, name),
        PROPERTY_EQ(PackageInfo, version_string),
        PROPERTY_EQ(PackageInfo, installer_index),
        PROPERTY_EQ(PackageInfo, installer_hash),
        PROPERTY_EQ(PackageInfo, installer)
);
TYPE_PRINTER(PackageInfo,
        PROPERTY_PRINT(version)
        PROPERTY_PRINT(uid)
        PROPERTY_PRINT(deleted)
        PROPERTY_PRINT(truncated_certificate_hash)
        PROPERTY_PRINT(name_hash)
        PROPERTY_PRINT(version_string_hash)
        PROPERTY_PRINT(name)
        PROPERTY_PRINT(version_string)
        PROPERTY_PRINT(installer_index)
        PROPERTY_PRINT(installer_hash)
        PROPERTY_PRINT(installer)
);

EQ_MATCHER(AttributionNode,
        PROPERTY_EQ(AttributionNode, uid),
        PROPERTY_EQ(AttributionNode, tag)
);
TYPE_PRINTER(AttributionNode,
        PROPERTY_PRINT(uid)
        PROPERTY_PRINT(tag)
);

EQ_MATCHER(ScreenStateChanged, PROPERTY_EQ(ScreenStateChanged, state));
TYPE_PRINTER(ScreenStateChanged, PROPERTY_PRINT(state));

EQ_MATCHER(TrainExperimentIds,
        REPEATED_PROPERTY_EQ(TrainExperimentIds, experiment_id)
);
TYPE_PRINTER(TrainExperimentIds, REPEATED_PROPERTY_PRINT(experiment_id));

EQ_MATCHER(TestAtomReported,
        REPEATED_PROPERTY_MATCHER(TestAtomReported, attribution_node, EqAttributionNode),
        PROPERTY_EQ(TestAtomReported, int_field),
        PROPERTY_EQ(TestAtomReported, long_field),
        PROPERTY_EQ(TestAtomReported, float_field),
        PROPERTY_EQ(TestAtomReported, string_field),
        PROPERTY_EQ(TestAtomReported, boolean_field),
        PROPERTY_EQ(TestAtomReported, state),
        PROPERTY_MATCHER(TestAtomReported, bytes_field, EqTrainExperimentIds),
        REPEATED_PROPERTY_EQ(TestAtomReported, repeated_int_field),
        REPEATED_PROPERTY_EQ(TestAtomReported, repeated_long_field),
        REPEATED_PROPERTY_EQ(TestAtomReported, repeated_float_field),
        REPEATED_PROPERTY_EQ(TestAtomReported, repeated_string_field),
        REPEATED_PROPERTY_EQ(TestAtomReported, repeated_boolean_field),
        REPEATED_PROPERTY_EQ(TestAtomReported, repeated_enum_field)
);
TYPE_PRINTER(TestAtomReported,
        REPEATED_PROPERTY_PRINT(attribution_node)
        PROPERTY_PRINT(int_field)
        PROPERTY_PRINT(long_field)
        PROPERTY_PRINT(float_field)
        PROPERTY_PRINT(string_field)
        PROPERTY_PRINT(boolean_field)
        PROPERTY_PRINT(state)
        PROPERTY_PRINT(bytes_field)
        REPEATED_PROPERTY_PRINT(repeated_int_field)
        REPEATED_PROPERTY_PRINT(repeated_long_field)
        REPEATED_PROPERTY_PRINT(repeated_float_field)
        REPEATED_PROPERTY_PRINT(repeated_string_field)
        REPEATED_PROPERTY_PRINT(repeated_boolean_field)
        REPEATED_PROPERTY_PRINT(repeated_enum_field)
);

EQ_MATCHER(CpuActiveTime,
        PROPERTY_EQ(CpuActiveTime, uid),
        PROPERTY_EQ(CpuActiveTime, time_millis)
);
TYPE_PRINTER(CpuActiveTime,
        PROPERTY_PRINT(uid)
        PROPERTY_PRINT(time_millis)
);

EQ_MATCHER(PluggedStateChanged, PROPERTY_EQ(PluggedStateChanged, state));
TYPE_PRINTER(PluggedStateChanged, PROPERTY_PRINT(state));

EQ_MATCHER(WakelockStateChanged,
        REPEATED_PROPERTY_MATCHER(WakelockStateChanged, attribution_node, EqAttributionNode),
        PROPERTY_EQ(WakelockStateChanged, type),
        PROPERTY_EQ(WakelockStateChanged, tag),
        PROPERTY_EQ(WakelockStateChanged, state),
        PROPERTY_EQ(WakelockStateChanged, process_state)
);
TYPE_PRINTER(WakelockStateChanged,
        REPEATED_PROPERTY_PRINT(attribution_node)
        PROPERTY_PRINT(type)
        PROPERTY_PRINT(tag)
        PROPERTY_PRINT(state)
        PROPERTY_PRINT(process_state)
);

EQ_MATCHER(Atom,
        PROPERTY_MATCHER(Atom, screen_state_changed, EqScreenStateChanged),
        PROPERTY_MATCHER(Atom, test_atom_reported, EqTestAtomReported),
        PROPERTY_MATCHER(Atom, wakelock_state_changed, EqWakelockStateChanged)
);
TYPE_PRINTER(Atom,
        PROPERTY_PRINT(screen_state_changed)
        PROPERTY_PRINT(test_atom_reported)
        PROPERTY_PRINT(wakelock_state_changed)
);

EQ_MATCHER(ShellData,
        REPEATED_PROPERTY_MATCHER(ShellData, atom, EqAtom),
        REPEATED_PROPERTY_EQ(ShellData, elapsed_timestamp_nanos)
);
TYPE_PRINTER(ShellData,
        REPEATED_PROPERTY_PRINT(atom)
        REPEATED_PROPERTY_PRINT(elapsed_timestamp_nanos)
);

// clang-format on

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