/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.log4j.net;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

import org.apache.log4j.helpers.LogLog;

public class ZeroConfSupport {
    private static Object jmDNS = initializeJMDNS();

    Object serviceInfo;
    private static Class jmDNSClass;
    private static Class serviceInfoClass;

    public ZeroConfSupport(String zone, int port, String name, Map properties) {
	// if version 3 is available, use it to constuct a serviceInfo instance,
	// otherwise support the version1 API
	boolean isVersion3 = false;
	try {
	    // create method is in version 3, not version 1
	    jmDNSClass.getMethod("create", null);
	    isVersion3 = true;
	} catch (NoSuchMethodException e) {
	    // no-op
	}

	if (isVersion3) {
	    LogLog.debug("using JmDNS version 3 to construct serviceInfo instance");
	    serviceInfo = buildServiceInfoVersion3(zone, port, name, properties);
	} else {
	    LogLog.debug("using JmDNS version 1.0 to construct serviceInfo instance");
	    serviceInfo = buildServiceInfoVersion1(zone, port, name, properties);
	}
    }

    public ZeroConfSupport(String zone, int port, String name) {
	this(zone, port, name, new HashMap());
    }

    private static Object createJmDNSVersion1() {
	try {
	    return jmDNSClass.newInstance();
	} catch (InstantiationException e) {
	    LogLog.warn("Unable to instantiate JMDNS", e);
	} catch (IllegalAccessException e) {
	    LogLog.warn("Unable to instantiate JMDNS", e);
	}
	return null;
    }

    private static Object createJmDNSVersion3() {
	try {
	    Method jmDNSCreateMethod = jmDNSClass.getMethod("create", null);
	    return jmDNSCreateMethod.invoke(null, null);
	} catch (IllegalAccessException e) {
	    LogLog.warn("Unable to instantiate jmdns class", e);
	} catch (NoSuchMethodException e) {
	    LogLog.warn("Unable to access constructor", e);
	} catch (InvocationTargetException e) {
	    LogLog.warn("Unable to call constructor", e);
	}
	return null;
    }

    private Object buildServiceInfoVersion1(String zone, int port, String name, Map properties) {
	// version 1 uses a hashtable
	Hashtable hashtableProperties = new Hashtable(properties);
	try {
	    Class[] args = new Class[6];
	    args[0] = String.class;
	    args[1] = String.class;
	    args[2] = int.class;
	    args[3] = int.class; // weight (0)
	    args[4] = int.class; // priority (0)
	    args[5] = Hashtable.class;
	    Constructor constructor = serviceInfoClass.getConstructor(args);
	    Object[] values = new Object[6];
	    values[0] = zone;
	    values[1] = name;
	    values[2] = new Integer(port);
	    values[3] = new Integer(0);
	    values[4] = new Integer(0);
	    values[5] = hashtableProperties;
	    Object result = constructor.newInstance(values);
	    LogLog.debug("created serviceinfo: " + result);
	    return result;
	} catch (IllegalAccessException e) {
	    LogLog.warn("Unable to construct ServiceInfo instance", e);
	} catch (NoSuchMethodException e) {
	    LogLog.warn("Unable to get ServiceInfo constructor", e);
	} catch (InstantiationException e) {
	    LogLog.warn("Unable to construct ServiceInfo instance", e);
	} catch (InvocationTargetException e) {
	    LogLog.warn("Unable to construct ServiceInfo instance", e);
	}
	return null;
    }

    private Object buildServiceInfoVersion3(String zone, int port, String name, Map properties) {
	try {
	    Class[] args = new Class[6];
	    args[0] = String.class; // zone/type
	    args[1] = String.class; // display name
	    args[2] = int.class; // port
	    args[3] = int.class; // weight (0)
	    args[4] = int.class; // priority (0)
	    args[5] = Map.class;
	    Method serviceInfoCreateMethod = serviceInfoClass.getMethod("create", args);
	    Object[] values = new Object[6];
	    values[0] = zone;
	    values[1] = name;
	    values[2] = new Integer(port);
	    values[3] = new Integer(0);
	    values[4] = new Integer(0);
	    values[5] = properties;
	    Object result = serviceInfoCreateMethod.invoke(null, values);
	    LogLog.debug("created serviceinfo: " + result);
	    return result;
	} catch (IllegalAccessException e) {
	    LogLog.warn("Unable to invoke create method", e);
	} catch (NoSuchMethodException e) {
	    LogLog.warn("Unable to find create method", e);
	} catch (InvocationTargetException e) {
	    LogLog.warn("Unable to invoke create method", e);
	}
	return null;
    }

    public void advertise() {
	try {
	    Method method = jmDNSClass.getMethod("registerService", new Class[] { serviceInfoClass });
	    method.invoke(jmDNS, new Object[] { serviceInfo });
	    LogLog.debug("registered serviceInfo: " + serviceInfo);
	} catch (IllegalAccessException e) {
	    LogLog.warn("Unable to invoke registerService method", e);
	} catch (NoSuchMethodException e) {
	    LogLog.warn("No registerService method", e);
	} catch (InvocationTargetException e) {
	    LogLog.warn("Unable to invoke registerService method", e);
	}
    }

    public void unadvertise() {
	try {
	    Method method = jmDNSClass.getMethod("unregisterService", new Class[] { serviceInfoClass });
	    method.invoke(jmDNS, new Object[] { serviceInfo });
	    LogLog.debug("unregistered serviceInfo: " + serviceInfo);
	} catch (IllegalAccessException e) {
	    LogLog.warn("Unable to invoke unregisterService method", e);
	} catch (NoSuchMethodException e) {
	    LogLog.warn("No unregisterService method", e);
	} catch (InvocationTargetException e) {
	    LogLog.warn("Unable to invoke unregisterService method", e);
	}
    }

    private static Object initializeJMDNS() {
	try {
	    jmDNSClass = Class.forName("javax.jmdns.JmDNS");
	    serviceInfoClass = Class.forName("javax.jmdns.ServiceInfo");
	} catch (ClassNotFoundException e) {
	    LogLog.warn("JmDNS or serviceInfo class not found", e);
	}

	// if version 3 is available, use it to constuct a serviceInfo instance,
	// otherwise support the version1 API
	boolean isVersion3 = false;
	try {
	    // create method is in version 3, not version 1
	    jmDNSClass.getMethod("create", null);
	    isVersion3 = true;
	} catch (NoSuchMethodException e) {
	    // no-op
	}

	if (isVersion3) {
	    return createJmDNSVersion3();
	} else {
	    return createJmDNSVersion1();
	}
    }

    public static Object getJMDNSInstance() {
	return jmDNS;
    }
}
