package com.ywt.mg.core.structs;

import java.io.Serializable;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Trie 树
 * Created by huangguoping on 16/9/13.
 */
public class TrieTree<T extends Serializable> implements Serializable {

    private Node<T> root;
    public TrieTree(){
        root = new Node<>();
    }

    public void insert(String key, T data){
        if (key == null || key.equals("")) return;
        Node<T> tempNode = root;
        for (int i = 0; i < key.length(); i++){
            if (tempNode.getChildren() == null) tempNode.setChildren(new HashMap<Character, Node<T>>());
            Character c = key.charAt(i);
            //System.out.println(c);
            if (tempNode.getChildren().containsKey(c)){
                //System.out.println(c);
                tempNode = tempNode.getChildren().get(c);
            }else{
                Node<T> node_t = new Node<>();
                tempNode.getChildren().put(c, node_t);
                tempNode = node_t;
            }
        }
        tempNode.setHitNum(tempNode.getHitNum() + 1);
        tempNode.setData(data);
        tempNode.setIshit(true);
    }

    private int _search(Node<T> node, String content, int next_i, int len, List<T> hitNodeDatas, List<String> hitKeys, StringBuffer stringBuffer){
        if (next_i >= len) return next_i;
        Character c = content.charAt(next_i);
        stringBuffer.append(c);
        if (node.getChildren().containsKey(c)){
            Node<T> nextNode = node.children.get(c);
            if (nextNode.ishit){
                hitKeys.add(stringBuffer.toString());
                hitNodeDatas.add(nextNode.data);
            }
            if (nextNode.children == null || nextNode.children.size() == 0){
                return next_i + 1;
            }
            return _search(nextNode, content, next_i + 1, len, hitNodeDatas, hitKeys, stringBuffer);
        }else {
            return next_i;
        }
    }

    public SearchResult<T> search(String content){
        SearchResult<T> searchResult = new SearchResult<>();

        if (content == null) return searchResult;
        int len = content.length();
        if (len == 0) return searchResult;

        StringBuffer stringBuffer = null;

        if(root.getChildren() == null || root.getChildren().size() == 0) return searchResult;
        List<T> hitNodeDataLstAll = new LinkedList<>();
        List<T> hitNodeDatasTemp = new LinkedList<>();
        List<String> hitKeysTemp = new LinkedList<>();
        Map<String, Integer> hitsCount = new HashMap<>();
        int curr_i = 0;
        while (curr_i < len){
            hitNodeDatasTemp.clear();
            hitKeysTemp.clear();
            stringBuffer = new StringBuffer();
            int next_i = this._search(root, content, curr_i, len, hitNodeDatasTemp, hitKeysTemp, stringBuffer);
            if (next_i > curr_i) {
                if (hitNodeDatasTemp.size() > 0) {
                    String key_t = hitKeysTemp.get(hitKeysTemp.size() - 1);
                    curr_i += key_t.length();
                    if (hitsCount.containsKey(key_t)){
                        hitsCount.replace(key_t, hitsCount.get(key_t) + 1);
                    }else {
                        hitNodeDataLstAll.add(hitNodeDatasTemp.get(hitNodeDatasTemp.size() - 1));
                        hitsCount.put(key_t, 1);
                    }
                } else {
                    curr_i++;
                }
            }
            else
                curr_i++;
        }

        searchResult.setHitsCount(hitsCount);
        searchResult.setHitNodeDatas(hitNodeDataLstAll);
        return searchResult;
    }

    public List<T> searchByPrefix(String prefix){
        if(prefix == null || prefix.length() == 0) return null;
        int len = prefix.length();
        int curr_i = 0;

        if (root.children == null || root.children.size() == 0) return null;
        Node<T> tempNode = root;
        while (curr_i < len) {
            Character c = prefix.charAt(curr_i);
            if (tempNode.children.containsKey(c)) {
                curr_i++;
                tempNode = tempNode.children.get(c);
                if (tempNode.children == null || tempNode.children.size() == 0) break;
            } else {
                break;
            }
        }

        List<T> hitNodeDataLstAll = new LinkedList<>();
        if (curr_i > 0 && curr_i == len){
            traversal(tempNode, hitNodeDataLstAll);
        }
        return hitNodeDataLstAll;
    }

    private void traversal(Node<T> node, List<T> hitNodeDatas){
        if (node.ishit){
            hitNodeDatas.add(node.data);
        }
        if (node.children == null || node.children.size() == 0) return;
        for (Map.Entry<Character, Node<T>> entry : node.children.entrySet()){
            traversal(entry.getValue(), hitNodeDatas);
        }
    }

    public class Node<T extends Serializable> implements Serializable {
        private T data;
        private int hitNum;
        private boolean ishit = false;

        private Map<Character, Node<T>> children = null;

        public T getData() {
            return data;
        }

        public void setData(T data) {
            this.data = data;
        }

        public int getHitNum() {
            return hitNum;
        }

        public void setHitNum(int hitNum) {
            this.hitNum = hitNum;
        }

        public Map<Character, Node<T>> getChildren() {
            return children;
        }

        public void setChildren(Map<Character, Node<T>> children) {
            this.children = children;
        }

        public boolean isIshit() {
            return ishit;
        }

        public void setIshit(boolean ishit) {
            this.ishit = ishit;
        }
    }

    public class SearchResult<T extends Serializable> {
        private List<T> hitNodeDatas = null;
        private Map<String, Integer> hitsCount = null;

        public SearchResult(){
            this.hitNodeDatas = new LinkedList<>();
            this.hitsCount = new HashMap<>();
        }

        public List<T> getHitNodeDatas() {
            return hitNodeDatas;
        }

        public void setHitNodeDatas(List<T> hitNodeDatas) {
            this.hitNodeDatas = hitNodeDatas;
        }

        public Map<String, Integer> getHitsCount() {
            return hitsCount;
        }

        public void setHitsCount(Map<String, Integer> hitsCount) {
            this.hitsCount = hitsCount;
        }
    }
}
