package com.maxifier.guice.mbean;

import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PreDestroy;
import javax.management.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;

@Singleton
public class MBeanManagerImpl implements MBeanManager {
    private static Logger logger = LoggerFactory.getLogger(MBeanManagerImpl.class);

    private final Collection<ObjectName> mbeans = new HashSet<ObjectName>();
    private final String domain;
    private final MBeanGenerator mbeanGenerator;
    private final MBeanServer mbeanServer;

    public MBeanManagerImpl(String domain,
                            MBeanServer mbeanServer, MBeanGenerator mBeanGenerator) {
        this.mbeanServer = mbeanServer;
        //add current time
        this.domain = domain;
        this.mbeanGenerator = mBeanGenerator;
        addShutdownHook();
    }

    @Override
    public synchronized void register(Object... mbean) {
        if (mbean.length > 0) {
            register(Arrays.asList(mbean));
        }
    }

    @Override
    public void register(Iterable<Object> mbeans) {
        for (Object mbean : mbeans) {
            String name = resolveName(mbean);
            register(name, mbean);
        }
    }

    @Override
    public synchronized void register(String name, Object mbeanPretender) {
        try {
            ObjectName objectName = prepareObjectName(name);
            Object mbean = makeMBean(mbeanPretender);
            try {
                mbeanServer.registerMBean(mbean, objectName);
            } catch (InstanceAlreadyExistsException e) {
                logger.warn("Instance with name {} is already exists, second instance will be registered instead of first", name);
                try {
                    mbeanServer.unregisterMBean(objectName);
                } catch (InstanceNotFoundException e1) {
                    //NOP
                }
                mbeanServer.registerMBean(mbean, objectName);
            }
            mbeans.add(objectName);
        } catch (MalformedObjectNameException e) {                    //TODO (didik) object info into warns
            logger.warn("Unable to register mbean, wrong name", e);
        } catch (MBeanRegistrationException e) {
            logger.warn("Unable to register mbean", e);
        } catch (NotCompliantMBeanException e) {
            logger.warn("Unable to register mbean, wrong mbean class", e);
        } catch (InstanceAlreadyExistsException e) {
            logger.warn("Unable to register mbean, instance already exists", e);
        } catch (MBeanGenerationException e) {
            logger.warn("Unable to register mbean, instance is not compliant with JMX spec and mbean generation was failed", e);
        }
    }

    Object makeMBean(Object mbeanPretender) throws MBeanGenerationException {
        return checkCompliantion(mbeanPretender) ? mbeanPretender : mbeanGenerator.makeMBean(mbeanPretender);
    }

    @Override
    public synchronized void unregister(String... name) {
        if (name.length > 0) {
            unregister(Arrays.asList(name));
        }
    }

    @Override
    public void unregister(Iterable<String> names) {
        for (String name : names) {
            try {
                unregisterMBean(prepareObjectName(name));
            } catch (MalformedObjectNameException e) {
                logger.error("MBean unregistration error", e);
            }
        }
    }

    @PreDestroy
    //it's here for supply lifecycle
    public synchronized void unregisterAll() {
        //new set to prevent CME
        for (ObjectName objectName : new HashSet<ObjectName>(mbeans)) {
            unregisterMBean(objectName);
        }
    }

    String resolveName(Object mbean) {
        String name = null;
        Class<?> mbeanClass = mbean.getClass();
        MBean mbeanAnnotation = mbeanClass.getAnnotation(MBean.class);
        if (mbeanAnnotation != null) {
            name = mbeanAnnotation.name();
        } else {
            //check old name
            com.magenta.guice.mbean.MBean oldAnnotation = mbeanClass.getAnnotation(com.magenta.guice.mbean.MBean.class);
            if (oldAnnotation != null) {
                name = oldAnnotation.name();
            }
        }
        if (isBlank(name)) {
            name = makeDefaultName(mbeanClass);
        }
        return checkAlreadyDomained(name);
    }

    String checkAlreadyDomained(String mbeanName) {
        final int index = mbeanName.indexOf(':');
        if (index > -1) {
            logger.warn("Managed bean name already include domain." +
                    " MBeanManager domain '{}' will be used instead.", domain);
            return mbeanName.substring(index + 1);
        }
        return mbeanName;
    }


    String makeDefaultName(Class<?> mbeanClass) {
        return String.format("class=%s", mbeanClass.getName());
    }

    ObjectName prepareObjectName(String name) throws MalformedObjectNameException {
        return new ObjectName(String.format("%s:%s", domain, name));
    }

    private boolean isBlank(String name) {
        return name == null || name.trim().isEmpty();
    }

    private void unregisterMBean(ObjectName objectName) {
        try {
            mbeanServer.unregisterMBean(objectName);
        } catch (Exception e) {
            logger.warn("MBean unregistration error", e);
        }
        mbeans.remove(objectName);
    }

    private void addShutdownHook() {
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                unregister();
            }
        }));
    }

    public static boolean checkCompliantion(Object mbeanPretender) {
        Class<?> mbeanPretenderClass = mbeanPretender.getClass();
        String mbeanPretenderInterfaceName = mbeanPretenderClass.getName() + "MBean";
        Class<?>[] mbeanPretenderClassInterfaces = mbeanPretenderClass.getInterfaces();
        for (Class<?> mbeanPretenderClassInterface : mbeanPretenderClassInterfaces) {
            if (mbeanPretenderClassInterface.getName().equals(mbeanPretenderInterfaceName)) {
                return true;
            }
        }
        return false;
    }
}


@@ -2192,65 +2192,8 @@
 e) %7B
-                    //TODO (didik) object info into warns
 %0D%0A  
@@ -2206,32 +2206,46 @@
     logger.warn(
+String.format(
 %22Unable to regis
@@ -2240,32 +2240,35 @@
 ble to register 
+%25s 
 mbean, wrong nam
@@ -2270,16 +2270,33 @@
 g name%22,
+ mbeanPretender),
  e);%0D%0A  
@@ -2359,32 +2359,46 @@
     logger.warn(
+String.format(
 %22Unable to regis
@@ -2406,17 +2406,37 @@
 er mbean
-%22
+ %25s%22, mbeanPretender)
 , e);%0D%0A 
@@ -2500,32 +2500,46 @@
     logger.warn(
+String.format(
 %22Unable to regis
@@ -2543,24 +2543,27 @@
 gister mbean
+ %25s
 , wrong mbea
@@ -2571,16 +2571,33 @@
  class%22,
+ mbeanPretender),
  e);%0D%0A  
@@ -2664,32 +2664,46 @@
     logger.warn(
+String.format(
 %22Unable to regis
@@ -2703,32 +2703,35 @@
 o register mbean
+ %25s
 , instance alrea
@@ -2741,16 +2741,33 @@
 exists%22,
+ mbeanPretender),
  e);%0D%0A  
@@ -2836,16 +2836,30 @@
 er.warn(
+String.format(
 %22Unable 
@@ -2875,16 +2875,19 @@
 er mbean
+ %25s
 , instan
@@ -2953,16 +2953,33 @@
 failed%22,
+ mbeanPretender),
  e);%0D%0A  

add information about mbeans into warnings about exceptions