/*
 *   Copyright (c) 2021 Project CHIP Authors
 *   All rights reserved.
 *
 *   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.
 *
 */
package chip.platform;

import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.MulticastLock;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;

public class NsdManagerServiceResolver implements ServiceResolver {
  private static final String TAG = NsdManagerServiceResolver.class.getSimpleName();
  private static final long RESOLVE_SERVICE_TIMEOUT = 30000;
  private final NsdManager nsdManager;
  private MulticastLock multicastLock;
  private Handler mainThreadHandler;
  private List<NsdManager.RegistrationListener> registrationListeners = new ArrayList<>();

  public NsdManagerServiceResolver(Context context) {
    this.nsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
    this.mainThreadHandler = new Handler(Looper.getMainLooper());

    this.multicastLock =
        ((WifiManager) context.getSystemService(Context.WIFI_SERVICE))
            .createMulticastLock("chipMulticastLock");
    this.multicastLock.setReferenceCounted(true);
  }

  @Override
  public void resolve(
      final String instanceName,
      final String serviceType,
      final long callbackHandle,
      final long contextHandle,
      final ChipMdnsCallback chipMdnsCallback) {
    multicastLock.acquire();

    NsdServiceInfo serviceInfo = new NsdServiceInfo();
    serviceInfo.setServiceName(instanceName);
    serviceInfo.setServiceType(serviceType);
    Log.d(TAG, "Starting service resolution for '" + instanceName + "'");

    Runnable timeoutRunnable =
        new Runnable() {
          @Override
          public void run() {
            // Ensure we always release the multicast lock. It's possible that we release the
            // multicast lock here before ResolveListener returns, but since NsdManager has no API
            // to cancel service resolution, there's not much we can do here.
            if (multicastLock.isHeld()) {
              multicastLock.release();
            }
          }
        };

    this.nsdManager.resolveService(
        serviceInfo,
        new NsdManager.ResolveListener() {
          @Override
          public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
            Log.w(
                TAG,
                "Failed to resolve service '" + serviceInfo.getServiceName() + "': " + errorCode);
            chipMdnsCallback.handleServiceResolve(
                instanceName, serviceType, null, 0, callbackHandle, contextHandle);

            if (multicastLock.isHeld()) {
              multicastLock.release();
            }
            mainThreadHandler.removeCallbacks(timeoutRunnable);
          }

          @Override
          public void onServiceResolved(NsdServiceInfo serviceInfo) {
            Log.i(
                TAG,
                "Resolved service '"
                    + serviceInfo.getServiceName()
                    + "' to "
                    + serviceInfo.getHost());
            // TODO: Find out if DNS-SD results for Android should contain interface ID
            chipMdnsCallback.handleServiceResolve(
                instanceName,
                serviceType,
                serviceInfo.getHost().getHostAddress(),
                serviceInfo.getPort(),
                callbackHandle,
                contextHandle);

            if (multicastLock.isHeld()) {
              multicastLock.release();
            }
            mainThreadHandler.removeCallbacks(timeoutRunnable);
          }
        });
    mainThreadHandler.postDelayed(timeoutRunnable, RESOLVE_SERVICE_TIMEOUT);
  }

  @Override
  public void publish(
      String serviceName,
      String hostName,
      String type,
      int port,
      String[] textEntriesKeys,
      byte[][] textEntriesDatas,
      String[] subTypes) {
    NsdServiceInfo serviceInfo = new NsdServiceInfo();
    serviceInfo.setServiceName(serviceName);
    serviceInfo.setServiceType(type);
    serviceInfo.setPort(port);
    Log.i(TAG, "publish serviceName=" + serviceName + " type=" + type + " port=" + port);

    NsdManager.RegistrationListener registrationListener =
        new NsdManager.RegistrationListener() {
          @Override
          public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
            Log.w(
                TAG,
                "service " + serviceInfo.getServiceName() + " onRegistrationFailed:" + errorCode);
          }

          @Override
          public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
            Log.w(
                TAG,
                "service " + serviceInfo.getServiceName() + " onUnregistrationFailed:" + errorCode);
          }

          @Override
          public void onServiceRegistered(NsdServiceInfo serviceInfo) {
            Log.i(TAG, "service " + serviceInfo.getServiceName() + " onServiceRegistered");
          }

          @Override
          public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
            Log.i(TAG, "service " + serviceInfo.getServiceName() + " onServiceRegistered");
          }
        };
    registrationListeners.add(registrationListener);

    nsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener);
  }

  @Override
  public void removeServices() {
    for (NsdManager.RegistrationListener l : registrationListeners) {
      nsdManager.unregisterService(l);
    }
  }
}

@@ -4825,16 +4825,306 @@
 + port);
+%0A    int cnt = Math.min(textEntriesDatas.length, textEntriesKeys.length);%0A    for (int i = 0; i %3C cnt; i++) %7B%0A      String value = new String(textEntriesDatas%5Bi%5D);%0A      serviceInfo.setAttribute(textEntriesKeys%5Bi%5D, value);%0A      Log.i(TAG, %22     %22 + textEntriesKeys%5Bi%5D + %22=%22 + value);%0A    %7D
 %0A%0A    Ns

set TXT record on android (#12715)