// Copyright 2010, 2011 The Apache Software Foundation
//
// 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 org.apache.tapestry5.internal.jmx;

import org.apache.tapestry5.ioc.annotations.PostInjection;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.ioc.internal.util.OneShotLock;
import org.apache.tapestry5.ioc.services.RegistryShutdownHub;
import org.apache.tapestry5.jmx.MBeanSupport;
import org.slf4j.Logger;

import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
import java.util.List;
import java.util.Set;

import static java.lang.String.format;

public class MBeanSupportImpl implements MBeanSupport
{
    private final Logger logger;

    private final MBeanServer server;

    private final OneShotLock lock = new OneShotLock();

    private final Set<ObjectName> registeredBeans = CollectionFactory.newSet();

    public MBeanSupportImpl(Logger logger)
    {
        this.logger = logger;

        // TODO: Agent Id should be configurable
        final List<MBeanServer> servers = MBeanServerFactory.findMBeanServer(null);

        MBeanServer server = null;

        if (servers != null && 0 < servers.size())
        {
            server = servers.get(0);
        }

        if (this.server == null)
        {
            server = ManagementFactory.getPlatformMBeanServer();
        }

        this.server = server;
    }

    @PostInjection
    public void listenForShutdown(RegistryShutdownHub hub)
    {
        hub.addRegistryShutdownListener(new Runnable()
        {
            @Override
            public void run()
            {
                registryDidShutdown();
            }
        });
    }

    @Override
    public void register(Object bean, String name)
    {
        register(bean, toObjectName(name));
    }

    private static ObjectName toObjectName(String name)
    {
        try
        {
            return new ObjectName(name);
        } catch (Exception ex)
        {
            throw new RuntimeException(ex);
        }
    }

    @Override
    public void register(final Object object, final ObjectName objectName)
    {
        lock.check();

        if (this.server.isRegistered(objectName))
            return;

        try
        {
            this.server.registerMBean(object, objectName);

            this.registeredBeans.add(objectName);

            this.logger.info(format("Registered MBean '%s' with server", objectName));
        } catch (final Exception e)
        {
            this.logger.error(format("Failed to register MBean '%s' with server", objectName), e);
        }
    }

    @Override
    public void unregister(final ObjectName objectName)
    {
        lock.check();

        doUnregister(objectName);
    }

    private void doUnregister(final ObjectName objectName)
    {
        if (this.server.isRegistered(objectName))
        {
            try
            {
                this.server.unregisterMBean(objectName);

                this.logger.info(format("Unregistered MBean '%s' from server", objectName));

                if (registeredBeans.contains(objectName))
                    registeredBeans.remove(objectName);
            } catch (final Exception e)
            {
                this.logger.error(String.format("Failed to unregister MBean '%s' from server", objectName), e);
            }
        }
    }

    private void registryDidShutdown()
    {
        lock.lock();
        // store into new data structure so we can remove them from registered beans
        ObjectName[] objects = registeredBeans.toArray(new ObjectName[registeredBeans.size()]);
        for (final ObjectName name : objects)
        {
            doUnregister(name);
        }

        this.registeredBeans.clear();

    }
}
