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

#pragma once

#include <iostream>
#include <string>
#include <vector>

#include <gmock/gmock.h>
#include <stats.pb.h>

namespace android::net {

android::net::NetworkDnsEventReported fromNetworkDnsEventReportedStr(const std::string& str);

// Used for gmock printing our desired debug messages. (Another approach is defining operator<<())
void PrintTo(const DnsQueryEvents& event, std::ostream* os);
void PrintTo(const DnsQueryEvent& event, std::ostream* os);
void PrintTo(const android::net::NetworkDnsEventReported& event, std::ostream* os);
void PrintTo(const Servers& event, std::ostream* os);
void PrintTo(const Server& event, std::ostream* os);
void PrintTo(const NetworkDnsServerSupportReported& event, std::ostream* os);

MATCHER_P(DnsQueryEventEq, other, "") {
    return ::testing::ExplainMatchResult(
            ::testing::AllOf(
                    ::testing::Property("rcode", &DnsQueryEvent::rcode,
                                        ::testing::Eq(other.rcode())),
                    ::testing::Property("ns_type", &DnsQueryEvent::type,
                                        ::testing::Eq(other.type())),
                    ::testing::Property("cache_hit", &DnsQueryEvent::cache_hit,
                                        ::testing::Eq(other.cache_hit())),
                    ::testing::Property("ip_version", &DnsQueryEvent::ip_version,
                                        ::testing::Eq(other.ip_version())),
                    ::testing::Property("protocol", &DnsQueryEvent::protocol,
                                        ::testing::Eq(other.protocol())),
                    ::testing::Property("retry_times", &DnsQueryEvent::retry_times,
                                        ::testing::Eq(other.retry_times())),
                    ::testing::Property("dns_server_index", &DnsQueryEvent::dns_server_index,
                                        ::testing::Eq(other.dns_server_index())),
                    // Removing the latency check, because we can't predict the time.
                    /*  ::testing::Property("latency_micros", &DnsQueryEvent::latency_micros,
                             ::testing::Eq(other.latency_micros())),*/
                    ::testing::Property("linux_errno", &DnsQueryEvent::linux_errno,
                                        ::testing::Eq(other.linux_errno())),
                    ::testing::Property("connected", &DnsQueryEvent::connected,
                                        ::testing::Eq(other.connected()))),
            arg, result_listener);
}

MATCHER_P(DnsQueryEventsEq, other, "") {
    const int eventSize = arg.dns_query_event_size();
    if (eventSize != other.dns_query_event_size()) {
        *result_listener << "Expected dns query event size: " << other.dns_query_event_size()
                         << " \n";
        for (int i = 0; i < other.dns_query_event_size(); ++i)
            PrintTo(other.dns_query_event(i), result_listener->stream());
        *result_listener << "Actual dns query event size: " << eventSize << "\n";
        for (int i = 0; i < eventSize; ++i)
            PrintTo(arg.dns_query_event(i), result_listener->stream());
        return false;
    }

    for (int i = 0; i < eventSize; ++i) {
        bool result =
                ::testing::Value(arg.dns_query_event(i), DnsQueryEventEq(other.dns_query_event(i)));
        if (!result) {
            *result_listener << "Expected event num: " << i << " \n";
            PrintTo(arg.dns_query_event(i), result_listener->stream());
            *result_listener << "Actual event num: " << i << " ";
            PrintTo(other.dns_query_event(i), result_listener->stream());
            return false;
        }
    }
    return true;
}

MATCHER_P(NetworkDnsEventEq, other, "") {
    return ::testing::ExplainMatchResult(
            ::testing::AllOf(
                    // Removing following fields check, because we can't verify those fields in unit
                    // test.
                    /*
                    ::testing::Property("event_type",
                                        &android::net::NetworkDnsEventReported::event_type,
                                        ::testing::Eq(other.event_type())),
                    ::testing::Property("return_code",
                                        &android::net::NetworkDnsEventReported::return_code,
                                        ::testing::Eq(other.return_code())),
                    ::testing::Property("latency_micros",
                       &android::net::NetworkDnsEventReported::latency_micros,
                              ::testing::Eq(other.latency_micros())),
                    ::testing::Property("hints_ai_flags",
                                        &android::net::NetworkDnsEventReported::hints_ai_flags,
                                        ::testing::Eq(other.hints_ai_flags())),
                    ::testing::Property("res_nsend_flags",
                                        &android::net::NetworkDnsEventReported::res_nsend_flags,
                                        ::testing::Eq(other.res_nsend_flags())),
                    ::testing::Property("network_type",
                                        &android::net::NetworkDnsEventReported::network_type,
                                        ::testing::Eq(other.network_type())),
                    ::testing::Property("private_dns_modes",
                                        &android::net::NetworkDnsEventReported::private_dns_modes,
                                        ::testing::Eq(other.private_dns_modes())),
                    */
                    ::testing::Property("dns_query_events",
                                        &android::net::NetworkDnsEventReported::dns_query_events,
                                        DnsQueryEventsEq(other.dns_query_events())),
                    ::testing::Property("uid", &android::net::NetworkDnsEventReported::uid,
                                        ::testing::Eq(other.uid()))),
            arg, result_listener);
}

MATCHER_P(ServerEq, other, "") {
    return ::testing::ExplainMatchResult(
            ::testing::AllOf(
                    ::testing::Property("protocol", &Server::protocol,
                                        ::testing::Eq(other.protocol())),
                    ::testing::Property("index", &Server::index, ::testing::Eq(other.index())),
                    ::testing::Property("validated", &Server::validated,
                                        ::testing::Eq(other.validated()))),
            arg, result_listener);
}

MATCHER_P(ServersEq, other, "") {
    const int server_size = arg.server_size();
    if (server_size != other.server_size()) {
        return false;
    }

    for (int i = 0; i < server_size; ++i) {
        if (!::testing::Value(arg.server(i), ServerEq(other.server(i)))) {
            *result_listener << "Expected server num: " << i << " \n";
            PrintTo(arg.server(i), result_listener->stream());
            *result_listener << "Actual server num: " << i << " ";
            PrintTo(other.server(i), result_listener->stream());
            return false;
        }
    }
    return true;
}

MATCHER_P(NetworkDnsServerSupportEq, other, "") {
    return ::testing::ExplainMatchResult(
            ::testing::AllOf(
                    ::testing::Property("network_type",
                                        &NetworkDnsServerSupportReported::network_type,
                                        ::testing::Eq(other.network_type())),
                    ::testing::Property("private_dns_modes",
                                        &NetworkDnsServerSupportReported::private_dns_modes,
                                        ::testing::Eq(other.private_dns_modes())),
                    ::testing::Property("servers", &NetworkDnsServerSupportReported::servers,
                                        ServersEq(other.servers()))),
            arg, result_listener);
}

}  // namespace android::net
