/* ==================================================================   
 * Created [2015-9-12] by Jon.King 
 * ==================================================================  
 * TSS 
 * ================================================================== 
 * mailTo:[email protected]
 * Copyright (c) boubei.com, 2015-2018 
 * ================================================================== 
 */

package com.boubei.tss.cache.extension;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.boubei.tss.EX;
import com.boubei.tss.PX;
import com.boubei.tss.cache.AbstractPool;
import com.boubei.tss.cache.CacheStrategy;
import com.boubei.tss.cache.Cacheable;
import com.boubei.tss.cache.JCache;
import com.boubei.tss.cache.Pool;
import com.boubei.tss.framework.web.display.grid.DefaultGridNode;
import com.boubei.tss.framework.web.display.grid.GridDataEncoder;
import com.boubei.tss.framework.web.display.grid.IGridNode;
import com.boubei.tss.framework.web.display.tree.DefaultTreeNode;
import com.boubei.tss.framework.web.display.tree.ITreeNode;
import com.boubei.tss.framework.web.display.tree.TreeEncoder;
import com.boubei.tss.framework.web.display.xform.XFormEncoder;
import com.boubei.tss.framework.web.mvc.BaseActionSupport;
import com.boubei.tss.modules.param.Param;
import com.boubei.tss.modules.param.ParamManager;
import com.boubei.tss.modules.param.ParamService;
import com.boubei.tss.util.BeanUtil;
import com.boubei.tss.util.DateUtil;
import com.boubei.tss.util.EasyUtils;
import com.boubei.tss.util.MathUtil;
import com.boubei.tss.util.XMLDocUtil;

@Controller
@RequestMapping("/cache")
public class CacheAction extends BaseActionSupport {
	
	protected Logger log = Logger.getLogger(this.getClass());
    
    /** 缓存策略模板目录 */
    final static String CACHESTRATEGY_XFORM_TEMPLET = "template/cache/strategy_xform.xml";
    final static String POOLS_GRID_TEMPLET = "template/cache/pool_grid.xml";
 
    private static JCache cache = JCache.getInstance();
    
    @Autowired public ParamService paramService;
    
    /**
     * 一般改的是池大小及等待时间等,只需更新pool对应的策略对象,无需重新生成pool对象。
     */
    @RequestMapping(method = RequestMethod.POST)
    public void modifyCacheConfig(HttpServletResponse response, String cacheCode, String jsonData) {
		
		// 将更新信息保存到系统参数模块(ParamListener-->PCache将会执行 rebuildCache)
		Param cacheGroup = CacheHelper.getCacheParamGroup(paramService);
		
		Param cacheParam = null;
		List<Param> cacheParams = paramService.getParamsByParentCode(PX.CACHE_PARAM);
		for(Param temp : cacheParams) {
			if(temp.getCode().equals(cacheCode)) {
				cacheParam = temp;
				break;
			}
		}
		if(cacheParam == null) { // 新建一个对象池Param配置
			Long parentId = cacheGroup.getId();
			String name = cache.getPool(cacheCode).getCacheStrategy().getName();
			cacheParam = ParamManager.addSimpleParam(parentId, cacheCode, name, jsonData);
		}
		else {
			cacheParam.setValue(jsonData);
	        paramService.saveParam(cacheParam);
		}
        
        printSuccessMessage();
    }
 
    /**
     * 树型展示所有缓存池
     */
    @RequestMapping("/list")
    public void getAllCacheStrategy4Tree(HttpServletResponse response) {
        List<CacheStrategy> strategyList = new ArrayList<CacheStrategy>();
        
        Set<Entry<String, Pool>> pools = cache.listCachePools(); 
        for(Entry<String, Pool> entry : pools) {
            Pool pool = entry.getValue(); 
            strategyList.add(pool.getCacheStrategy());
        }
        
        List<ITreeNode> treeNodeList = new ArrayList<ITreeNode>();
        for(final CacheStrategy stategy : strategyList) {
        	DefaultTreeNode node = new DefaultTreeNode(stategy.code, stategy.name);
        	node.getAttributes().put("icon", "images/cache.gif");
        	node.getAttributes().put("display", stategy.visible);
        	
            treeNodeList.add(node);
        }
        
        TreeEncoder encoder = new TreeEncoder(treeNodeList);
        encoder.setNeedRootNode(false);
        print("CacheTree", encoder);
    }
    
    @RequestMapping("/grid")
    public void getPoolsGrid(HttpServletResponse response) {
    	
    	List<IGridNode> dataList = new ArrayList<IGridNode>();
    	 
    	Set<Entry<String, Pool>> pools = cache.listCachePools(); 
        for(Entry<String, Pool> entry : pools) {
            Pool pool = entry.getValue(); 
            CacheStrategy strategy = pool.getCacheStrategy();
            
            DefaultGridNode gridNode = new DefaultGridNode();
            Map<String, Object> attrs = gridNode.getAttrs();
			attrs.put("code", entry.getKey());
            attrs.put("name", pool.getName());
            attrs.put("accessMethod", strategy.getAccessMethod());
            attrs.put("disabled", strategy.getDisabled());
            attrs.put("cyclelife", strategy.getCyclelife() / 1000);
            attrs.put("interruptTime", strategy.getInterruptTime());
            attrs.put("poolSize", strategy.getPoolSize());
            attrs.put("initNum", strategy.getInitNum());
            attrs.put("requests", pool.getRequests());
            attrs.put("hitrate", Math.round( pool.getHitRate() ) + "%");
            attrs.put("hitLong", Math.round( pool.getHitLong() / Math.max(1, pool.getRequests()) ));
            
        	AbstractPool _pool = (AbstractPool)pool;
        	int busys = _pool.getUsing().size();
			attrs.put("freeItemNum", _pool.getFree().size());
			attrs.put("busyItemNum", busys > 0 ? "<b>" + busys + "</b>" : busys);
            
            dataList.add(gridNode);
        }
        
        GridDataEncoder gEncoder = new GridDataEncoder(dataList, POOLS_GRID_TEMPLET);
        print("PoolGrid", gEncoder);
    }
    
    /**
     * 获取缓存策略以及缓存池信息
     */
    @RequestMapping("/list/{code}")
    public void listCacheItems(HttpServletResponse response, @PathVariable String code) {
        Pool pool = cache.getPool(code);
        CacheStrategy strategy = pool.getCacheStrategy();
        Map<String, Object> strategyProperties = new HashMap<String, Object>();
        BeanUtil.addBeanProperties2Map(strategy, strategyProperties);
        
        XFormEncoder xEncoder = new XFormEncoder(CACHESTRATEGY_XFORM_TEMPLET, strategyProperties); 
        String hitRate = Math.round(pool.getHitRate()) + "%";
        
        Set<Cacheable> cachedItems = pool.listItems();
        long requests = strategy.getPoolInstance().getRequests();
        List<IGridNode> dataList = new ArrayList<IGridNode>();
        for(Cacheable item : cachedItems) {
            int hit = item.getHit();
            Object thisKey = item.getKey();
            int hitrate = MathUtil.calPercent(hit, requests);
            long birthday = item.getBirthday();
            long now = System.currentTimeMillis();
            
            DefaultGridNode gridNode = new DefaultGridNode();
            gridNode.getAttrs().put("id", thisKey);
            gridNode.getAttrs().put("key", thisKey.toString());
            gridNode.getAttrs().put("code", code);
            gridNode.getAttrs().put("death", item.getDeath());
			gridNode.getAttrs().put("birthday", DateUtil.formatCare2Second(new Date(birthday)) );
			gridNode.getAttrs().put("age", (now - birthday)/1000 );
            gridNode.getAttrs().put("hit", new Integer(hit));
			gridNode.getAttrs().put("hitRate", hitrate + "%");
            gridNode.getAttrs().put("remark", item.getValue());
            
        	boolean isFree = ( (AbstractPool)pool ).getFree().keySet().contains(thisKey);
        	if( !isFree ) {
        		gridNode.getAttrs().put("state", "1");
        		gridNode.getAttrs().put("hitLong", (now - item.getAccessed()) / 1000);
        	} 
            
            dataList.add(gridNode);
        }
        
        StringBuffer template = new StringBuffer();
        template.append("<grid><declare sequence=\"true\">");
        template.append("<column name=\"id\" mode=\"string\" display=\"none\"/>");
        template.append("<column name=\"code\" mode=\"string\" display=\"none\"/>");
        template.append("<column name=\"key\" caption=\"key\" mode=\"string\" width=\"300px\" sortable=\"true\"/>");
        template.append("<column name=\"birthday\" caption=\"出生时间\" mode=\"string\" width=\"100px\"/>");
        template.append("<column name=\"age\" caption=\"已存活(秒)\" mode=\"string\" width=\"60px\" sortable=\"true\"/>");
        template.append("<column name=\"hitLong\" caption=\"当前占时(秒)\" mode=\"string\" width=\"70px\" sortable=\"true\"/>");
        template.append("<column name=\"hit\" caption=\"命中次数\" mode=\"string\" width=\"60px\" sortable=\"true\"/>");
        template.append("<column name=\"hitRate\" caption=\"命中率\" mode=\"string\" width=\"50px\"/>");
        template.append("<column name=\"state\" caption=\"状态 \" mode=\"string\" width=\"50px\" values=\"0|1\" texts=\"空闲|忙碌\"/>");
        template.append("<column name=\"remark\" caption=\"说明\" mode=\"string\" width=\"120px\"/>");
        template.append("</declare><data></data></grid>");
        
        GridDataEncoder gEncoder = new GridDataEncoder(dataList, XMLDocUtil.dataXml2Doc(template.toString()));
           
        int totalRows = cachedItems.size();
        String pageInfo = generatePageInfo(totalRows, 1, totalRows + 1, totalRows); // 加入分页信息,总是只有一页。
        print(new String[]{"CacheStrategy", "CacheItemList", "PageInfo", "HitRate"}, 
                new Object[]{xEncoder, gEncoder, pageInfo, hitRate});
    }
    
    /**
     * 查看详细的缓存项内容。对象XML格式展示
     */
    @RequestMapping("/item/{code}")
    public void viewCachedItem(HttpServletResponse response, 
    		@PathVariable String code, 
    		@RequestParam("key") String key) {
    	
        Cacheable item = cache.getPool(code).getObject(key);
        if(item != null) {
            String returnStr = EasyUtils.obj2Json(item.getValue());
            print(returnStr);
        }
        else {
            print(EX.CACHE_5);
        }
    }
    
    @RequestMapping(value = "/item/{code}", method = RequestMethod.DELETE)
    public void removeCachedItem(HttpServletResponse response, 
    		@PathVariable String code, 
    		@RequestParam("key") String key) {
    	
        Pool pool = cache.getPool(code);
		boolean rt = pool.destroyByKey(key);
        printSuccessMessage( !rt ? "destroy succeed。" : EX.CACHE_5);
    }
    
    /**
     * 清空释放缓存池
     */
    @RequestMapping("/release/{code}")
    public void releaseCache(HttpServletResponse response, @PathVariable String code){
        cache.getPool(code).flush();
        printSuccessMessage();
    }
    
    /**
     * 初始化缓存池
     */
    @RequestMapping("/init/{code}")
    public void initPool(HttpServletResponse response, @PathVariable String code){
        cache.getPool(code).init();
        printSuccessMessage();
    }
}