package com.atlassian.stash.internal.spring.jmx;

import com.atlassian.stash.internal.annotation.Throttled;
import com.atlassian.stash.internal.concurrent.ClusterLockMXBeanAdapter;
import com.atlassian.stash.internal.concurrent.MonitoredClusterLockService;
import com.atlassian.stash.internal.concurrent.ThreadPoolMXBeanAdapter;
import com.atlassian.stash.internal.event.AsyncBatchingInvokersTransformer;
import com.atlassian.stash.internal.event.EventStatisticsMXBean;
import com.atlassian.stash.internal.event.EventThreadPoolExecutor;
import com.atlassian.stash.internal.event.TransactionAwareEventPublisher;
import com.atlassian.stash.internal.project.InternalProjectService;
import com.atlassian.stash.internal.project.ProjectMXBeanAdapter;
import com.atlassian.stash.internal.repository.InternalRepositoryService;
import com.atlassian.stash.internal.repository.RepositoryMXBeanAdapter;
import com.atlassian.stash.internal.scm.ScmMXBean;
import com.atlassian.stash.internal.throttle.InternalThrottleService;
import com.atlassian.stash.internal.throttle.TicketMXBeanAdapter;
import com.atlassian.stash.internal.throttle.TicketSummaryMXBeanAdapter;
import com.google.common.collect.ImmutableMap;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.jmx.internal.JmxServiceImpl;
import org.hibernate.stat.Statistics;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.support.RegistrationPolicy;

import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * Spring configuration for JMX MBean registration. These beans are only created if {@link JmxEnabledCondition JMX
 * is enabled}.
 *
 * @since 3.4
 */
@Configuration
@Conditional(JmxEnabledCondition.class)
public class JmxConfiguration {

    @Value("${event.dispatcher.queue.size}")
    private int queueCapacity;

    @Bean
    public MBeanExporter mbeanExporter(SessionFactoryImplementor sessionFactory,
                                       MonitoredClusterLockService clusterLockService,
                                       InternalThrottleService throttleService,
                                       InternalProjectService internalProjectService,
                                       InternalRepositoryService internalRepositoryService,
                                       ScmMXBean scmMXBean,
                                       EventThreadPoolExecutor eventThreadPoolExecutor,
                                       TransactionAwareEventPublisher eventPublisher,
                                       AsyncBatchingInvokersTransformer invokersTransformer,
                                       ScheduledThreadPoolExecutor scheduledThreadPoolExecutor,
                                       @Qualifier("ioPumpThreadPoolExecutor") ThreadPoolExecutor ioPumpThreadPoolExecutor) {
        //Hibernate's JMX support was partially refactored in 4.3 and no longer works directly, pending completion
        //of that refactor. That's currently _projected_ for inclusion in Hibernate 5, but bears monitoring. Until
        //then, we'll need to export their statistics manually.

        Statistics statistics = sessionFactory.getStatistics(); //Enabled in stash-context.xml if JMX is enabled
        String statisticsMBeanName = String.format(JmxServiceImpl.OBJ_NAME_TEMPLATE,
                AvailableSettings.JMX_DEFAULT_OBJ_NAME_DOMAIN,
                sessionFactory.getSettings().getSessionFactoryName(),
                Statistics.class.getName(),
                statistics.getClass().getName());

        EventStatisticsMXBean eventMXBean = new EventStatisticsMXBean(eventPublisher, eventThreadPoolExecutor,
                invokersTransformer, queueCapacity);

        MBeanExporter exporter = new MBeanExporter();
        exporter.setBeans(ImmutableMap.<String, Object>builder()
                .put("com.atlassian.bitbucket:name=Tickets", new TicketMXBeanAdapter(throttleService))
                .put("com.atlassian.bitbucket:name=CommandTickets", new TicketSummaryMXBeanAdapter(throttleService, Throttled.SCM_COMMAND))
                .put("com.atlassian.bitbucket:name=EventStatistics", eventMXBean)
                .put("com.atlassian.bitbucket:name=HostingTickets", new TicketSummaryMXBeanAdapter(throttleService, Throttled.SCM_HOSTING))
                .put("com.atlassian.bitbucket:name=ClusterLocks", new ClusterLockMXBeanAdapter(clusterLockService))
                .put("com.atlassian.bitbucket:name=Projects", new ProjectMXBeanAdapter(internalProjectService))
                .put("com.atlassian.bitbucket:name=Repositories", new RepositoryMXBeanAdapter(internalRepositoryService))
                .put("com.atlassian.bitbucket:name=ScmStatistics", scmMXBean)
                .put("com.atlassian.bitbucket.thread-pools:name=EventThreadPool", new ThreadPoolMXBeanAdapter(eventThreadPoolExecutor))
                .put("com.atlassian.bitbucket.thread-pools:name=IoPumpThreadPool", new ThreadPoolMXBeanAdapter(ioPumpThreadPoolExecutor))
                .put("com.atlassian.bitbucket.thread-pools:name=ScheduledThreadPool", new ThreadPoolMXBeanAdapter(scheduledThreadPoolExecutor))
                .put(statisticsMBeanName, statistics)
                .build());
        exporter.setRegistrationPolicy(RegistrationPolicy.REPLACE_EXISTING);

        return exporter;
    }
}