/*
 * 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.clerezza.rdf.locking;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.security.AccessControlException;
import java.security.AccessController;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import org.apache.clerezza.platform.globalmenu.GlobalMenuItem;
import org.apache.clerezza.platform.globalmenu.GlobalMenuItemsProvider;
import org.apache.clerezza.rdf.core.UriRef;
import org.apache.clerezza.rdf.core.access.TcManager;
import org.apache.clerezza.rdf.core.access.debug.ReadLockDebug;
import org.apache.clerezza.rdf.core.access.debug.ReentrantReadWriteLockTracker;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.felix.scr.annotations.Services;

/**
 * Provides an overview page of all locks of all graphs.
 * @author mir
 */
@Component()
@Services({
    @Service(value=Object.class),
    @Service(value=GlobalMenuItemsProvider.class)
})
@Property(name = "javax.ws.rs", boolValue = true)
@Path("admin/graphs/locks")
public class LockOverview implements GlobalMenuItemsProvider {

    @Reference
    TcManager tcManager;

    @GET
    public String getOverview() {
        Set<Thread> threadSet = getAllThreads();
        Iterator<UriRef> mGraphUris = tcManager.listMGraphs().iterator();

        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter);
        while (mGraphUris.hasNext()) {
            UriRef uriRef = mGraphUris.next();
            ReentrantReadWriteLock lock = (ReentrantReadWriteLock) tcManager.getMGraph(uriRef).getLock();
            int readLockCount = lock.getReadLockCount();
            printWriter.append(uriRef.getUnicodeString());
            printWriter.append("\n");
            printWriter.append("Read-Lock count:    " + readLockCount);
            printWriter.append("\n");
            printWriter.append("Write-Locked:       " + (lock.isWriteLocked() ? "YES" : "NO"));
            printWriter.append("\n");
            printWriter.append("Has queued threads: " + (lock.hasQueuedThreads() ? "YES" : "NO"));
            printWriter.append("\n");
            if (readLockCount > 0 && lock instanceof ReentrantReadWriteLockTracker) {
                ReentrantReadWriteLockTracker debugLock = (ReentrantReadWriteLockTracker) lock;
                printWriter.append("Threads holding read-lock: \n");
                Set<ReadLockDebug> lockedReadLocks = debugLock.getLockedReadLocks();
                for (ReadLockDebug readLockDebug : lockedReadLocks) {
                    printStackTrace(readLockDebug.getStackTrace(), printWriter);
                    printWriter.append("\n");
                }
                printWriter.append("\n");
            }
            if (lock.isWriteLocked() && lock instanceof ReentrantReadWriteLockTracker) {
                ReentrantReadWriteLockTracker debugLock = (ReentrantReadWriteLockTracker) lock;
                printWriter.append("Thread holding write-lock: \n");
                printStackTrace(debugLock.writeLock().getStackTrace(), printWriter);
                printWriter.append("\n");
            }
            printWriter.append("Queue length:       " + lock.getQueueLength());
            printWriter.append("\n");
            printWriter.append("Queued threads: ");
            printWriter.append("\n");
            printQueuedThreads(lock, printWriter, threadSet);
            printWriter.append("----------------------------------------------------");
            printWriter.append("\n");

        }
        return stringWriter.toString();
    }

    private void printQueuedThreads(ReentrantReadWriteLock lock, PrintWriter printWriter,
            Set<Thread> threadSet) {
        for (Thread thread : threadSet) {
            if (lock.hasQueuedThread(thread)) {
                printWriter.append("" + thread.getId());
                printStackTrace(thread.getStackTrace(), printWriter);
                printWriter.append("\n");
            }
        }
    }

    private void printStackTrace(StackTraceElement[] stackTrace, PrintWriter printWriter) {
        Throwable throwable = new Throwable();
        throwable.setStackTrace(stackTrace);
        throwable.printStackTrace(printWriter);
    }

    public Set<Thread> getAllThreads() {
        // Find the root thread group
        ThreadGroup root = Thread.currentThread().getThreadGroup().getParent();
        while (root.getParent() != null) {
            root = root.getParent();
        }
        HashSet<Thread> threadSet = new HashSet<Thread>();
        visit(root, 0, threadSet);
        return threadSet;
    }

    public void visit(ThreadGroup group, int level, HashSet<Thread> threadSet) {
        // Get threads in `group'
        int numThreads = group.activeCount();
        Thread[] threads = new Thread[numThreads * 2];
        numThreads = group.enumerate(threads, false);
        // Enumerate each thread in `group'
        for (int i = 0; i < numThreads; i++) {
            // Get thread
            Thread thread = threads[i];
            threadSet.add(thread);
        }
        // Get thread subgroups of `group'
        int numGroups = group.activeGroupCount();
        ThreadGroup[] groups = new ThreadGroup[numGroups * 2];
        numGroups = group.enumerate(groups, false);
        // Recursively visit each subgroup
        for (int i = 0; i < numGroups; i++) {
            visit(groups[i], level + 1, threadSet);
        }
    }

    @Override
    public Set<GlobalMenuItem> getMenuItems() {
        Set<GlobalMenuItem> items = new HashSet<GlobalMenuItem>();
        try {
            AccessController.checkPermission(
                    new LockOverviewPermission());
        } catch (AccessControlException e) {
            return items;
        }
        items.add(new GlobalMenuItem("/admin/graphs/locks", "SCM", "MGraph Locking", 1,
                "Development"));
        return items;
    }
}
