package com.netflix.eureka.util;

import java.lang.management.ManagementFactory;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import com.netflix.appinfo.InstanceInfo;
import com.netflix.config.ConfigurationManager;
import com.netflix.discovery.provider.Serializer;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamOmitField;

/**
 * An utility class for exposing status information of an instance.
 *
 * @author Greg Kim
 */
@Serializer("com.netflix.discovery.converters.EntityBodyConverter")
@XStreamAlias("status")
public class StatusInfo {
    private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss Z";
    private static final boolean ARCHAIUS_EXISTS = classExists("com.netflix.config.ConfigurationManager");

    public static final class Builder {

        @XStreamOmitField
        private StatusInfo result;

        private Builder() {
            result = new StatusInfo();
        }

        public static Builder newBuilder() {
            return new Builder();
        }

        public Builder isHealthy(boolean b) {
            result.isHeathly = Boolean.valueOf(b);
            return this;
        }

        public Builder withInstanceInfo(InstanceInfo instanceInfo) {
            result.instanceInfo = instanceInfo;
            return this;
        }

        /**
         * Add any application specific status data.
         */
        public Builder add(String key, String value) {
            if (result.applicationStats == null) {
                result.applicationStats = new HashMap<String, String>();
            }
            result.applicationStats.put(key, value);
            return this;
        }

        /**
         * Build the {@link StatusInfo}. General information are automatically
         * built here too.
         */
        public StatusInfo build() {
            if (result.instanceInfo == null) {
                throw new IllegalStateException("instanceInfo can not be null");
            }

            result.generalStats.put("server-uptime", getUpTime());
            if (ARCHAIUS_EXISTS) {
                result.generalStats.put("environment", ConfigurationManager
                        .getDeploymentContext().getDeploymentEnvironment());
            }

            Runtime runtime = Runtime.getRuntime();
            int totalMem = (int) (runtime.totalMemory() / 1048576);
            int freeMem = (int) (runtime.freeMemory() / 1048576);
            int usedPercent = (int) (((float) totalMem - freeMem) / (totalMem) * 100.0);

            result.generalStats.put("num-of-cpus",
                    String.valueOf(runtime.availableProcessors()));
            result.generalStats.put("total-avail-memory",
                    String.valueOf(totalMem) + "mb");
            result.generalStats.put("current-memory-usage",
                    String.valueOf(totalMem - freeMem) + "mb" + " ("
                            + usedPercent + "%)");

            return result;
        }
    }

    private Map<String, String> generalStats = new HashMap<String, String>();
    private Map<String, String> applicationStats;
    private InstanceInfo instanceInfo;
    private Boolean isHeathly;

    private StatusInfo() {
    }

    public InstanceInfo getInstanceInfo() {
        return instanceInfo;
    }

    public boolean isHealthy() {
        return isHeathly.booleanValue();
    }

    public Map<String, String> getGeneralStats() {
        return generalStats;
    }

    public Map<String, String> getApplicationStats() {
        return applicationStats;
    }

    /**
     * Output the amount of time that has elapsed since the given date in the
     * format x days, xx:xx.
     *
     * @return A string representing the formatted interval.
     */
    public static String getUpTime() {
        long diff = ManagementFactory.getRuntimeMXBean().getUptime();
        diff /= 1000 * 60;
        long minutes = diff % 60;
        diff /= 60;
        long hours = diff % 24;
        diff /= 24;
        long days = diff;
        StringBuilder buf = new StringBuilder();
        if (days == 1) {
            buf.append("1 day ");
        } else if (days > 1) {
            buf.append(Long.valueOf(days).toString()).append(" days ");
        }
        DecimalFormat format = new DecimalFormat();
        format.setMinimumIntegerDigits(2);
        buf.append(format.format(hours)).append(":")
                .append(format.format(minutes));
        return buf.toString();
    }

    public static String getCurrentTimeAsString() {
        SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
        return format.format(new Date());
    }

    private static boolean classExists(String className) {
        try  {
            Class.forName(className);
            return true;
        }  catch (ClassNotFoundException e) {
            return false;
        }
    }

}
