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

#include <binder/app_launch_event.h>

#include <gtest/gtest.h>

// TODO: move to app_launch_event.h
#include <google/protobuf/util/message_differencer.h>

using namespace iorap::binder;  // NOLINT

using android::Parcel;

using Type = AppLaunchEvent::Type;
using Temperature = AppLaunchEvent::Temperature;

namespace iorap::binder {

inline bool ProtosEqual(const ::google::protobuf::Message& lhs,
                        const ::google::protobuf::Message& rhs) {
  return ::google::protobuf::util::MessageDifferencer::Equals(lhs, rhs);
}

inline bool ProtosEqual(const ::google::protobuf::MessageLite& lhs,
                        const ::google::protobuf::MessageLite& rhs) {
  // MessageLite does not support 'MessageDifferencer' which requires protobuf-full
  // because it uses reflection.
  //
  // Serialize as a string and compare. This may lead to false inequality when protobufs
  // are actually the same but their encodings are slightly different.
  return lhs.GetTypeName() == rhs.GetTypeName()
      && lhs.SerializeAsString() == rhs.SerializeAsString();
}

template <typename T>
inline bool ProtoPointersEqual(const T& lhs_ptr, const T& rhs_ptr) {
  if (lhs_ptr == nullptr && rhs_ptr == nullptr) {
    return true;
  }
  else if (lhs_ptr != nullptr && rhs_ptr != nullptr) {
    return ProtosEqual(*lhs_ptr, *rhs_ptr);
  }
  return false;
}

// Field-by-field equality.
// Protos are compared according by checking that their serialized encodings are the same.
inline bool operator==(const AppLaunchEvent& lhs, const AppLaunchEvent& rhs) {
# define EQ_OR_RETURN(l, r, val) if (!(l.val == r.val)) { return false; }
# define PROTO_EQ_OR_RETURN(l, r, val) if (!ProtoPointersEqual(l.val, r.val)) { return false; }

  EQ_OR_RETURN(lhs, rhs, type);
  EQ_OR_RETURN(lhs, rhs, sequence_id);
  PROTO_EQ_OR_RETURN(lhs, rhs, intent_proto);
  EQ_OR_RETURN(lhs, rhs, temperature);
  PROTO_EQ_OR_RETURN(lhs, rhs, activity_record_proto);

# undef EQ_OR_RETURN
# undef PROTO_EQ_OR_RETURN

  return true;
}

inline bool operator!=(const AppLaunchEvent& lhs, const AppLaunchEvent& rhs) {
  return !(lhs == rhs);
}

static AppLaunchEvent MakeIntentStarted(int64_t sequence_id,
                                            // non-null
                                            std::unique_ptr<IntentProto> intent_proto) {
  DCHECK(intent_proto != nullptr);

  AppLaunchEvent e{Type::kIntentStarted, sequence_id, std::move(intent_proto)};
  return e;
}

static AppLaunchEvent MakeIntentFailed(int64_t sequence_id) {
  AppLaunchEvent e{Type::kIntentFailed, sequence_id};
  return e;
}

static AppLaunchEvent
MakeActivityLaunched(int64_t sequence_id,
                     Temperature temperature,
                     // non-null
                     std::unique_ptr<ActivityRecordProto> activity_record_proto) {
  DCHECK(activity_record_proto != nullptr);

  AppLaunchEvent e{Type::kActivityLaunched,
                   sequence_id,
                   /*intent_proto*/nullptr,
                   temperature,
                   std::move(activity_record_proto)};
  return e;
}

static AppLaunchEvent
MakeActivityLaunchCancelled(int64_t sequence_id,
                            // nullable
                            std::unique_ptr<ActivityRecordProto> activity_record_proto = nullptr) {
  AppLaunchEvent e{Type::kActivityLaunchCancelled,
                   sequence_id,
                   /*intent_proto*/nullptr,
                   Temperature::kUninitialized,
                   std::move(activity_record_proto)};
  return e;
}

static AppLaunchEvent
MakeActivityLaunchFinished(int64_t sequence_id,
                           // non-null
                           std::unique_ptr<ActivityRecordProto> activity_record_proto) {
  DCHECK(activity_record_proto != nullptr);
  AppLaunchEvent e{Type::kActivityLaunchFinished,
                   sequence_id,
                   /*intent_proto*/nullptr,
                   Temperature::kUninitialized,
                   std::move(activity_record_proto)};
  return e;
}

}  // namespace iorap::binder

auto MakeDummyIntent(std::string action = "package_name/.ClassName") {
  std::unique_ptr<IntentProto> dummy_intent{new IntentProto{}};
  dummy_intent->set_action(action);
  return dummy_intent;
}

auto MakeDummyActivityRecord(std::string title = "package_name/.ClassName") {
  std::unique_ptr<ActivityRecordProto> dummy{new ActivityRecordProto{}};

  dummy->mutable_identifier()->set_title(title);

  return dummy;
}

TEST(AppLaunchEventTest, Equals) {
  EXPECT_EQ(MakeIntentStarted(456, MakeDummyIntent()), MakeIntentStarted(456, MakeDummyIntent()));
  EXPECT_NE(MakeIntentStarted(45, MakeDummyIntent()), MakeIntentStarted(45, MakeDummyIntent("a")));

  EXPECT_EQ(MakeIntentFailed(123), MakeIntentFailed(123));
  EXPECT_NE(MakeIntentFailed(0), MakeIntentFailed(123));

  EXPECT_EQ((MakeActivityLaunched(3, Temperature::kHot, MakeDummyActivityRecord())),
            (MakeActivityLaunched(3, Temperature::kHot, MakeDummyActivityRecord())));
  EXPECT_NE((MakeActivityLaunched(3, Temperature::kHot, MakeDummyActivityRecord())),
            (MakeActivityLaunched(3, Temperature::kCold, MakeDummyActivityRecord())));
  EXPECT_NE((MakeActivityLaunched(3, Temperature::kHot, MakeDummyActivityRecord())),
            (MakeActivityLaunched(3, Temperature::kHot, MakeDummyActivityRecord("other title"))));

  EXPECT_EQ((MakeActivityLaunchCancelled(4)),
            (MakeActivityLaunchCancelled(4)));
  EXPECT_EQ((MakeActivityLaunchCancelled(4, MakeDummyActivityRecord())),
            (MakeActivityLaunchCancelled(4, MakeDummyActivityRecord())));
  EXPECT_NE((MakeActivityLaunchCancelled(4, MakeDummyActivityRecord())),
            (MakeActivityLaunchCancelled(4, MakeDummyActivityRecord("other"))));
  EXPECT_NE((MakeActivityLaunchCancelled(4, MakeDummyActivityRecord())),
            (MakeActivityLaunchCancelled(4)));

  EXPECT_EQ((MakeActivityLaunchFinished(5, MakeDummyActivityRecord())),
            (MakeActivityLaunchFinished(5, MakeDummyActivityRecord())));
  EXPECT_NE((MakeActivityLaunchFinished(5, MakeDummyActivityRecord())),
            (MakeActivityLaunchFinished(5, MakeDummyActivityRecord("other title"))));
}

template <typename T>
T ValueParcelRoundTrip(const T& value) {
  ::android::Parcel p;
  CHECK_EQ(value.writeToParcel(&p), ::android::NO_ERROR);

  T new_value;
  p.setDataPosition(0);
  CHECK_EQ(new_value.readFromParcel(&p), ::android::NO_ERROR);

  return new_value;
}

#define EXPECT_PARCELING_ROUND_TRIP(a) EXPECT_EQ((a), ValueParcelRoundTrip((a)))

TEST(AppLaunchEventTest, ParcelingRoundTrip) {
  EXPECT_PARCELING_ROUND_TRIP(MakeIntentStarted(456, MakeDummyIntent()));
  EXPECT_PARCELING_ROUND_TRIP(MakeIntentFailed(123));
  EXPECT_PARCELING_ROUND_TRIP(MakeActivityLaunched(3, Temperature::kHot, MakeDummyActivityRecord()));
  EXPECT_PARCELING_ROUND_TRIP(MakeActivityLaunchCancelled(4));
  EXPECT_PARCELING_ROUND_TRIP(MakeActivityLaunchCancelled(4, MakeDummyActivityRecord()));
  EXPECT_PARCELING_ROUND_TRIP(MakeActivityLaunchFinished(5, MakeDummyActivityRecord()));
}