/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.ignite.internal.visor.debug;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.lang.management.ThreadInfo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.internal.visor.VisorDataTransferObject;

/**
 * Data transfer object for Visor {@link ThreadInfo}.
 */
public class VisorThreadInfo extends VisorDataTransferObject {
    /** */
    private static final long serialVersionUID = 0L;

    /** Thread name. */
    private String name;

    /** Thread ID. */
    private long id;

    /** Thread state. */
    private Thread.State state;

    /** Lock information. */
    private VisorThreadLockInfo lock;

    /** Lock name. */
    private String lockName;

    /** Lock owner thread ID. */
    private long lockOwnerId;

    /** Lock owner name. */
    private String lockOwnerName;

    /** Thread executing native code. */
    private boolean inNative;

    /** Thread is suspended. */
    private boolean suspended;

    /** Waited count. */
    private long waitedCnt;

    /** Waited time. */
    private long waitedTime;

    /** Blocked count. */
    private long blockedCnt;

    /** Blocked time. */
    private long blockedTime;

    /** Stack trace. */
    private List<StackTraceElement> stackTrace;

    /** Locks info. */
    private List<VisorThreadLockInfo> locks;

    /** Locked monitors. */
    private List<VisorThreadMonitorInfo> lockedMonitors;

    /**
     * Default constructor.
     */
    public VisorThreadInfo() {
        // No-op.
    }

    /**
     * Create data transfer object for given thread info.
     *
     * @param ti Thread info.
     */
    public VisorThreadInfo(ThreadInfo ti) {
        assert ti != null;

        name = ti.getThreadName();
        id = ti.getThreadId();
        state = ti.getThreadState();
        lock = ti.getLockInfo() != null ? new VisorThreadLockInfo(ti.getLockInfo()) : null;
        lockName =ti.getLockName();
        lockOwnerId = ti.getLockOwnerId();
        lockOwnerName = ti.getLockOwnerName();
        inNative = ti.isInNative();
        suspended = ti.isSuspended();
        waitedCnt = ti.getWaitedCount();
        waitedTime = ti.getWaitedTime();
        blockedCnt = ti.getBlockedCount();
        blockedTime = ti.getBlockedTime();
        stackTrace = Arrays.asList(ti.getStackTrace());

        locks = ti.getLockedSynchronizers() != null ?
            new ArrayList<VisorThreadLockInfo>(ti.getLockedSynchronizers().length) : null;

        if (ti.getLockedSynchronizers() != null)
            for (int i = 0; i < ti.getLockedSynchronizers().length; i++)
                locks.add(new VisorThreadLockInfo(ti.getLockedSynchronizers()[i]));

        lockedMonitors = ti.getLockedMonitors() != null ?
            new ArrayList<VisorThreadMonitorInfo>(ti.getLockedMonitors().length) : null;

        if (ti.getLockedMonitors() != null)
            for (int i = 0; i < ti.getLockedMonitors().length; i++)
                lockedMonitors.add(new VisorThreadMonitorInfo(ti.getLockedMonitors()[i]));
    }

    /**
     * @return Thread name.
     */
    public String getName() {
        return name;
    }

    /**
     * @return Thread ID.
     */
    public long getId() {
        return id;
    }

    /**
     * @return Thread state.
     */
    public Thread.State getState() {
        return state;
    }

    /**
     * @return Lock information.
     */
    public VisorThreadLockInfo getLock() {
        return lock;
    }

    /**
     * @return Lock name.
     */
    public String getLockName() {
        return lockName;
    }

    /**
     * @return Lock owner thread ID.
     */
    public long getLockOwnerId() {
        return lockOwnerId;
    }

    /**
     * @return Lock owner name.
     */
    public String getLockOwnerName() {
        return lockOwnerName;
    }

    /**
     * @return Thread executing native code.
     */
    public boolean isInNative() {
        return inNative;
    }

    /**
     * @return Thread is suspended.
     */
    public boolean isSuspended() {
        return suspended;
    }

    /**
     * @return Waited count.
     */
    public long getWaitedCount() {
        return waitedCnt;
    }

    /**
     * @return Waited time.
     */
    public long getWaitedTime() {
        return waitedTime;
    }

    /**
     * @return Blocked count.
     */
    public long getBlockedCount() {
        return blockedCnt;
    }

    /**
     * @return Blocked time.
     */
    public long getBlockedTime() {
        return blockedTime;
    }

    /**
     * @return Stack trace.
     */
    public List<StackTraceElement> getStackTrace() {
        return stackTrace;
    }

    /**
     * @return Locks info.
     */
    public List<VisorThreadLockInfo> getLocks() {
        return locks;
    }

    /**
     * @return Locked monitors.
     */
    public List<VisorThreadMonitorInfo> getLockedMonitors() {
        return lockedMonitors;
    }

    /** {@inheritDoc} */
    @Override protected void writeExternalData(ObjectOutput out) throws IOException {
        U.writeString(out, name);
        out.writeLong(id);
        U.writeString(out, state.toString());
        out.writeObject(lock);
        U.writeString(out, lockName);
        out.writeLong(lockOwnerId);
        U.writeString(out, lockOwnerName);
        out.writeBoolean(inNative);
        out.writeBoolean(suspended);
        out.writeLong(waitedCnt);
        out.writeLong(waitedTime);
        out.writeLong(blockedCnt);
        out.writeLong(blockedTime);
        U.writeCollection(out, stackTrace);
        U.writeCollection(out, locks);
        U.writeCollection(out, lockedMonitors);
    }

    /** {@inheritDoc} */
    @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
        name = U.readString(in);
        id = in.readLong();

        String statePresentation = U.readString(in);

        if (statePresentation != null)
            state = Enum.valueOf(Thread.State.class, statePresentation);

        lock = (VisorThreadLockInfo)in.readObject();
        lockName = U.readString(in);
        lockOwnerId = in.readLong();
        lockOwnerName = U.readString(in);
        inNative = in.readBoolean();
        suspended = in.readBoolean();
        waitedCnt = in.readLong();
        waitedTime = in.readLong();
        blockedCnt = in.readLong();
        blockedTime = in.readLong();
        stackTrace = U.readList(in);
        locks = U.readList(in);
        lockedMonitors = U.readList(in);
    }

    /** {@inheritDoc} */
    @Override public String toString() {
        StringBuilder sb = new StringBuilder(512);

        sb.append('"').append(name).append('"').append(" Id=").append(id).append(' ').append(state);

        if (lockName != null)
            sb.append(" on ").append(lockName);

        if (lockOwnerName != null)
            sb.append(" owned by \"").append(lockOwnerName).append("\" Id=").append(lockOwnerId);

        if (suspended)
            sb.append(" (suspended)");

        if (inNative)
            sb.append(" (in native)");

        sb.append('\n');

        for (int i = 0; i < stackTrace.size(); i++) {
            StackTraceElement ste = stackTrace.get(i);

            sb.append("\tat ").append(ste).append('\n');

            if (i == 0 && lock != null) {
                switch (state) {
                    case BLOCKED:
                        sb.append("\t-  blocked on ").append(lock).append('\n');
                        break;

                    case WAITING:
                        sb.append("\t-  waiting on ").append(lock).append('\n');
                        break;

                    case TIMED_WAITING:
                        sb.append("\t-  waiting on ").append(lock).append('\n');
                        break;

                    default:
                }
            }

            for (VisorThreadMonitorInfo mi : lockedMonitors) {
                if (mi.getStackDepth() == i)
                    sb.append("\t-  locked ").append(mi).append('\n');
            }
        }

        if (!F.isEmpty(locks)) {
            sb.append("\n\tNumber of locked synchronizers = ").append(locks.size()).append('\n');

            for (VisorThreadLockInfo li : locks)
                sb.append("\t- ").append(li).append('\n');
        }

        sb.append('\n');

        return sb.toString();
    }
}
