/**
 * Copyright (C) 2011 Akiban Technologies Inc.
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses.
 */

package com.akiban.util.tap;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

abstract class RecursiveTap extends Tap
{
    // Object interface

    public final String toString()
    {
        return String.format("%s inCount=%,d outCount=%,d time=%,dms",
                             name, inCount, outCount, cumulativeNanos / MILLION);
    }

    // Tap interface
    
    public final void in()
    {
        Stack<RecursiveTap> tapStack = tapStack();
        if (tapStack != null) {
            // print("in %s", this);
            long now = System.nanoTime();
            inCount++;
            inNanos = now;
            if (!tapStack.empty()) {
                RecursiveTap current = tapStack.peek();
                current.lastDuration = now - current.inNanos;
                current.cumulativeNanos += current.lastDuration;
                // print("    added %s to %s", current.lastDuration / MILLION, current);
            }
            tapStack.push(this);
        }
        // else: outermost tap has just been disabled
    }

    public final void out()
    {
        Stack<RecursiveTap> tapStack = tapStack();
        if (tapStack != null) {
            // print("out %s", this);
            if (!tapStack.empty()) {
                long now = System.nanoTime();
                outCount++;
                endNanos = now;
                lastDuration = now - inNanos;
                cumulativeNanos += lastDuration;
                // print("    added %s to %s", lastDuration / MILLION, this);
                RecursiveTap current = tapStack.pop();
                if (current == this) {
                    if (!tapStack.empty()) {
                        RecursiveTap newCurrent = tapStack.peek();
                        newCurrent.inNanos = now;
                    }
                }
            }
        }
        // else: outermost tap has just been disabled
    }

    public final long getDuration()
    {
        return lastDuration;
    }

    public void reset()
    {
        inCount = 0;
        outCount = 0;
        cumulativeNanos = 0;
    }

    public final void appendReport(StringBuilder buffer)
    {
        buffer.append(String.format("%20s inCount=%,10d outCount=%,10d time=%,12dms",
                                name, inCount, outCount, cumulativeNanos / 1000000));
        if (outCount > 0) {
            buffer.append(String.format("  per=%,12dns  interval=%,12dns",
                                    cumulativeNanos / outCount, (endNanos - startNanos) / outCount));
        }
    }

    // RecursiveTap interface

    public RecursiveTap createSubsidiaryTimer(String name)
    {
        if (!(this instanceof Outermost)) {
            throw new IllegalArgumentException("Only the topmost recursive timer may have a subsidiary timer");
        }
        return new Subsidiary(name, (Outermost) this);
    }

    // For use by subclasses

    // tapStack() returns the current stack of taps, which may be null. The callers (RecursiveTap.in/out)
    // should call tapStack() exactly once. This insulates in/out processing from a stack "disappearing".
    protected abstract Stack<RecursiveTap> tapStack();

    protected RecursiveTap(String name)
    {
        super(name);
    }
    
    // For use by this class
    
    private void print(String template, Object ... args)
    {
        System.out.println(String.format(template, args));
    }

    // Class state

    private static final int MILLION = 1000000;

    // Object state

    protected volatile long cumulativeNanos;
    protected volatile long inNanos;
    protected volatile long startNanos = System.nanoTime();
    protected volatile long endNanos;
    protected long lastDuration;

    static class Outermost extends RecursiveTap
    {
        // Tap interface

        @Override
        public void reset()
        {
            tapStack = null;
            super.reset();
            for (Subsidiary subsidiaryTap : subsidiaryTaps) {
                subsidiaryTap.reset();
            }
            tapStack = new Stack<RecursiveTap>();
        }

        @Override
        public void disable()
        {
            tapStack = null;
        }

        @Override
        public TapReport[] getReports()
        {
            TapReport[] reports = new TapReport[subsidiaryTaps.size() + 1];
            int r = 0;
            for (Subsidiary tap : subsidiaryTaps) {
                reports[r++] = new TapReport(tap.getName(), tap.inCount, tap.outCount, tap.cumulativeNanos);
            }
            reports[r] = new TapReport(name, inCount, outCount, cumulativeNanos);
            return reports;
        }

        // RecursiveTap interface

        @Override
        protected Stack<RecursiveTap> tapStack()
        {
            return tapStack;
        }

        public Outermost(String name)
        {
            super(name);
        }

        private volatile Stack<RecursiveTap> tapStack;
        private final List<Subsidiary> subsidiaryTaps = new ArrayList<Subsidiary>();
    }

    static class Subsidiary extends RecursiveTap
    {
        @Override
        public TapReport[] getReports()
        {
            throw new UnsupportedOperationException();
        }

        @Override
        protected Stack<RecursiveTap> tapStack()
        {
            return outermostTap.tapStack();
        }
        
        Subsidiary(String name, Outermost outermostTap)
        {
            super(name);
            this.outermostTap = outermostTap;
            outermostTap.subsidiaryTaps.add(this);
        }

        private RecursiveTap outermostTap;
    }
}

@@ -702,20 +702,21 @@
 il.Array
-List
+Deque
 ;%0Aimport
@@ -726,16 +726,21 @@
 va.util.
+Array
 List;%0Aim
@@ -754,21 +754,44 @@
 va.util.
-Stack
+Deque;%0Aimport java.util.List
 ;%0A%0Aabstr
@@ -1112,37 +1112,37 @@
 )%0A    %7B%0A        
-Stack
+Deque
 %3CRecursiveTap%3E t
@@ -1342,33 +1342,35 @@
   if (!tapStack.
-e
+isE
 mpty()) %7B%0A      
@@ -1795,21 +1795,21 @@
         
-Stack
+Deque
 %3CRecursi
@@ -1926,33 +1926,35 @@
   if (!tapStack.
-e
+isE
 mpty()) %7B%0A      
@@ -2354,17 +2354,19 @@
 apStack.
-e
+isE
 mpty()) 
@@ -3842,21 +3842,21 @@
 bstract 
-Stack
+Deque
 %3CRecursi
@@ -4754,21 +4754,26 @@
 k = new 
-Stack
+ArrayDeque
 %3CRecursi
@@ -5417,37 +5417,37 @@
       protected 
-Stack
+Deque
 %3CRecursiveTap%3E t
@@ -5615,21 +5615,21 @@
 olatile 
-Stack
+Deque
 %3CRecursi
@@ -5962,21 +5962,21 @@
 otected 
-Stack
+Deque
 %3CRecursi

Switch RecursiveTap from Stack to Deque.