/*
 * Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.jmx;

import java.lang.management.ManagementFactory;

import java.sql.Timestamp;
import java.util.Hashtable;
import java.util.Map;
import java.util.TreeMap;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import org.h2.command.Command;
import org.h2.engine.ConnectionInfo;
import org.h2.engine.Constants;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.store.PageStore;
import org.h2.table.Table;
import org.h2.util.New;

/**
 * The MBean implementation.
 *
 * @author Eric Dong
 * @author Thomas Mueller
 */
public class DatabaseInfo implements DatabaseInfoMBean {

    private static final Map<String, ObjectName> MBEANS = New.hashMap();

    /** Database. */
    private final Database database;

    private DatabaseInfo(Database database) {
        if (database == null) {
            throw new IllegalArgumentException("Argument 'database' must not be null");
        }
        this.database = database;
    }

    /**
     * Returns a JMX new ObjectName instance.
     *
     * @param name name of the MBean
     * @param path the path
     * @return a new ObjectName instance
     * @throws JMException if the ObjectName could not be created
     */
    private static ObjectName getObjectName(String name, String path)
            throws JMException {
        name = name.replace(':', '_');
        path = path.replace(':', '_');
        Hashtable<String, String> map = new Hashtable<>();
        map.put("name", name);
        map.put("path", path);
        return new ObjectName("org.h2", map);
    }

    /**
     * Registers an MBean for the database.
     *
     * @param connectionInfo connection info
     * @param database database
     */
    public static void registerMBean(ConnectionInfo connectionInfo,
            Database database) throws JMException {
        String path = connectionInfo.getName();
        if (!MBEANS.containsKey(path)) {
            MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
            String name = database.getShortName();
            ObjectName mbeanObjectName = getObjectName(name, path);
            MBEANS.put(path, mbeanObjectName);
            DatabaseInfo info = new DatabaseInfo(database);
            Object mbean = new DocumentedMBean(info, DatabaseInfoMBean.class);
            mbeanServer.registerMBean(mbean, mbeanObjectName);
        }
    }

    /**
     * Unregisters the MBean for the database if one is registered.
     *
     * @param name database name
     */
    public static void unregisterMBean(String name) throws Exception {
        ObjectName mbeanObjectName = MBEANS.remove(name);
        if (mbeanObjectName != null) {
            MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
            mbeanServer.unregisterMBean(mbeanObjectName);
        }
    }

    @Override
    public boolean isExclusive() {
        return database.getExclusiveSession() != null;
    }

    @Override
    public boolean isReadOnly() {
        return database.isReadOnly();
    }

    @Override
    public String getMode() {
        return database.getMode().getName();
    }

    @Override
    public boolean isMultiThreaded() {
        return database.isMultiThreaded();
    }

    @Override
    public boolean isMvcc() {
        return database.isMultiVersion();
    }

    @Override
    public int getLogMode() {
        return database.getLogMode();
    }

    @Override
    public void setLogMode(int value) {
        database.setLogMode(value);
    }

    @Override
    public int getTraceLevel() {
        return database.getTraceSystem().getLevelFile();
    }

    @Override
    public void setTraceLevel(int level) {
        database.getTraceSystem().setLevelFile(level);
    }

    @Override
    public long getFileWriteCountTotal() {
        if (!database.isPersistent()) {
            return 0;
        }
        PageStore p = database.getPageStore();
        if (p != null) {
            return p.getWriteCountTotal();
        }
        // TODO remove this method when removing the page store
        // (the MVStore doesn't support it)
        return 0;
    }

    @Override
    public long getFileWriteCount() {
        if (!database.isPersistent()) {
            return 0;
        }
        PageStore p = database.getPageStore();
        if (p != null) {
            return p.getWriteCount();
        }
        return database.getMvStore().getStore().getFileStore().getReadCount();
    }

    @Override
    public long getFileReadCount() {
        if (!database.isPersistent()) {
            return 0;
        }
        PageStore p = database.getPageStore();
        if (p != null) {
            return p.getReadCount();
        }
        return database.getMvStore().getStore().getFileStore().getReadCount();
    }

    @Override
    public long getFileSize() {
        if (!database.isPersistent()) {
            return 0;
        }
        PageStore p = database.getPageStore();
        if (p != null) {
            return p.getPageCount() * p.getPageSize() / 1024;
        }
        return database.getMvStore().getStore().getFileStore().size();
    }

    @Override
    public int getCacheSizeMax() {
        if (!database.isPersistent()) {
            return 0;
        }
        PageStore p = database.getPageStore();
        if (p != null) {
            return p.getCache().getMaxMemory();
        }
        return database.getMvStore().getStore().getCacheSize() * 1024;
    }

    @Override
    public void setCacheSizeMax(int kb) {
        if (database.isPersistent()) {
            database.setCacheSize(kb);
        }
    }

    @Override
    public int getCacheSize() {
        if (!database.isPersistent()) {
            return 0;
        }
        PageStore p = database.getPageStore();
        if (p != null) {
            return p.getCache().getMemory();
        }
        return database.getMvStore().getStore().getCacheSizeUsed() * 1024;
    }

    @Override
    public String getVersion() {
        return Constants.getFullVersion();
    }

    @Override
    public String listSettings() {
        StringBuilder buff = new StringBuilder();
        for (Map.Entry<String, String> e :
                new TreeMap<>(
                database.getSettings().getSettings()).entrySet()) {
            buff.append(e.getKey()).append(" = ").append(e.getValue()).append('\n');
        }
        return buff.toString();
    }

    @Override
    public String listSessions() {
        StringBuilder buff = new StringBuilder();
        for (Session session : database.getSessions(false)) {
            buff.append("session id: ").append(session.getId());
            buff.append(" user: ").
                    append(session.getUser().getName()).
                    append('\n');
            buff.append("connected: ").
                    append(new Timestamp(session.getSessionStart())).
                    append('\n');
            Command command = session.getCurrentCommand();
            if (command != null) {
                buff.append("statement: ").
                        append(session.getCurrentCommand()).
                        append('\n');
                long commandStart = session.getCurrentCommandStart();
                if (commandStart != 0) {
                    buff.append("started: ").append(
                            new Timestamp(commandStart)).
                            append('\n');
                }
            }
            Table[] t = session.getLocks();
            if (t.length > 0) {
                for (Table table : session.getLocks()) {
                    if (table.isLockedExclusivelyBy(session)) {
                        buff.append("write lock on ");
                    } else {
                        buff.append("read lock on ");
                    }
                    buff.append(table.getSchema().getName()).
                            append('.').append(table.getName()).
                            append('\n');
                }
            }
            buff.append('\n');
        }
        return buff.toString();
    }

}
