package com.github.emacsist;

import org.apache.commons.lang.StringUtils;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

import static java.lang.System.currentTimeMillis;

/**
 * Created by sky on 16-7-15.
 */
public class TTree {

    private static final String STEP_FIRST_CHAR = "`---";
    private static final String STEP_NORMAL_CHAR = "+---";
    private static final String STEP_HAS_BOARD = "|   ";
    private static final String STEP_EMPTY_BOARD = "    ";

    // 是否输出耗时
    private final boolean isPrintCost;

    // 根节点
    private final Node root;

    // 当前节点
    private Node current;


    public TTree(boolean isPrintCost, String title) {
        this.root = new Node(title).markBegin().markEnd();
        this.current = root;
        this.isPrintCost = isPrintCost;
    }

    public String rendering() {

        final StringBuilder treeSB = new StringBuilder();
        recursive(0, true, "", root, new Callback() {

            @Override
            public void callback(int deep, boolean isLast, String prefix, Node node) {
                if (node.endTimestamp != 0) {
                    final boolean hasChild = !node.children.isEmpty();
                    final String stepString = isLast ? STEP_FIRST_CHAR : STEP_NORMAL_CHAR;
                    final int stepStringLength = StringUtils.length(stepString);
                    treeSB.append(prefix).append(stepString);

                    int costPrefixLength = 0;
                    if (hasChild) {
                        treeSB.append("+");
                    }
                    if (isPrintCost
                            && !node.isRoot()) {
                        final String costPrefix = String.format("[%s,%sms]", (node.endTimestamp - root.beginTimestamp), (node.endTimestamp - node.beginTimestamp));
                        costPrefixLength = StringUtils.length(costPrefix);
                        treeSB.append(costPrefix);
                    }

                    final Scanner scanner = new Scanner(new StringReader(node.data.toString()));
                    try {
                        boolean isFirst = true;
                        while (scanner.hasNextLine()) {
                            if (isFirst) {
                                treeSB.append(scanner.nextLine()).append("\n");
                                isFirst = false;
                            } else {
                                treeSB
                                        .append(prefix)
                                        .append(StringUtils.repeat(" ", stepStringLength))
                                        .append(hasChild ? "|" : " ")
                                        .append(StringUtils.repeat(" ", costPrefixLength))
                                        .append(scanner.nextLine())
                                        .append("\n");
                            }
                        }
                    } finally {
                        scanner.close();
                    }
                }
            }

        });

        return treeSB.toString();
    }

    /**
     * 递归遍历
     */
    private void recursive(int deep, boolean isLast, String prefix, Node node, Callback callback) {
        callback.callback(deep, isLast, prefix, node);
        if (!node.isLeaf()) {
            final int size = node.children.size();
            for (int index = 0; index < size; index++) {
                final boolean isLastFlag = index == size - 1;
                final String currentPrefix = isLast ? prefix + STEP_EMPTY_BOARD : prefix + STEP_HAS_BOARD;
                recursive(
                        deep + 1,
                        isLastFlag,
                        currentPrefix,
                        node.children.get(index),
                        callback
                );
            }
        }
    }

    public boolean isTop() {
        return current.isRoot();
    }

    /**
     * 创建一个分支节点
     *
     * @param data 节点数据
     * @return this
     */
    public TTree begin(Object data) {
        current = new Node(current, data);
        current.markBegin();
        return this;
    }

    public TTree begin() {
        return begin(null);
    }

    public Object get() {
        if (current.isRoot()) {
            throw new IllegalStateException("current node is root.");
        }
        return current.data;
    }

    public TTree set(Object data) {
        if (current.isRoot()) {
            throw new IllegalStateException("current node is root.");
        }
        current.data = data;
        return this;
    }

    /**
     * 结束一个分支节点
     *
     * @return this
     */
    public TTree end() {
        if (current.isRoot()) {
            throw new IllegalStateException("current node is root.");
        }
        current.markEnd();
        current = current.parent;
        return this;
    }


    /**
     * 遍历回调接口
     */
    private interface Callback {

        void callback(int deep, boolean isLast, String prefix, Node node);

    }

    /**
     * 树节点
     */
    private static class Node {

        /**
         * 父节点
         */
        final Node parent;
        /**
         * 子节点
         */
        final List<Node> children = new ArrayList<Node>();
        /**
         * 节点数据
         */
        Object data;
        /**
         * 开始时间戳
         */
        private long beginTimestamp;

        /**
         * 结束时间戳
         */
        private long endTimestamp;

        /**
         * 构造树节点(根节点)
         */
        private Node(Object data) {
            this.parent = null;
            this.data = data;
        }

        /**
         * 构造树节点
         *
         * @param parent 父节点
         * @param data   节点数据
         */
        private Node(Node parent, Object data) {
            this.parent = parent;
            this.data = data;
            parent.children.add(this);
        }

        /**
         * 是否根节点
         *
         * @return true / false
         */
        boolean isRoot() {
            return null == parent;
        }

        /**
         * 是否叶子节点
         *
         * @return true / false
         */
        boolean isLeaf() {
            return children.isEmpty();
        }

        Node markBegin() {
            beginTimestamp = currentTimeMillis();
            return this;
        }

        Node markEnd() {
            endTimestamp = currentTimeMillis();
            return this;
        }

    }

}
