/*
 * Copyright 2003-2004 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package sun.management.snmp.jvminstr;

// java imports
//
import java.io.Serializable;
import java.lang.management.ThreadInfo;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;

// jmx imports
//
import com.sun.jmx.snmp.SnmpStatusException;

// jdmk imports
//
import com.sun.jmx.snmp.agent.SnmpMib;
import com.sun.jmx.snmp.SnmpOid;
import com.sun.jmx.snmp.SnmpDefinitions;
import com.sun.jmx.snmp.SnmpOidTable;
import com.sun.jmx.snmp.SnmpOidRecord;

import sun.management.snmp.jvmmib.JvmThreadInstanceEntryMBean;
import sun.management.snmp.jvmmib.JVM_MANAGEMENT_MIBOidTable;
import sun.management.snmp.util.MibLogger;

/**
 * The class is used for implementing the "JvmThreadInstanceEntry" group.
 */
public class JvmThreadInstanceEntryImpl
    implements JvmThreadInstanceEntryMBean, Serializable {

    public final static class ThreadStateMap {
        public final static class Byte0 {
            public final static byte inNative     = (byte)0x80; // bit 1
            public final static byte suspended    = (byte)0x40; // bit 2
            public final static byte newThread    = (byte)0x20; // bit 3
            public final static byte runnable     = (byte)0x10; // bit 4
            public final static byte blocked      = (byte)0x08; // bit 5
            public final static byte terminated   = (byte)0x04; // bit 6
            public final static byte waiting      = (byte)0x02; // bit 7
            public final static byte timedWaiting = (byte)0x01; // bit 8
        }
        public final static class Byte1 {
            public final static byte other        = (byte)0x80; // bit 9
            public final static byte reserved10   = (byte)0x40; // bit 10
            public final static byte reserved11   = (byte)0x20; // bit 11
            public final static byte reserved12   = (byte)0x10; // bit 12
            public final static byte reserved13   = (byte)0x08; // bit 13
            public final static byte reserved14   = (byte)0x04; // bit 14
            public final static byte reserved15   = (byte)0x02; // bit 15
            public final static byte reserved16   = (byte)0x01; // bit 16
        }

        public final static byte mask0 = (byte)0x3F;
        public final static byte mask1 = (byte)0x80;

        private static void setBit(byte[] bitmap, int index, byte state) {
            bitmap[index] = (byte) (bitmap[index] | state);
        }
        public static void setNative(byte[] bitmap) {
            setBit(bitmap,0,Byte0.inNative);
        }
        public static void setSuspended(byte[] bitmap) {
            setBit(bitmap,0,Byte0.suspended);
        }
        public static void setState(byte[] bitmap, Thread.State state) {
            switch(state) {
            case BLOCKED:
                setBit(bitmap,0,Byte0.blocked);
                return;
            case NEW:
                setBit(bitmap,0,Byte0.newThread);
                return;
            case RUNNABLE:
                setBit(bitmap,0,Byte0.runnable);
                return;
            case TERMINATED:
                setBit(bitmap,0,Byte0.terminated);
                return;
            case TIMED_WAITING:
                setBit(bitmap,0,Byte0.timedWaiting);
                return;
            case WAITING:
                setBit(bitmap,0,Byte0.waiting);
                return;
            }
        }

        public static void checkOther(byte[] bitmap) {
            if (((bitmap[0]&mask0)==(byte)0x00) &&
                ((bitmap[1]&mask1)==(byte)0x00))
                setBit(bitmap,1,Byte1.other);
        }

        public static Byte[] getState(ThreadInfo info) {
            byte[] bitmap = new byte[] {(byte)0x00, (byte)0x00};
            try {
                final Thread.State state = info.getThreadState();
                final boolean inNative  = info.isInNative();
                final boolean suspended = info.isSuspended();
                log.debug("getJvmThreadInstState",
                          "[State=" + state +
                          ",isInNative=" + inNative +
                          ",isSuspended=" + suspended + "]");
                setState(bitmap,state);
                if (inNative)  setNative(bitmap);
                if (suspended) setSuspended(bitmap);
                checkOther(bitmap);
            } catch (RuntimeException r) {
                bitmap[0]=(byte)0x00;
                bitmap[1]=Byte1.other;
                log.trace("getJvmThreadInstState",
                          "Unexpected exception: " + r);
                log.debug("getJvmThreadInstState",r);
            }
            Byte[] result = { new Byte(bitmap[0]), new Byte(bitmap[1]) };
            return result;
        }
    }

    private final ThreadInfo info;
    private final Byte[] index;

    /**
     * Constructor for the "JvmThreadInstanceEntry" group.
     */
    public JvmThreadInstanceEntryImpl(ThreadInfo info,
                                      Byte[] index) {
        this.info = info;
        this.index = index;
    }


    private static String  jvmThreadInstIndexOid = null;
    public static String getJvmThreadInstIndexOid()
        throws SnmpStatusException {
        if (jvmThreadInstIndexOid == null) {
            final SnmpOidTable  table = new JVM_MANAGEMENT_MIBOidTable();
            final SnmpOidRecord record =
                table.resolveVarName("jvmThreadInstIndex");
            jvmThreadInstIndexOid = record.getOid();
        }
        return jvmThreadInstIndexOid;
    }



    /**
     * Getter for the "JvmThreadInstLockedOwnerId" variable.
     */
    public String getJvmThreadInstLockOwnerPtr() throws SnmpStatusException {
       long id = info.getLockOwnerId();

       if(id == -1)
           return new String("0.0");

       SnmpOid oid = JvmThreadInstanceTableMetaImpl.makeOid(id);

       return getJvmThreadInstIndexOid() + "." + oid.toString();
    }

    private String validDisplayStringTC(String str) {
        return JVM_MANAGEMENT_MIB_IMPL.validDisplayStringTC(str);
    }

    private String validJavaObjectNameTC(String str) {
        return JVM_MANAGEMENT_MIB_IMPL.validJavaObjectNameTC(str);
    }

    private String validPathElementTC(String str) {
        return JVM_MANAGEMENT_MIB_IMPL.validPathElementTC(str);
    }

    /**
     * Getter for the "JvmThreadInstLockName" variable.
     */
    public String getJvmThreadInstLockName() throws SnmpStatusException {
        return validJavaObjectNameTC(info.getLockName());
    }

    /**
     * Getter for the "JvmThreadInstName" variable.
     */
    public String getJvmThreadInstName() throws SnmpStatusException {
        return validJavaObjectNameTC(info.getThreadName());
    }

    /**
     * Getter for the "JvmThreadInstCpuTimeNs" variable.
     */
    public Long getJvmThreadInstCpuTimeNs() throws SnmpStatusException {
        long l = 0;
        final ThreadMXBean tmb = JvmThreadingImpl.getThreadMXBean();

        try {
            if (tmb.isThreadCpuTimeSupported()) {
                l = tmb.getThreadCpuTime(info.getThreadId());
                log.debug("getJvmThreadInstCpuTimeNs", "Cpu time ns : " + l);

                //Cpu time measurement is disabled or the id is not valid.
                if(l == -1) l = 0;
            }
        } catch (UnsatisfiedLinkError e) {
            // XXX Revisit: catch TO BE EVENTUALLY REMOVED
            log.debug("getJvmThreadInstCpuTimeNs",
                      "Operation not supported: " + e);
        }
        return new Long(l);
    }

    /**
     * Getter for the "JvmThreadInstBlockTimeMs" variable.
     */
    public Long getJvmThreadInstBlockTimeMs() throws SnmpStatusException {
        long l = 0;

        final ThreadMXBean tmb = JvmThreadingImpl.getThreadMXBean();

        if (tmb.isThreadContentionMonitoringSupported()) {
            l = info.getBlockedTime();

            //Monitoring is disabled
            if(l == -1) l = 0;
        }
        return new Long(l);
    }

    /**
     * Getter for the "JvmThreadInstBlockCount" variable.
     */
    public Long getJvmThreadInstBlockCount() throws SnmpStatusException {
        return new Long(info.getBlockedCount());
    }

    /**
     * Getter for the "JvmThreadInstWaitTimeMs" variable.
     */
    public Long getJvmThreadInstWaitTimeMs() throws SnmpStatusException {
        long l = 0;

        final ThreadMXBean tmb = JvmThreadingImpl.getThreadMXBean();

        if (tmb.isThreadContentionMonitoringSupported()) {
            l = info.getWaitedTime();

            //Monitoring is disabled
            if(l == -1) l = 0;
        }
        return new Long(l);
    }

    /**
     * Getter for the "JvmThreadInstWaitCount" variable.
     */
    public Long getJvmThreadInstWaitCount() throws SnmpStatusException {
        return new Long(info.getWaitedCount());
    }

    /**
     * Getter for the "JvmThreadInstState" variable.
     */
    public Byte[] getJvmThreadInstState()
        throws SnmpStatusException {
        return ThreadStateMap.getState(info);
    }

    /**
     * Getter for the "JvmThreadInstId" variable.
     */
    public Long getJvmThreadInstId() throws SnmpStatusException {
        return new Long(info.getThreadId());
    }

    /**
     * Getter for the "JvmThreadInstIndex" variable.
     */
    public Byte[] getJvmThreadInstIndex() throws SnmpStatusException {
        return index;
    }

    /**
     * Getter for the "JvmThreadInstStackTrace" variable.
     */
    private String getJvmThreadInstStackTrace() throws SnmpStatusException {
        StackTraceElement[] stackTrace = info.getStackTrace();
        //We append the stack trace in a buffer
        // XXX Revisit: should check isDebugOn()
        StringBuffer b = new StringBuffer();
        final int stackSize = stackTrace.length;
        log.debug("getJvmThreadInstStackTrace", "Stack size : " + stackSize);
        for(int i = 0; i < stackSize; i++) {
            log.debug("getJvmThreadInstStackTrace", "Append " +
                      stackTrace[i].toString());
            b.append(stackTrace[i].toString());
            //Append \n at the end of each line except the last one
            if(i < stackSize)
                b.append("\n");
        }
        //The stack trace is truncated if its size exceeds 255.
        return validPathElementTC(b.toString());
    }
    static final MibLogger log =
        new MibLogger(JvmThreadInstanceEntryImpl.class);
}
