/**
 * 
 */
package org.sagacity.sqltoy.dialect.utils;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.sagacity.sqltoy.SqlToyContext;
import org.sagacity.sqltoy.config.model.SqlToyConfig;
import org.sagacity.sqltoy.executor.QueryExecutor;
import org.sagacity.sqltoy.utils.CollectionUtil;

/**
 * @project sagacity-sqltoy4.0
 * @description 提供分页优化缓存实现,记录相同查询条件的总记录数,采用FIFO算法保留符合活跃时间和记录规模
 * @author chenrenfei <a href="mailto:[email protected]">联系作者</a>
 * @version id:PageOptimizeCache.java,Revision:v1.0,Date:2016年11月24日
 */
public class PageOptimizeUtils {
	private static final int INITIAL_CAPACITY = 128;
	private static final float LOAD_FACTOR = 0.75f;

	// 定义sql对应的分页查询请求(不同查询条件构成的key)对应的总记录数Object[] as
	// {expireTime(失效时间),recordCount(分页查询总记录数)}
	private static ConcurrentHashMap<String, LinkedHashMap<String, Object[]>> pageOptimizeCache = new ConcurrentHashMap<String, LinkedHashMap<String, Object[]>>(
			INITIAL_CAPACITY, LOAD_FACTOR);

	/**
	 * @todo 根据查询条件组成key
	 * @param sqlToyContext
	 * @param sqlToyConfig
	 * @param queryExecutor
	 * @return
	 * @throws Exception
	 */
	public static String generateOptimizeKey(final SqlToyContext sqlToyContext, final SqlToyConfig sqlToyConfig,
			final QueryExecutor queryExecutor) throws Exception {
		// 没有开放分页优化或sql id为null都不执行优化操作
		if (!sqlToyConfig.isPageOptimize() || null == sqlToyConfig.getId())
			return null;

		String[] paramNames = queryExecutor.getParamsName(sqlToyConfig);
		Object[] paramValues = queryExecutor.getParamsValue(sqlToyContext, sqlToyConfig);
		// sql中所有参数都为null,返回sqlId作为key
		if (paramValues == null || paramValues.length == 0)
			return sqlToyConfig.getId();
		StringBuilder cacheKey = new StringBuilder();
		boolean isParamsNamed = true;
		if (null == paramNames || paramNames.length == 0) {
			isParamsNamed = false;
		}
		int i = 0;
		// 循环查询条件的值构造key
		for (Object value : paramValues) {
			if (i > 0) {
				cacheKey.append(",");
			}
			if (isParamsNamed) {
				cacheKey.append(paramNames[i]).append("=");
			} else {
				cacheKey.append("p_").append(i).append("=");
			}
			if (value == null) {
				cacheKey.append("null");
			} else if ((value instanceof Object[]) || value.getClass().isArray() || (value instanceof List)) {
				Object[] arrayValue = (value instanceof List) ? ((List) value).toArray()
						: CollectionUtil.convertArray(value);
				cacheKey.append("[");
				for (Object obj : arrayValue) {
					cacheKey.append((obj == null) ? "null" : obj.toString()).append(",");
				}
				cacheKey.append("]");
			} else {
				cacheKey.append(value.toString());
			}
			i++;
		}
		return cacheKey.toString();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.sagacity.sqltoy.cache.PageOptimizeCache#getPageTotalCount(java.lang.
	 * String, java.lang.String)
	 */
	public static Long getPageTotalCount(final SqlToyConfig sqlToyConfig, String conditionsKey) {
		LinkedHashMap<String, Object[]> map = pageOptimizeCache.get(sqlToyConfig.getId());
		// sql初次执行查询
		if (null == map) {
			return null;
		}
		Object[] values = map.get(conditionsKey);
		// 为null表示条件初次查询或已经全部过期移除
		if (null == values)
			return null;
		// 总记录数
		Long totalCount = (Long) values[1];
		// 失效时间
		long expireTime = (Long) values[0];
		long nowTime = System.currentTimeMillis();

		// 先移除(为了调整排列顺序)
		map.remove(conditionsKey);
		// 超时,返回null表示需要重新查询,并不需要定时检测
		// 1、控制总记录数量,最早的始终会排在最前面,会最先排挤出去
		// 2、每次查询时相同的条件会自动检测是否过期,过期则会重新执行
		if (nowTime >= expireTime) {
			return null;
		}
		// 重置过期时间
		values[0] = nowTime + sqlToyConfig.getPageAliveSeconds() * 1000;
		// 重新置于linkedHashMap的最后位置
		map.put(conditionsKey, values);
		return totalCount;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.sagacity.sqltoy.cache.PageOptimizeCache#put(java.lang.String,
	 * java.lang.String)
	 */
	public static void registPageTotalCount(final SqlToyConfig sqlToyConfig, String pageQueryKey, Long totalCount) {
		long nowTime = System.currentTimeMillis();
		// 当前时间
		long expireTime = nowTime + sqlToyConfig.getPageAliveSeconds() * 1000;
		// 同一个分页查询sql保留的不同查询条件记录数量
		int aliveMax = sqlToyConfig.getPageAliveMax();
		LinkedHashMap<String, Object[]> map = pageOptimizeCache.get(sqlToyConfig.getId());
		if (null == map) {
			map = new LinkedHashMap<String, Object[]>(sqlToyConfig.getPageAliveMax());
			map.put(pageQueryKey, new Object[] { expireTime, totalCount });
			pageOptimizeCache.put(sqlToyConfig.getId(), map);
		} else {
			map.put(pageQueryKey, new Object[] { expireTime, totalCount });
			// 长度超阀值,移除最早进入的
			while (map.size() > aliveMax) {
				map.remove(map.keySet().iterator().next());
			}

			// 剔除过期数据
			Iterator<Map.Entry<String, Object[]>> iter = map.entrySet().iterator();
			Map.Entry<String, Object[]> entry;
			while (iter.hasNext()) {
				entry = iter.next();
				// 当前时间已经大于过期时间
				if (nowTime >= ((Long) entry.getValue()[0])) {
					iter.remove();
				} else {
					break;
				}
			}
		}
	}

	/**
	 * @todo 清除掉sql对应的分页count缓存
	 * @param sqlId
	 */
	public static void remove(String sqlId) {
		pageOptimizeCache.remove(sqlId);
	}
}