/*
 * Copyright 2015 NAVER Corp.
 *
 * Licensed 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 com.navercorp.pinpoint.web.calltree.span;

import static org.junit.Assert.*;

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

import org.junit.Test;

import com.navercorp.pinpoint.common.server.bo.SpanBo;
import com.navercorp.pinpoint.common.server.bo.SpanEventBo;

/**
 * 
 * @author jaehong.kim
 *
 */
public class CallTreeTest {

    private static final boolean SYNC = false;
    private static final boolean ASYNC = true;

    private final SpanCallTree callTree = new SpanCallTree(makeSpanAlign());
    private final List<String> expectResult = new ArrayList<String>();

    @Test
    public void add() {
        expectResult.clear();
        expectResult.add("#");
        expectResult.add("##");
        expectResult.add("###");
        expectResult.add("####");
        expectResult.add("#####");

        callTree.add(1, makeSpanAlign(SYNC, (short) 0));
        callTree.add(2, makeSpanAlign(SYNC, (short) 1));
        callTree.add(3, makeSpanAlign(SYNC, (short) 2));
        callTree.add(4, makeSpanAlign(SYNC, (short) 3));
        assertDepth("add", callTree, expectResult);
    }

    @Test
    public void addAndSort() {
        expectResult.clear();
        expectResult.add("#");
        expectResult.add("##");
        expectResult.add("###");
        expectResult.add("####");
        expectResult.add("#####");

        callTree.add(1, makeSpanAlign(SYNC, (short) 0));
        callTree.add(2, makeSpanAlign(SYNC, (short) 1));
        callTree.add(3, makeSpanAlign(SYNC, (short) 2));
        callTree.add(4, makeSpanAlign(SYNC, (short) 3));
        assertDepth("addAndSort", callTree, expectResult);

        callTree.sort();
        assertDepth("addAndSort", callTree, expectResult);
    }

    @Test
    public void addLevel() {
        expectResult.clear();
        expectResult.add("#");
        expectResult.add("##");
        expectResult.add("###");
        expectResult.add("####");
        expectResult.add("#####");
        expectResult.add("#####");
        expectResult.add("#####");
        expectResult.add("#####");

        callTree.add(1, makeSpanAlign(SYNC, (short) 0));
        callTree.add(2, makeSpanAlign(SYNC, (short) 1));
        callTree.add(3, makeSpanAlign(SYNC, (short) 2));
        callTree.add(4, makeSpanAlign(SYNC, (short) 3));
        callTree.add(-1, makeSpanAlign(SYNC, (short) 4));
        callTree.add(-1, makeSpanAlign(SYNC, (short) 5));
        callTree.add(-1, makeSpanAlign(SYNC, (short) 6));
        assertDepth("addLevel", callTree, expectResult);
    }

    @Test
    public void addComplex() {
        expectResult.clear();
        expectResult.add("#");
        expectResult.add("##");
        expectResult.add("###");
        expectResult.add("####");
        expectResult.add("#####");
        expectResult.add("######");
        expectResult.add("######");
        expectResult.add("######");
        expectResult.add("######");
        expectResult.add("######");
        expectResult.add("#####");
        expectResult.add("#####");
        expectResult.add("######");
        expectResult.add("#######");
        expectResult.add("########");
        expectResult.add("########");
        expectResult.add("#####");
        expectResult.add("#####");
        expectResult.add("#####");

        callTree.add(1, makeSpanAlign(SYNC, (short) 0));
        callTree.add(2, makeSpanAlign(SYNC, (short) 1));
        callTree.add(3, makeSpanAlign(SYNC, (short) 2));
        callTree.add(4, makeSpanAlign(SYNC, (short) 3));
        callTree.add(5, makeSpanAlign(SYNC, (short) 4));
        callTree.add(-1, makeSpanAlign(SYNC, (short) 5));
        callTree.add(-1, makeSpanAlign(SYNC, (short) 6));
        callTree.add(-1, makeSpanAlign(SYNC, (short) 7));
        callTree.add(-1, makeSpanAlign(SYNC, (short) 8));
        callTree.add(4, makeSpanAlign(SYNC, (short) 9));
        callTree.add(-1, makeSpanAlign(SYNC, (short) 10));
        callTree.add(5, makeSpanAlign(SYNC, (short) 11));
        callTree.add(6, makeSpanAlign(SYNC, (short) 12));
        callTree.add(7, makeSpanAlign(SYNC, (short) 13));
        callTree.add(-1, makeSpanAlign(SYNC, (short) 14));
        callTree.add(4, makeSpanAlign(SYNC, (short) 15));
        callTree.add(-1, makeSpanAlign(SYNC, (short) 16));
        callTree.add(-1, makeSpanAlign(SYNC, (short) 17));
        assertDepth("addComplex", callTree, expectResult);
    }

    @Test
    public void addSubTree() {
        expectResult.clear();
        expectResult.add("#");
        expectResult.add("##");
        expectResult.add("###");
        expectResult.add("##");

        callTree.add(1, makeSpanAlign(SYNC, (short) 0));
        SpanCallTree subTree = new SpanCallTree(makeSpanAlign());
        callTree.add(subTree);
        callTree.add(-1, makeSpanAlign(SYNC, (short) 1));
        assertDepth("addSubTree", callTree, expectResult);
    }

    @Test
    public void addNestedSubTree() {
        expectResult.clear();
        expectResult.add("#");
        expectResult.add("##");
        expectResult.add("###");
        expectResult.add("####");
        expectResult.add("#####");

        callTree.add(1, makeSpanAlign(SYNC, (short) 0));
        SpanCallTree subTree = new SpanCallTree(makeSpanAlign(ASYNC, (short) 0));
        subTree.add(1, makeSpanAlign(ASYNC, (short) 1));

        SpanCallTree subTree2 = new SpanCallTree(makeSpanAlign(ASYNC, (short) 0));
        subTree.add(subTree2);
        callTree.add(subTree);
        assertDepth("addNestedSubTree", callTree, expectResult);
    }

    @Test
    public void missing() {
        expectResult.clear();
        expectResult.add("#");

        try {
            callTree.add(-1, makeSpanAlign(SYNC, (short) 20));
            callTree.add(-1, makeSpanAlign(SYNC, (short) 21));
            callTree.add(-1, makeSpanAlign(SYNC, (short) 22));
            callTree.add(-1, makeSpanAlign(SYNC, (short) 23));
        } catch (Exception ignored) {
        }
        assertDepth("missing-case-1", callTree, expectResult);
    }

    @Test
    public void missingMiddleNodes() {
        expectResult.clear();
        expectResult.add("#");
        expectResult.add("##");
        expectResult.add("###");
        expectResult.add("####");

        try {
            callTree.add(1, makeSpanAlign(SYNC, (short) 0));
            callTree.add(2, makeSpanAlign(SYNC, (short) 1));
            callTree.add(3, makeSpanAlign(SYNC, (short) 2));
            // callTree.add(4, makeSpanAlign(SYNC, (short) 3));
            // callTree.add(5, makeSpanAlign(SYNC, (short) 4));
            // callTree.add(-1, makeSpanAlign(SYNC, (short) 5));
            callTree.add(-1, makeSpanAlign(SYNC, (short) 6));
            callTree.add(-1, makeSpanAlign(SYNC, (short) 7));
            callTree.add(-1, makeSpanAlign(SYNC, (short) 8));
            callTree.add(4, makeSpanAlign(SYNC, (short) 9));
            callTree.add(-1, makeSpanAlign(SYNC, (short) 10));
            callTree.add(5, makeSpanAlign(SYNC, (short) 11));
            callTree.add(6, makeSpanAlign(SYNC, (short) 12));
            callTree.add(7, makeSpanAlign(SYNC, (short) 13));
            callTree.add(-1, makeSpanAlign(SYNC, (short) 14));
            callTree.add(4, makeSpanAlign(SYNC, (short) 15));
            callTree.add(-1, makeSpanAlign(SYNC, (short) 16));
            callTree.add(-1, makeSpanAlign(SYNC, (short) 17));
        } catch (Exception ignored) {
        }
        assertDepth("missing-case-2", callTree, expectResult);
    }

    @Test
    public void missingAsync() {
        expectResult.add("#");
        expectResult.add("##");

        callTree.add(1, makeSpanAlign(SYNC, (short) 0, 1, -1));
        try {
            SpanCallTree subTree = new SpanCallTree(makeSpanAlign());
            // subTree.add(1, makeSpanAlign(ASYNC, (short) 0, -1, 1));
            subTree.add(2, makeSpanAlign(ASYNC, (short) 1, 2, 1));
            callTree.add(subTree);
        } catch (Exception ignored) {
        }
        assertDepth("missing-case-3", callTree, expectResult);
    }

    @Test
    public void missingAsyncEvent() {
        expectResult.add("#");
        expectResult.add("##");
        expectResult.add("##");

        callTree.add(1, makeSpanAlign(SYNC, (short) 0, 1, -1));
        // callTree.add(makeSpanEventBo(ASYNC, (short) 0, 1));
        callTree.add(-1, makeSpanAlign(SYNC, (short) 1, -1, -1));
        assertDepth("missing-case-5", callTree, expectResult);

    }

    @Test
    public void sort() {
        expectResult.add("#");
        expectResult.add("##");
        expectResult.add("###"); // remote
        expectResult.add("####"); // remote
        expectResult.add("###");
        expectResult.add("###");
        expectResult.add("##");
        
        Align root = makeSpanAlign(0, 10);
        SpanCallTree callTree = new SpanCallTree(root);
        callTree.add(1, makeSpanAlign(root.getSpanBo(), SYNC, (short) 0, -1, -1, 1, 1));

        Align remoteRoot = makeSpanAlign(4, 5);
        SpanCallTree subTree = new SpanCallTree(remoteRoot);
        subTree.add(1, makeSpanAlign(remoteRoot.getSpanBo(), SYNC, (short) 0, -1, -1, 1, 1));
        callTree.add(subTree);

        callTree.add(2, makeSpanAlign(root.getSpanBo(), SYNC, (short) 1, -1, -1, 2, 1));
        callTree.add(-1, makeSpanAlign(root.getSpanBo(), SYNC, (short) 2, -1, -1, 3, 1));
        callTree.add(1, makeSpanAlign(root.getSpanBo(), SYNC, (short) 3, -1, -1, 4, 1));


        assertDepth("before sort", callTree, expectResult);
        
        callTree.sort();
        
        expectResult.clear();
        expectResult.add("#");
        expectResult.add("##");
        expectResult.add("###");
        expectResult.add("###");
        expectResult.add("###"); // remote
        expectResult.add("####"); // remote
        expectResult.add("##");

        assertDepth("after sort", callTree, expectResult);
    }

    @Test
    public void sort2() {
        expectResult.add("#");
        expectResult.add("##");
        expectResult.add("###"); // remote 1
        expectResult.add("####"); // remote 1
        expectResult.add("###"); // remote 2
        expectResult.add("###");

        Align root = makeSpanAlign(0, 10);
        SpanCallTree callTree = new SpanCallTree(root);
        callTree.add(1, makeSpanAlign(root.getSpanBo(), SYNC, (short) 0, -1, -1, 1, 1));

        Align remoteRoot1 = makeSpanAlign(4, 5);
        SpanCallTree subTree1 = new SpanCallTree(remoteRoot1);
        subTree1.add(1, makeSpanAlign(remoteRoot1.getSpanBo(), SYNC, (short) 0, -1, -1, 1, 1));
        callTree.add(subTree1);

        Align remoteRoot2 = makeSpanAlign(3, 4);
        SpanCallTree subTree2 = new SpanCallTree(remoteRoot2);
        callTree.add(subTree2);

        callTree.add(2, makeSpanAlign(root.getSpanBo(), SYNC, (short) 1, -1, -1, 2, 1));

        assertDepth("before sort", callTree, expectResult);

        callTree.sort();

        expectResult.clear();
        expectResult.add("#");
        expectResult.add("##");
        expectResult.add("###");
        expectResult.add("###"); // remote 2
        expectResult.add("###"); // remote 1
        expectResult.add("####"); // remote 1

        assertDepth("after sort", callTree, expectResult);
    }

    private void assertDepth(final String name, SpanCallTree tree, List<String> result) {
        int index = 0;
        CallTreeIterator iterator = tree.iterator();
        CallTreeNode node = null;
        while ((node = iterator.next()) != null) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i <= node.getDepth(); i++) {
                sb.append("#");
            }
            final String expected = result.get(index++);
            printDepth(name, index, sb.toString(), expected);
        }
    }

    private void printDepth(final String name, final int index, final String depth, final String expected) {
        if (!expected.equals(depth)) {
            fail("Not Matched " + name + "(" + index + "): " + depth + ", expected=" + expected);
        }
    }

    private Align makeSpanAlign() {
        return makeSpanAlign(0, 0);
    }

    private Align makeSpanAlign(long startTime, int elapsed) {
        SpanBo span = new SpanBo();
        span.setStartTime(startTime);
        span.setElapsed(elapsed);

        return new SpanAlign(span);
    }

    private Align makeSpanAlign(final boolean async, final short sequence) {
        return makeSpanAlign(async, sequence, -1, -1);
    }

    private Align makeSpanAlign(final boolean async, final short sequence, final int nextAsyncId, final int asyncId) {
        return makeSpanAlign(new SpanBo(), async, sequence, nextAsyncId, asyncId, -1, -1);
    }

    private Align makeSpanAlign(SpanBo span, final boolean async, final short sequence, int nextAsyncId, final int asyncId, int startElapsed, int endElapsed) {
        SpanEventBo event = new SpanEventBo();
        event.setAsyncId(async ? 1 : -1);
        event.setSequence(sequence);
        event.setNextAsyncId(nextAsyncId);
        event.setAsyncId(asyncId);
        event.setStartElapsed(startElapsed);
        event.setEndElapsed(endElapsed);

        return new SpanEventAlign(span, event);
    }
}