package com.atlassian.confluence.admin.actions;

import com.atlassian.config.db.HibernateConfig;
import com.atlassian.confluence.cluster.NodeStatus;
import com.atlassian.confluence.core.Administrative;
import com.atlassian.confluence.core.ConfluenceActionSupport;
import com.atlassian.confluence.setup.BootstrapManager;
import com.atlassian.confluence.setup.BootstrapUtils;
import com.atlassian.confluence.util.GeneralUtil;
import com.atlassian.core.util.DateUtils;
import com.atlassian.plugin.PluginManager;
import com.atlassian.spring.container.ComponentNotFoundException;
import com.atlassian.spring.container.ContainerManager;
import com.opensymphony.module.sitemesh.util.Container;
import com.opensymphony.webwork.ServletActionContext;
import org.apache.commons.collections.SequencedHashMap;

import javax.servlet.ServletContext;
import java.sql.Driver;
import java.sql.DriverManager;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

public class ViewSystemInfoAction extends ConfluenceActionSupport implements Administrative, NodeStatus
{
    Properties sysProps = System.getProperties();
    Runtime rt = Runtime.getRuntime();
    Map props = new SequencedHashMap();

    DateFormat dateFormatter = new SimpleDateFormat("EEEEE, dd MMM yyyy");
    DateFormat timeFormatter = new SimpleDateFormat("HH:mm:ss");

    static final long MEGABYTE = 1048576;
    private ServletContext servletContext;
    private HibernateConfig hibernateConfig;
    private Map buildstats;
    private boolean gc = false;


    /**
     * Keep the blank constructor so WebWork can construct the action
     */
    public ViewSystemInfoAction()
    {
    }

    public String execute() throws Exception
    {
        if (gc)
        {
            System.gc();
            Thread.sleep(500);
        }

        return super.execute();
    }

    public void setHibernateConfig(HibernateConfig hibernateConfig)
    {
        this.hibernateConfig = hibernateConfig;
    }

    /**
     * This constructor is used by the error page (500page.jsp) to create a new action after the ActionContext has been reset.
     */
    public ViewSystemInfoAction(ServletContext servletContext)
    {
        this.servletContext = servletContext;
    }

    public ServletContext getServletContext()
    {
        if (servletContext != null)
            return servletContext;
        else
            return ServletActionContext.getServletContext();
    }

    public Map getProps()
    {
        props.put("system.date", dateFormatter.format(new Date()));
        props.put("system.time", timeFormatter.format(new Date()));
        props.put("system.favourite.colour", "Claret");

        props.put("java.version", sysProps.getProperty("java.version"));
        props.put("java.vendor", sysProps.getProperty("java.vendor"));
        props.put("jvm.version", sysProps.getProperty("java.vm.specification.version"));
        props.put("jvm.vendor", sysProps.getProperty("java.vm.specification.vendor"));
        props.put("jvm.implementation.version", sysProps.getProperty("java.vm.version"));
        props.put("java.runtime", sysProps.getProperty("java.runtime.name"));
        props.put("java.vm", sysProps.getProperty("java.vm.name"));
        props.put("CSP-7323", "proxy parameters follow");
        props.put("http.proxyHost", sysProps.getProperty("http.proxyHost"));
        props.put("http.proxyPort", sysProps.getProperty("http.proxyPort"));

        props.put("user.name.word", sysProps.getProperty("user.name"));
        props.put("user.timezone", sysProps.getProperty("user.timezone"));

        props.put("operating.system", sysProps.getProperty("os.name") + " " + sysProps.getProperty("os.version"));
        props.put("os.architecture", sysProps.getProperty("os.arch"));
        props.put("fs.encoding", sysProps.getProperty("file.encoding"));

        return props;
    }

    public Map getJVMstats()
    {
        Map jvmstats = new SequencedHashMap();
        jvmstats.put("total.memory", "" + getTotalMemory());
        jvmstats.put("free.memory", "" + getFreeMemory());
        jvmstats.put("used.memory", "" + getUsedMemory());

        return jvmstats;
    }

    public Map getBuildStats()
    {
        if (buildstats == null)
        {
            buildstats = new SequencedHashMap();

            BootstrapManager setup = BootstrapUtils.getBootstrapManager();
            if (setup != null)
                buildstats.put("confluence.home", setup.getConfluenceHome());
            else
                buildstats.put("confluence.home", "Unknown?");

            buildstats.put("system.uptime", getUptime());
            buildstats.put("system.version", GeneralUtil.getVersionNumber());
            buildstats.put("build.number", GeneralUtil.getBuildNumber());

            if (isDevMode())
                buildstats.put("developer.mode", "Enabled");
        }

        return buildstats;
    }

    // CONF-5212
    public String getDatabaseURL()
    {
        BootstrapManager setup = BootstrapUtils.getBootstrapManager();

        if (setup == null || (hibernateConfig != null && !hibernateConfig.isHibernateSetup()))
            return "Could not retrieve JDBC connection URL or datasource";

        String databaseUrl;

        // First try the normal connection URL
        databaseUrl = setup.getString("hibernate.connection.url");

        // Try a datasource if there's no connection URL
        if (databaseUrl == null)
            databaseUrl = setup.getString("hibernate.connection.datasource");

        // If we can't find either, show a message
        if (databaseUrl == null)
            databaseUrl = "Could not retrieve JDBC connection URL or datasource";

        return databaseUrl;
    }

    public String getDatabaseDialect()
    {
        BootstrapManager setup = BootstrapUtils.getBootstrapManager();
        if (setup == null || (hibernateConfig != null && !hibernateConfig.isHibernateSetup()))
        {
            return "N/A";
        }
        else
        {
            return setup.getString("hibernate.dialect");
        }
    }

    /**
     * Retrieves the class name of the database driver
     *
     * @return Returns the class name as a String, or "N/A" if it was unable to be retrieved
     */
    public String getDatabaseDriverName()
    {
        BootstrapManager setup = BootstrapUtils.getBootstrapManager();
        if (setup == null || (hibernateConfig != null && !hibernateConfig.isHibernateSetup()))
        {
            return "N/A";
        }
        else
        {
            String driverClass = setup.getString("hibernate.connection.driver_class");
            if (driverClass == null)
            {
                return "N/A";
            }
            else
            {
                return driverClass;
            }
        }
    }

    /**
     * Retrieves the version of the database driver, in the form "MAJOR.MINOR"
     *
     * @return Returns the version as a String, or "N/A" if it was unable to be retrieved
     */
    public String getDatabaseDriverVersion()
    {
        BootstrapManager setup = BootstrapUtils.getBootstrapManager();

        if (setup == null || (hibernateConfig != null && !hibernateConfig.isHibernateSetup()))
            return "Not available";

        // Get the connection URL
        String connectionUrl = setup.getString("hibernate.connection.url");

        // If the connectionUrl is null (e.g. if it's a datasource), return "N/A" (CONF-5192, CONF-5211)
        if (connectionUrl == null)
            return "N/A";

        try
        {
            Driver driver = DriverManager.getDriver(connectionUrl);
            return driver.getMajorVersion() + "." + driver.getMinorVersion();
        }
        catch (Throwable t)
        {
            log.error("Error trying to retrieve the Driver for url '" + connectionUrl + "' " + t.getMessage(), t);
            return "N/A";
        }
    }

    /**
     * Retrieves the list of enabled plugins.
     * Returns null when there was a problem retrieving the pluginManager
     *
     * @return List containing the enabled plugins
     */
    public List getEnabledPlugins()
    {
        PluginManager pluginManager;

        // This is unlikely to happen, but if it does, handle it gracefully
        try
        {
            pluginManager = (PluginManager) ContainerManager.getInstance().getContainerContext().getComponent("pluginManager");
        }
        catch (ComponentNotFoundException e)
        {
            log.error("Error trying to retrieve the plugin manager when printing out the enabled plugins: ", e);
            return null;
        }

        // Being (ultra) defensive. The last thing we want is the error page to fail.
        if (pluginManager == null)
        {
            log.error("Error trying to retrieve the plugin manager when printing out the enabled plugins.");
            return null;
        }

        return new ArrayList(pluginManager.getEnabledPlugins());
    }

    private String getUptime()
    {
        Long startupTime = GeneralUtil.getSystemStartupTime();

        if (startupTime == null)
            return "N/A";

        long currentTime = System.currentTimeMillis();
        return DateUtils.dateDifference(startupTime.longValue(), currentTime, 4, getI18n().getResourceBundle());
    }

    public String getAppServer()
    {
        switch (Container.get())
        {
            case Container.TOMCAT:
                return "Apache Tomcat";
            case Container.ORION:
                return "Orion";
            case Container.WEBLOGIC:
                return "IBM WebLogic";
            case Container.JRUN:
                return "JRUN";
            case Container.RESIN:
                return "RESIN" + Container.get();
            case Container.HPAS:
                return "HPAS";
            case Container.UNKNOWN:
                return "Unknown";
        }
        return "Unknown";
    }

    public long getTotalMemory()
    {
        return rt.totalMemory() / MEGABYTE;
    }

    public long getFreeMemory()
    {
        return rt.freeMemory() / MEGABYTE;
    }

    public long getUsedMemory()
    {
        return getTotalMemory() - getFreeMemory();
    }

    public void setGc(boolean gc)
    {
        this.gc = gc;
    }
}
