package gmt.db;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;


import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.json.simple.JSONObject;

import gmt.common.type.LogLevelType;
import gmt.common.util.GmtUtils;
import gmt.db.event.IGmtDBResultListener;
import gmt.db.vo.GmtDbInfoVO;
import gmt.db.vo.GmtDbResultVO;
import gmt.logger.GmtLogManager;


public class GmtDbManager {

	private GmtLogManager logger;
	private static int core;
	private static ExecutorService executorService;
	private HashMap<String, IGmtDBResultListener> requestMap;
	private GmtDb db;
	private static String path;

	private GmtDbManager() {
		logger = GmtLogManager.getInstance();
		db = new GmtDb();
		core = Runtime.getRuntime().availableProcessors(); 
		executorService = Executors.newFixedThreadPool(core);

		requestMap = new HashMap<String, IGmtDBResultListener>();
	}

	private static class Holder {
		public static final GmtDbManager INSTANCE = new GmtDbManager();
	}

	public static GmtDbManager getInstance() {
		return Holder.INSTANCE;
	}

	public void start(String path) {
		db.initializeDbFromXML(path);
	}

	public int ping(String dbId, String sql) {
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(dbId);
		SqlSession session = sqlSessionFactory.openSession();
		ResultSet rs = null;
		Statement stmt = null;
		int result = 0;
		try {
			stmt = session.getConnection().createStatement();
			rs = stmt.executeQuery(sql);
			if (rs.next()) {
				result = rs.getInt(1);
			}
		} catch (SQLException e) {
			logger.writeLevelLog("SQL : " + sql + "\r\n" + GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		} finally {
			
			if (rs != null) try { rs.close(); } catch(SQLException e) 
			{logger.writeLevelLog("SQL : " + sql + "\r\n" + GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");}
			if (stmt != null) try { stmt.close(); } catch(SQLException e) 
			{logger.writeLevelLog("SQL : " + sql + "\r\n" + GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");}
			if (session != null) { session.close(); } 
		}
		return result;
	}

	public boolean select(String dbId, String sql, String requestID, IGmtDBResultListener rl) {

		try {

			if (rl != null) {
				requestMap.put(requestID, rl);
			}

			Callable<List<Map<String, Object>>> task = new Callable<List<Map<String, Object>>>() {

				@Override
				public List<Map<String, Object>> call() throws Exception {
					List<Map<String, Object>> data = new ArrayList<Map<String, Object>>();
					Map<String, Object> map;
					SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(dbId);
					SqlSession session = sqlSessionFactory.openSession();
					ResultSet rs = null;
					Statement stmt = null;
					try {
						stmt = session.getConnection().createStatement();
						rs = stmt.executeQuery(sql);
						ResultSetMetaData metaData = rs.getMetaData();
						int count = metaData.getColumnCount();

						String column;
						while (rs.next()) {
							map = new HashMap<String, Object>();
							for (int i = 1; i <= count; i++) {
								column = metaData.getColumnName(i);
								map.put(column, rs.getString(column));
							}
							data.add(map);
						}
					} catch (SQLException e){
						logger.writeLevelLog("SQL : " + sql + "\r\n" + GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
					} finally {
						if (rs != null) { rs.close(); } 
						if (stmt != null) { stmt.close(); }
						if (session != null) { session.close(); }
					}
					return data;
				}
			};

			Future<List<Map<String, Object>>> future = executorService.submit(task);
			getSelectResult(future, requestID);

		} catch (Exception e) {
			return false;
		}
		return true;
	}

	public Boolean execSQL(String dbId, String sql, String requestID, IGmtDBResultListener rl) {
		try {

			if (rl != null) {
				requestMap.put(requestID, rl);
			}

			Callable<Integer> task = new Callable<Integer>() {

				@Override
				public Integer call() {
					int rowsAffected = 0;
					Statement stmt = null;
					SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(dbId);
					SqlSession session = sqlSessionFactory.openSession();
					try {
						stmt = session.getConnection().createStatement();
						rowsAffected = stmt.executeUpdate(sql);
						session.commit();
						stmt.close();
					} catch (SQLException e) {
						e.printStackTrace();
					} finally {
						if(stmt != null) try { stmt.close();} catch(SQLException e) {
							logger.writeLevelLog("SQL : " + sql + "\r\n" + GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
						}
						if(session != null) { session.close(); }
					}
					return rowsAffected;
				}
			};

			Future<Integer> future = executorService.submit(task);
			getExecSQLResult(future, requestID);

		} catch (Exception e2) {
			return false;
		}
		return true;
	}

	public Boolean execSQL(String dbId, ArrayList<String> sqlList, String requestID, IGmtDBResultListener rl) {
		try {
			if (rl != null) {
				requestMap.put(requestID, rl);
			}

			Callable<Integer> task = new Callable<Integer>() {

				@Override
				public Integer call() {
					int rowsAffected = 0;
					int rowsAffecteds = 0;
					String query = "";
					Statement stmt = null;
					SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(dbId);
					SqlSession session = sqlSessionFactory.openSession();
					Connection conn = session.getConnection();
					try {
						conn.setAutoCommit(false);
						stmt = conn.createStatement();
						for (String sql : sqlList) {
							query = sql;
							rowsAffected = stmt.executeUpdate(sql);
							rowsAffecteds = rowsAffecteds + rowsAffected;
						}
						stmt.close();
						conn.commit();
					} catch (SQLException e) {
						logger.writeLevelLog("SQL : " + query + "\r\n" + GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
						try {
							conn.rollback();
						} catch (SQLException e1) {
							logger.writeLevelLog("SQL : " + query + "\r\n" + GmtUtils.getStatckTrace(e1), LogLevelType.LOG_ERROR, "AllLog");
						}
					} finally {
						if(stmt != null) try { stmt.close(); } catch(SQLException e2) {logger.writeLevelLog("SQL : " + query + "\r\n" + GmtUtils.getStatckTrace(e2), LogLevelType.LOG_ERROR, "AllLog"
								);}
						if(conn != null) try {conn.close();} catch(SQLException e3) {logger.writeLevelLog("SQL : " + query + "\r\n" + GmtUtils.getStatckTrace(e3), LogLevelType.LOG_ERROR, "AllLog"
								);}
						if(session != null) {session.close();}
					}
					return rowsAffected;
				}
			};

			Future<Integer> future = executorService.submit(task);
			getExecSQLResult(future, requestID);

		} catch (Exception e) {
			return false;
		}

		return true;
	}

	public Boolean execSQL(String dbId, ArrayList<String> sqlList, int batchsize, String requestID,
			IGmtDBResultListener rl) {
		try {
			if (rl != null) {
				requestMap.put(requestID, rl);
			}

			Callable<Integer> task = new Callable<Integer>() {

				@Override
				public Integer call() {
					int tmp = 0;
					String query = "";
					Statement stmt = null;
					SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(dbId);
					SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
					Connection conn = session.getConnection();
					try {
						stmt = conn.createStatement();
						int count = 0;

						for (String sql : sqlList) {
							query = sql;
							stmt.addBatch(sql);
							if (++count % batchsize == 0) {
								int[] rowsAffected = stmt.executeBatch();
								tmp = tmp + getBatchCnt(rowsAffected);
								conn.commit();
							}
						}
						int[] rowsAffected = stmt.executeBatch();
						tmp = tmp + getBatchCnt(rowsAffected);
						conn.commit();
						
					} catch (SQLException e) {
						logger.writeLevelLog("SQL : " + query + "\r\n" + GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
					} finally {
						if(stmt != null) try { stmt.close(); } catch(SQLException e2) {logger.writeLevelLog("SQL : " + query + "\r\n" + GmtUtils.getStatckTrace(e2), LogLevelType.LOG_ERROR, "AllLog"
								);}
						if(conn != null) try {conn.close();} catch(SQLException e3) {logger.writeLevelLog("SQL : " + query + "\r\n" + GmtUtils.getStatckTrace(e3), LogLevelType.LOG_ERROR, "AllLog"
								);}
						if(session != null) {session.close();}
					}
					return tmp;
				}
			};

			Future<Integer> future = executorService.submit(task);
			getExecSQLResult(future, requestID);

		} catch (Exception e) {
			logger.writeLevelLog("[GMTDBManager] "+ GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
		return true;
	}

	private int getBatchCnt(int[] rowsAffected) {
		int tmpCnt = 0;
		for (int i = 0; i < rowsAffected.length; i++) {
			if (rowsAffected[i] == Statement.SUCCESS_NO_INFO) {
				tmpCnt = tmpCnt + 1;
			} else if (rowsAffected[i] == Statement.EXECUTE_FAILED) {
				tmpCnt = tmpCnt + 0;
			} else {
				tmpCnt = tmpCnt + rowsAffected[i];
			}
		}
		return tmpCnt;
	}

	private void getSelectResult(Future<List<Map<String, Object>>> future, String requestID) {
		executorService.submit(() -> {
			GmtDbResultVO result = new GmtDbResultVO();
			try {
				result.setRequestID(requestID);
				result.setDataSet(future.get());
				result.setSuccess(true);
				result.setErrorCode("Success");

				System.out.println("Task " + requestID + " is done.....");
			} catch (InterruptedException e) {
				result.setSuccess(false);
				result.setErrorCode(e.getMessage());
				e.printStackTrace();
			} catch (ExecutionException e) {
				result.setSuccess(false);
				result.setErrorCode(e.getMessage());
				e.printStackTrace();
			}

			IGmtDBResultListener requestListener = requestMap.get(requestID);
			if (requestListener != null) {
				requestListener.getDBResult(result);
			}
			requestMap.remove(requestID);
		});
	}

	private void getExecSQLResult(Future<Integer> future, String requestID) {

		executorService.submit(() -> {
			GmtDbResultVO result = new GmtDbResultVO();
			try {
				result.setRequestID(requestID);
				result.setRowsAffected(future.get());
				result.setSuccess(true);
				result.setErrorCode("Success");
			} catch (InterruptedException e) {
				result.setSuccess(false);
				result.setErrorCode(e.getMessage());
				e.printStackTrace();
			} catch (ExecutionException e) {
				result.setSuccess(false);
				result.setErrorCode(e.getMessage());
				e.printStackTrace();
			}

			IGmtDBResultListener requestListener = requestMap.get(requestID);
			if (requestListener != null) {
				requestListener.getDBResult(result);
			}
			requestMap.remove(requestID);
		});
	}

	public void finish() {
		executorService.shutdown();
	}

	private SqlSessionFactory getSqlSessionFactory(String dbID) {

		return db.getSqlSessionFactoryMap().get(dbID);
	}

	public SqlSessionFactory getSqlSessionFactory(String dbId, Class<?> mapper) {
		return db.getSqlSessionFactory(db.getDbMap().get(dbId), mapper);
	}

	public JSONObject getDbJson() {
		return db.getDbJson();
	}

	public synchronized Map<String, GmtDbInfoVO> getDbMap() {
		return db.getDbMap();
	}
	
	public void checkDBStatus() {
		Map<String, GmtDbInfoVO> dbMap = getDbMap();
		String sql = "";
		String dbId = "";
		Object[] keys = dbMap.keySet().toArray();
		for (int i = 0; i < keys.length; i++) {
			int dbType = Integer.parseInt(dbMap.get(keys[i]).getDBType());
			dbId = dbMap.get(keys[i]).getID();
			switch (dbType) {
			case 0:

				break;

			case 1:
				sql = "select 1 from dual";
				break;

			case 2:
				sql = "select 1";
				break;

			case 3:

				break;

			case 4:
				sql = "select 1 from db_root";
				break;

			case 5:
				sql = "select 1 from dual";
				break;

			case 6:
				sql = "select 1";
				break;

			case 7:
				sql = "select 1";
				break;
			}
			
			int result = ping(dbId, sql);
			
			if(result == 1) {
				dbMap.get(dbId).setStatus(true);
			} else {
				dbMap.get(dbId).setStatus(false);
			}
		}
	}
}
