package com.hzya.frame.page;

import com.hzya.frame.web.exception.BaseSystemException;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.mapping.MappedStatement.Builder;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.RowBounds;

import java.util.*;

@Intercepts({ @org.apache.ibatis.plugin.Signature(type = Executor.class, method = "query", args = {
		MappedStatement.class, Object.class, RowBounds.class, org.apache.ibatis.session.ResultHandler.class }) })
public class PageHelper implements Interceptor {
	public static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
	public static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
	private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();
	public static final MetaObject NULL_META_OBJECT;
	private static final String BOUND_SQL = "sqlSource.boundSql.sql";
	private static final ThreadLocal<Page> LOCAL_PAGE;
	private static final List<ResultMapping> EMPTY_RESULTMAPPING;
	private static String dialect;
	private static boolean offsetAsPageNum;
	private static boolean rowBoundsWithCount;
	private static boolean pageSizeZero;
	public static   ThreadLocal<String> dialect_type;
	public static final String MYSQL = "mysql";
	public static final String SQLSERVER = "sqlServer";
	public static final String ORACLE = "oracle";
	static {
		NULL_META_OBJECT = MetaObject.forObject(NullObject.class, DEFAULT_OBJECT_FACTORY,DEFAULT_OBJECT_WRAPPER_FACTORY,DEFAULT_REFLECTOR_FACTORY);
		LOCAL_PAGE = new ThreadLocal();
		EMPTY_RESULTMAPPING = new ArrayList(0);
		dialect = "";
		offsetAsPageNum = false;
		rowBoundsWithCount = false;
		pageSizeZero = false;
		dialect_type = new ThreadLocal<>();
	}

	public PageHelper() {
	}

	public static MetaObject forObject(Object object) {
		return MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);
	}

	public static void startPage(int pageNum, int pageSize) {
		startPage(pageNum, pageSize, true);
	}

	public static void startPage(int pageNum, int pageSize, boolean count) {
		LOCAL_PAGE.set(new Page(pageNum, pageSize, count));
	}

	private Page getPage(RowBounds rowBounds) {
		Page page = LOCAL_PAGE.get();
		LOCAL_PAGE.remove();
		if (page == null) {
			if (offsetAsPageNum) {
				page = new Page(rowBounds.getOffset(), rowBounds.getLimit(), rowBoundsWithCount);
			} else {
				page = new Page(rowBounds, rowBoundsWithCount);
			}
		}

		return page;
	}

	public Object intercept(Invocation invocation) throws Throwable {
		/*if(!"mysql".equals(dialect)){
			return invocation.proceed();
		}*/
		Object[] args = invocation.getArgs();
		RowBounds rowBounds = (RowBounds) args[2];
		if (LOCAL_PAGE.get() == null && rowBounds == RowBounds.DEFAULT) {
			return invocation.proceed();
		} else {
			args[2] = RowBounds.DEFAULT;
			MappedStatement ms = (MappedStatement) args[0];
			Object parameterObject = args[1];
			BoundSql boundSql = ms.getBoundSql(parameterObject);
			Page page = this.getPage(rowBounds);
			if (pageSizeZero && page.getPageSize() == 0) {
				Object qs1 = invocation.proceed();
				page.addAll((List) qs1);
				page.setPageSize(page.size());
				page.setTotal((long) page.size());
				return page;
			} else {
				MappedStatement qs = this.newMappedStatement(ms, new BoundSqlSqlSource(boundSql));
				args[0] = qs;
				MetaObject msObject = forObject(qs);
				String sql = (String) msObject.getValue("sqlSource.boundSql.sql");
				Object result;
				if (page.isCount()) {
					msObject.setValue("sqlSource.boundSql.sql", this.getCountSql(sql));
					result = invocation.proceed();
					page.setTotal((long) ((Integer) ((List) result).get(0)).intValue());
					if (page.getTotal() == 0L) {
						return page;
					}
				}

				if (page.getPageSize() > 0) {
					msObject.setValue("sqlSource.boundSql.sql", this.getPageSql(sql, page));
					msObject.setValue("resultMaps", ms.getResultMaps());
					result = invocation.proceed();
					page.addAll((List) result);
				}

				return page;
			}
		}
	}

	private String getCountSql(String sql) {
		sql = formatterSql( sql).get("resultSql");
		return "select count(0) from (" + sql + ") tmp_count";
	}

	private String getPageSql(String sql, Page page) {
		StringBuilder pageSql = new StringBuilder(200);
		if (MYSQL.equals(dialect_type.get())) {
			pageSql.append("select * from (");
			pageSql.append(sql);
			pageSql.append(") as tmp_page limit " + page.getStartRow() + "," + page.getPageSize());
		} else if ("hsqldb".equals(dialect)) {
			pageSql.append(sql);
			pageSql.append(" LIMIT " + page.getPageSize() + " OFFSET " + page.getStartRow());
		} else if (ORACLE.equals(dialect)) {
			pageSql.append("select * from ( select temp.*, rownum row_id from ( ");
			pageSql.append(sql);
			pageSql.append(" ) temp where rownum <= ").append(page.getEndRow());
			pageSql.append(") where row_id > ").append(page.getStartRow());
		} else if (SQLSERVER.equals(dialect_type.get())) {
		/*	pageSql.append("select top ").append(page.getPageSize()).append(" * from(");
			pageSql.append(sql);
			pageSql.append(" ) temp where temp.id 	not in (");
			pageSql.append("select top ").append(page.getStartRow()).append(" id from(");
			pageSql.append(sql);
			pageSql.append(") temp_result ");
			pageSql.append(")");*/
			pageSql.append("SELECT * from (");
			pageSql.append("select  ROW_NUMBER () OVER (ORDER BY (select 0)) AS row_id_t, * from(");
			/** 判断sql 内是否有排序，如果有，把排序截取出来，然后放到最后 ORDER BY*/
			String resultOrderBy = null;
			if(sql.indexOf("order by") == -1){
				pageSql.append(sql);
			}else{
				resultOrderBy = formatterSql(sql).get("resultOrderBy");
				pageSql.append(formatterSql(sql).get("resultSql"));
			}

			pageSql.append(" ) temp ");
			pageSql.append(")as temp_t");
			/** 找到temp最后的位置，截取到最后，如果存在where 拼接AND 否则拼接where*/
			int lastIndex = pageSql.toString().lastIndexOf("temp");
			String tempSql = pageSql.toString().substring(lastIndex,pageSql.toString().length());
			/** 根据当前页码计算 开始的row_number 和结束 row_number  第一页   start =(1-1)*20  end = 1*20   */
			/** 开始行号*/
			int start =(page.getPageNum()-1) * page.getPageSize();
			/**  结束行号*/
			int end = page.getPageNum() *page.getPageSize();


			if (!tempSql.contains("where")){//这里这样写有问题，不能这样写
				pageSql.append(" where  temp_t.row_id_t   >"+ start +" and  temp_t.row_id_t <= "+end);
			}else{
				pageSql.append(" and  temp_t.row_id_t >  "+ start +" and  temp_t.row_id_t <= "+end);
			}
			if(null != resultOrderBy){
				pageSql.append(" "+resultOrderBy+" ");
			}
		}

		return pageSql.toString();
	}

	/** 根据sql 判断sql内是否有 orderby语句，如果有，截取出来*/
	private Map<String,String> formatterSql(String sql){
		Map<String,String> result = new HashMap<>();
		int index = sql.indexOf("order by");
		String resultOrderBy;
		String resultSql;
		if (index > 0){
			resultSql = sql.substring(0,sql.indexOf("order by"));
			resultOrderBy = sql.substring(sql.indexOf("order by"),sql.length());
		}else {
			resultSql = sql;
			resultOrderBy = sql.substring(0,sql.length());
		}

		result.put("resultSql",resultSql);
		result.put("resultOrderBy",resultOrderBy);
		return  result;
	}

	private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
		Builder builder = new Builder(ms.getConfiguration(), ms.getId() + "_PageHelper", newSqlSource,ms.getSqlCommandType());
		builder.resource(ms.getResource());
		builder.fetchSize(ms.getFetchSize());
		builder.statementType(ms.getStatementType());
		builder.keyGenerator(ms.getKeyGenerator());
		if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
			StringBuilder resultMaps = new StringBuilder();
			String[] var8;
			int var7 = (var8 = ms.getKeyProperties()).length;

			for (int var6 = 0; var6 < var7; ++var6) {
				String resultMap = var8[var6];
				resultMaps.append(resultMap).append(",");
			}

			resultMaps.delete(resultMaps.length() - 1, resultMaps.length());
			builder.keyProperty(resultMaps.toString());
		}

		builder.timeout(ms.getTimeout());
		builder.parameterMap(ms.getParameterMap());
		ArrayList var9 = new ArrayList();
		ResultMap var10 = (new ResultMap.Builder(ms.getConfiguration(), ms.getId(),Integer.TYPE, EMPTY_RESULTMAPPING)).build();
		var9.add(var10);
		builder.resultMaps(var9);
		builder.resultSetType(ms.getResultSetType());
		builder.cache(ms.getCache());
		builder.flushCacheRequired(ms.isFlushCacheRequired());
		builder.useCache(ms.isUseCache());
		return builder.build();
	}

	public Object plugin(Object target) {
		return target instanceof Executor ? Plugin.wrap(target, this) : target;
	}

	public void setProperties(Properties p) {
		dialect = p.getProperty("dialect");
		if (dialect != null && !"".equals(dialect)) {
			String offset = p.getProperty("offsetAsPageNum");
			if (offset != null && "TRUE".equalsIgnoreCase(offset)) {
				offsetAsPageNum = true;
			}

			String withcount = p.getProperty("rowBoundsWithCount");
			if (withcount != null && "TRUE".equalsIgnoreCase(withcount)) {
				rowBoundsWithCount = true;
			}

			String sizeZero = p.getProperty("pageSizeZero");
			if (sizeZero != null && "TRUE".equalsIgnoreCase(sizeZero)) {
				pageSizeZero = true;
			}

		} else {
			throw new BaseSystemException("Mybatis分页插件PageHelper无法获取dialect参数!");
		}
	}

	private class BoundSqlSqlSource implements SqlSource {
		BoundSql boundSql;

		public BoundSqlSqlSource(BoundSql boundSql) {
			this.boundSql = boundSql;
		}

		public BoundSql getBoundSql(Object parameterObject) {
			return this.boundSql;
		}
	}

	private static class NullObject {
		private NullObject() {
		}
	}
}
