/*
 * Copyright (C) 2025 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 "discovered_services.h"

#include "adb_trace.h"

#include <algorithm>

std::ostream& operator<<(std::ostream& os, const ServiceInfo& info) {
    os << info.instance << "." << info.service << ":" << info.port << "";
    return os;
}

// Parse a key/value from a TXT record. Format expected is "key=value"
std::tuple<bool, std::string, std::string> ServiceInfo::ParseTxtKeyValue(const std::string& kv) {
    auto split_loc = std::ranges::find(kv, static_cast<uint8_t>('='));
    if (split_loc == kv.end()) {
        return {false, "", ""};
    }
    std::string key;
    std::string value;

    key.assign(kv.begin(), split_loc);
    if (split_loc + 1 != kv.end()) {
        value.assign(split_loc + 1, kv.end());
    }

    if (key.empty()) {
        return {false, key, value};
    }
    return {true, key, value};
}

std::unordered_map<std::string, std::string> ServiceInfo::ParseTxt(
        const std::vector<std::vector<uint8_t>>& txt) {
    std::unordered_map<std::string, std::string> kv;
    for (auto& in_kv : txt) {
        std::string skv = std::string(in_kv.begin(), in_kv.end());
        auto [valid, key, value] = ParseTxtKeyValue(skv);
        if (!valid) {
            VLOG(MDNS) << "Bad TXT value '" << skv << "'";
            continue;
        }
        kv[key] = value;
    }
    return kv;
}

namespace mdns {
DiscoveredServices discovered_services [[clang::no_destroy]];

static std::string fq_name(const ServiceInfo& si) {
    return fmt::format("{}.{}", si.instance, si.service);
}

void DiscoveredServices::ServiceCreated(const ServiceInfo& service_info) {
    std::lock_guard lock(services_mutex_);
    VLOG(MDNS) << "Service created " << service_info;
    services_[fq_name(service_info)] = service_info;
}

bool DiscoveredServices::ServiceUpdated(const ServiceInfo& service_info) {
    std::lock_guard lock(services_mutex_);

    const auto key = fq_name(service_info);
    if (!services_.contains(key)) {
        services_[key] = service_info;
        return true;
    }

    auto& current_service = services_[key];
    bool updated = false;

    if (service_info.v4_address.has_value() &&
        service_info.v4_address != current_service.v4_address) {
        current_service.v4_address = service_info.v4_address;
        updated = true;
    }

    for (auto& new_address : service_info.v6_addresses) {
        if (!current_service.v6_addresses.contains(new_address)) {
            updated = true;
            current_service.v6_addresses.insert(new_address);
        }
    }

    if (service_info.port != current_service.port) {
        current_service.port = service_info.port;
        updated = true;
    }

    if (service_info.attributes != current_service.attributes) {
        current_service.attributes = service_info.attributes;
        updated = true;
    }

    if (updated) {
        VLOG(MDNS) << "Service update " << service_info;
    }

    return updated;
}

void DiscoveredServices::ServiceDeleted(const ServiceInfo& service_info) {
    std::lock_guard lock(services_mutex_);
    VLOG(MDNS) << "Service deleted " << service_info;
    services_.erase(fq_name(service_info));
}

std::optional<ServiceInfo> DiscoveredServices::FindInstance(const std::string& service,
                                                            const std::string& instance) {
    std::lock_guard lock(services_mutex_);
    std::string fully_qualified_name = fmt::format("{}.{}", instance, service);
    if (!services_.contains(fully_qualified_name)) {
        return {};
    }
    return services_[fully_qualified_name];
}

void DiscoveredServices::ForEachServiceNamed(
        const std::string& service_name, const std::function<void(const ServiceInfo&)>& callback) {
    std::lock_guard lock(services_mutex_);
    for (const auto& [_, value] : services_) {
        if (value.service != service_name) {
            continue;
        }
        callback(value);
    }
}
void DiscoveredServices::ForAllServices(const std::function<void(const ServiceInfo&)>& callback) {
    std::lock_guard lock(services_mutex_);
    for (const auto& [_, value] : services_) {
        callback(value);
    }
}
}  // namespace mdns
