package com.nice.common.jmonitor;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.Thread.State;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.text.DateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.nice.common.jmonitor.JMDispatcher.HttpMapping;
import com.sun.management.OperatingSystemMXBean;

/***
 * 
 * @author [email protected]
 *
 */
@HttpMapping(url = "/jm")
public class JMServer {

    @HttpMapping(url = "/deadlockCheck")
    public JSONObject doDeadlockCheck(Map<String, Object> param) {
        try {
            String app = ((HttpServletRequest) param.get(JMDispatcher.REQ)).getParameter("app");
            ThreadMXBean tBean = JMConnManager.getThreadMBean(app);
            JSONObject json = new JSONObject();
            long[] dTh = tBean.findDeadlockedThreads();
            if (dTh != null) {
                ThreadInfo[] threadInfo = tBean.getThreadInfo(dTh, Integer.MAX_VALUE);
                StringBuffer sb = new StringBuffer();
                for (ThreadInfo info : threadInfo) {
                    sb.append("\n").append(info);
                }
                json.put("hasdeadlock", true);
                json.put("info", sb);
                return json;
            }
            json.put("hasdeadlock", false);
            return json;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @HttpMapping(url = "/loadThreadInfo")
    public JSONObject doLoadThreadInfo(Map<String, Object> param) {
        try {
            String app = ((HttpServletRequest) param.get(JMDispatcher.REQ)).getParameter("app");
            ThreadMXBean tBean = JMConnManager.getThreadMBean(app);
            ThreadInfo[] allThreads = tBean.dumpAllThreads(false, false);

            JSONObject root = new JSONObject();
            JSONArray detail = new JSONArray();
            HashMap<State, Integer> state = new HashMap<Thread.State, Integer>();
            for (ThreadInfo info : allThreads) {
                JSONObject th = new JSONObject();
                long threadId = info.getThreadId();
                long cpu = tBean.getThreadCpuTime(threadId);
                State tState = info.getThreadState();

                th.put("id", threadId);
                th.put("state", tState);
                th.put("name", info.getThreadName());
                th.put("cpu", TimeUnit.NANOSECONDS.toMillis(cpu));
                detail.add(th);

                Integer vl = state.get(tState);
                if (vl == null) {
                    state.put(tState, 0);
                } else {
                    state.put(tState, vl + 1);
                }
            }

            root.put("state", state);
            root.put("detail", detail);
            root.put("total", tBean.getThreadCount());
            root.put("time", System.currentTimeMillis());
            root.put("deamon", tBean.getDaemonThreadCount());

            return root;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @HttpMapping(url = "/dumpThead")
    public void doDumpThread(Map<String, Object> param) {
        try {
            HttpServletRequest req = (HttpServletRequest) param.get(JMDispatcher.REQ);
            HttpServletResponse resp = (HttpServletResponse) param.get(JMDispatcher.RESP);
            String app = req.getParameter("app");
            String threadId = req.getParameter("threadId");
            ThreadMXBean tBean = JMConnManager.getThreadMBean(app);
            JSONObject data = new JSONObject();
            if (threadId != null) {
                Long id = Long.valueOf(threadId);
                ThreadInfo threadInfo = tBean.getThreadInfo(id, Integer.MAX_VALUE);
                data.put("info", threadInfo.toString());
            } else {
                ThreadInfo[] dumpAllThreads = tBean.dumpAllThreads(false, false);
                StringBuffer info = new StringBuffer();
                for (ThreadInfo threadInfo : dumpAllThreads) {
                    info.append("\n").append(threadInfo);
                }
                data.put("info", info);
            }
            writeFile(req, resp, data);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @HttpMapping(url = "/loadRuntimeInfo")
    public JSONObject doLoadRuntimeInfo(Map<String, Object> param) {
        try {
            String app = ((HttpServletRequest) param.get(JMDispatcher.REQ)).getParameter("app");
            RuntimeMXBean mBean = JMConnManager.getRuntimeMBean(app);
            ClassLoadingMXBean cBean = JMConnManager.getClassMbean(app);
            Map<String, String> props = mBean.getSystemProperties();
            DateFormat format = DateFormat.getInstance();
            List<String> input = mBean.getInputArguments();
            Date date = new Date(mBean.getStartTime());

            TreeMap<String, Object> data = new TreeMap<String, Object>();

            data.put("apppid", mBean.getName());
            data.put("startparam", input.toString());
            data.put("starttime", format.format(date));
            data.put("classLoadedNow", cBean.getLoadedClassCount());
            data.put("classUnloadedAll", cBean.getUnloadedClassCount());
            data.put("classLoadedAll", cBean.getTotalLoadedClassCount());
            data.putAll(props);

            JSONObject json = new JSONObject(true);
            json.putAll(data);
            return json;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @HttpMapping(url = "/doGC")
    public String doVMGC(Map<String, Object> param) {
        try {
            String app = ((HttpServletRequest) param.get(JMDispatcher.REQ)).getParameter("app");
            JMConnManager.getMemoryMBean(app).gc();
            return "success";
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @HttpMapping(url = "/doHeapDump")
    public JSONObject doHeapDump(Map<String, Object> param) {
        try {
            HttpServletRequest req = (HttpServletRequest) param.get(JMDispatcher.REQ);
            String app = req.getParameter("app");
            JMConnBean bean = JMConnManager.getApps().get(app);
            String host = bean.getHost();
            boolean islocal = JMConnManager.isLocalHost(host);
            DateFormat fmt = DateFormat.getDateTimeInstance();
            String date = fmt.format(new Date()).replaceAll("\\D", "_");
            if (islocal) {
                return doLocalDump(req, app, date);
            }
            return doRemoteDump(app, date, host);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private JSONObject doRemoteDump(String app, String date, String host) throws IOException {
        RuntimeMXBean mBean = JMConnManager.getRuntimeMBean(app);
        String dir = mBean.getSystemProperties().get("user.dir");
        String dumpFile = String.format("%s/%s_%s_heap.hprof", dir, app, date);
        JMConnManager.getHotspotBean(app).dumpHeap(dumpFile, false);

        JSONObject res = new JSONObject();
        res.put("file", host + ":" + dumpFile);
        res.put("local", false);
        return res;
    }

    @SuppressWarnings("deprecation")
    private JSONObject doLocalDump(HttpServletRequest req, String app, String date) throws IOException {
        File root = new File(req.getRealPath(req.getRequestURI()));
        String dir = root.getParentFile().getParent();
        File file = new File(String.format("%s/dump/%s_%s_heap.hprof", dir, app, date));
        file.getParentFile().mkdirs();
        String dumpFile = file.getAbsolutePath();
        JMConnManager.getHotspotBean(app).dumpHeap(dumpFile, false);

        JSONObject res = new JSONObject();
        res.put("local", true);
        res.put("file", String.format("./dump/%s", file.getName()));

        return res;
    }

    @HttpMapping(url = "/loadMonitorData")
    public JSONObject doLoadMonitorData(Map<String, Object> param) {
        try {
            String app = ((HttpServletRequest) param.get(JMDispatcher.REQ)).getParameter("app");
            long now = System.currentTimeMillis();
            JSONObject data = new JSONObject();

            JSONObject gc = geGCInfo(app);
            JSONObject cpu = findCpuInfo(app);
            JSONObject memory = loadMemoryInfo(app);

            data.put("gc", gc);
            data.put("cpu", cpu);
            data.put("time", now);
            data.put("memory", memory);

            return data;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @HttpMapping(url = "/loadCluster")
    public JSONArray doRequestLoadCluster(Map<String, Object> param) {
        Map<String, JMConnBean> apps = JMConnManager.getApps();
        JSONArray tree = new JSONArray();
        for (JMConnBean bean : apps.values()) {
            JSONObject node = new JSONObject();
            node.put("host", bean.getHost());
            node.put("port", bean.getPort());
            node.put("text", bean.getName());
            node.put("cluster", "test");
            tree.add(node);
        }
        return tree;

    }


    public static JSONObject loadMemoryInfo(String app) {
        try {
            MemoryMXBean mBean = JMConnManager.getMemoryMBean(app);
            MemoryUsage nonHeap = mBean.getNonHeapMemoryUsage();
            MemoryUsage heap = mBean.getHeapMemoryUsage();

            JSONObject map = new JSONObject(true);
            buildMemoryJSon(heap, "heap", map);
            buildMemoryJSon(nonHeap, "nonheap", map);

            JSONObject heapChild = new JSONObject();
            JSONObject nonheapChild = new JSONObject();

            JSONObject heapUsed = new JSONObject();
            JSONObject heapMax = new JSONObject();
            heapUsed.put("used", heap.getUsed());
            heapMax.put("used", heap.getCommitted());
            heapChild.put("HeapUsed", heapUsed);
            heapChild.put("HeapCommit", heapMax);

            JSONObject nonheapUsed = new JSONObject();
            JSONObject noheapMax = new JSONObject();
            nonheapUsed.put("used", nonHeap.getUsed());
            noheapMax.put("used", nonHeap.getCommitted());

            nonheapChild.put("NonheapUsed", nonheapUsed);
            nonheapChild.put("NonheapCommit", noheapMax);

            ObjectName obj = new ObjectName("java.lang:type=MemoryPool,*");
            MBeanServerConnection conn = JMConnManager.getConn(app);
            Set<ObjectInstance> MBeanset = conn.queryMBeans(obj, null);
            for (ObjectInstance objx : MBeanset) {
                String name = objx.getObjectName().getCanonicalName();
                String keyName = objx.getObjectName().getKeyProperty("name");
                MemoryPoolMXBean bean = JMConnManager.getServer(app, name, MemoryPoolMXBean.class);
                JSONObject item = toJson(bean.getUsage());
                if (JMConnManager.HEAP_ITEM.contains(keyName)) {
                    heapChild.put(keyName, item);
                } else {
                    nonheapChild.put(keyName, item);
                }
            }
            map.getJSONObject("heap").put("childs", heapChild);
            map.getJSONObject("nonheap").put("childs", nonheapChild);

            return map;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static JSONObject findCpuInfo(String app) {
        try {
            JSONObject map = new JSONObject(true);
            OperatingSystemMXBean os = JMConnManager.getOSMbean(app);
            map.put("os", (long) (os.getSystemCpuLoad() * 100));
            map.put("vm", (long) (os.getProcessCpuLoad() * 100));
            map.put("cores", (long) (os.getAvailableProcessors()));
            map.put("freememory", os.getFreePhysicalMemorySize());
            return map;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static JSONObject geGCInfo(String app) throws Exception {
        ObjectName obj = new ObjectName("java.lang:type=GarbageCollector,*");
        MBeanServer conn = ManagementFactory.getPlatformMBeanServer();
        Set<ObjectInstance> MBeanset = conn.queryMBeans(obj, null);
        Class<GarbageCollectorMXBean> cls = GarbageCollectorMXBean.class;
        JSONObject data = new JSONObject();
        for (ObjectInstance objx : MBeanset) {
            String name = objx.getObjectName().getCanonicalName();
            String keyName = objx.getObjectName().getKeyProperty("name");
            GarbageCollectorMXBean gc = ManagementFactory.newPlatformMXBeanProxy(conn, name, cls);
            data.put(keyName + "-time", gc.getCollectionTime() / 1000.0);
            data.put(keyName + "-count", gc.getCollectionCount());
        }
        return data;
    }

    private static void buildMemoryJSon(MemoryUsage useage, String name, JSONObject map) {
        JSONObject item = toJson(useage);
        map.put(name, item);
    }

    private static JSONObject toJson(MemoryUsage useage) {
        JSONObject item = new JSONObject();
        item.put("commit", useage.getCommitted());
        item.put("used", useage.getUsed());
        item.put("init", useage.getInit());
        item.put("max", useage.getMax());
        return item;
    }

    private void writeFile(HttpServletRequest req, HttpServletResponse resp, JSONObject rtInfo) {
        try {
            String javaApp = req.getParameter("app");
            DateFormat fmt = DateFormat.getDateTimeInstance();
            String dateStr = fmt.format(new Date()).replaceAll("\\D", "_");
            String fileName = String.format("%s-%s.threaddump", javaApp, dateStr);
            resp.setHeader("content-disposition", "attachment; filename=" + fileName);
            PrintWriter out = resp.getWriter();
            out.print(rtInfo.get("info"));
            out.flush();
            out.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}