package hiro.yoshioka.sql;

import hiro.yoshioka.classmanager.ClassManager;
import hiro.yoshioka.sdh.DatabaseType;
import hiro.yoshioka.sdh2.ResultSetDataHolder2;
import hiro.yoshioka.sql.engine.DominoTransactionRequest;
import hiro.yoshioka.sql.engine.GettingResourceRequest;
import hiro.yoshioka.sql.engine.MirroringRequest;
import hiro.yoshioka.sql.engine.Request;
import hiro.yoshioka.sql.engine.SQLOperationType;
import hiro.yoshioka.sql.engine.TransactionRequest;
import hiro.yoshioka.sql.notes.NotesRunnerAcl;
import hiro.yoshioka.sql.notes.NotesRunnerConnectCheck;
import hiro.yoshioka.sql.notes.NotesRunnerCount;
import hiro.yoshioka.sql.notes.NotesRunnerMeta;
import hiro.yoshioka.sql.notes.NotesRunnerSelection;
import hiro.yoshioka.sql.notes.ddl.ACL;
import hiro.yoshioka.sql.params.ConnectionProperties;
import hiro.yoshioka.sql.params.DBUserPass;
import hiro.yoshioka.sql.resource.DBCrossRefference;
import hiro.yoshioka.sql.resource.DBRoot;
import hiro.yoshioka.sql.resource.IDBSchema;
import hiro.yoshioka.sql.resource.IDBSequence;
import hiro.yoshioka.sql.resource.IDBTable;
import hiro.yoshioka.util.StringUtil;

import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class DominoSQL extends AbsNoSQL {
	public static final String DOMINO_SQL_EXCEPTION_STAUS = "DOMINO_SQL_EXCEPTION_STAUS";
	boolean needsAuth;
	String host;
	Map<String, DBUserPass> dbPassWordMap = new LinkedHashMap<String, DBUserPass>();

	private static final String ADMIN = "admin";

	public DominoSQL(ClassManager classManager) {
		super(classManager);
	}

	public DatabaseType getDatabaseType() {
		return DatabaseType.DOMINO;
	}

	protected DBRoot getMetaData(GettingResourceRequest request)
			throws InterruptedException, ExecutionException {
		this.capturing = true;

		if (request.canceld()) {
			return null;
		}
		ExecutorService ex = Executors.newSingleThreadExecutor();
		try {
			Future<DBRoot> future = null;
			switch (request.targetType) {
			case ONLY_TABLE:
				IDBTable table = (IDBTable) request.selectionResource;
				future = ex.submit(new NotesRunnerMeta(classManager, request,
						table));
				break;
			case ONLY_SCHEMA:
				IDBSchema schema = (IDBSchema) request.selectionResource;
				future = ex.submit(new NotesRunnerMeta(classManager, request,
						schema));
				break;
			default:
				future = ex.submit(new NotesRunnerMeta(classManager, request));
			}
			DBRoot root = future.get();
			setRoot(root);
			return root;
		} finally {
			ex.shutdown();
			capturing = false;
		}
	}

	public void addAuthenticate(String dbName, String user, String pass) {
		addAuthenticate(new DBUserPass(dbName, user, pass));
	}

	public void addAuthenticate(DBUserPass dup) {
		if (dup == null) {
			fLogger.warn("Nothing authenticate informain...");
		} else {
			dbPassWordMap.put(dup.db, dup);
			// NotesRunner db = notesRunnerMap.get(dup.db);
		}
	}

	private boolean hasAdminAuthenticate() {
		System.out.println(dbPassWordMap);
		return dbPassWordMap.containsKey(ADMIN);
	}

	public void addUser(String dbname, String username, String passwd) {
		// mongo.getDB(dbname).addUser(username, passwd.toCharArray());
	}

	public void init() throws SQLException {
		this.init(null);
	}

	public void init(String host) throws SQLException {
		fLogger.info("host:" + host);
		System.out.println("========================");
	}

	@Override
	public boolean canDoOperation(SQLOperationType operation) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean doOperation(SQLOperationType operation, Request request)
			throws SQLException {

		TransactionRequest treq = null;
		setMakeBlobData(request.makeBlob);
		if (request instanceof TransactionRequest) {
			treq = (TransactionRequest) request;
			setMaxRowNum(treq.getMaxRownum());
		}
		DominoTransactionRequest dreq = null;
		if (request instanceof DominoTransactionRequest) {
			dreq = (DominoTransactionRequest) request;
			System.out.println("domino accept DominoTransactionRequest ["
					+ operation + "] dreq[" + dreq + "] ");
		}
		ExecutorService ex = null;
		boolean retCode = true;
		System.out.println("domino accept request [" + operation + "] req["
				+ request + "] ");
		if (SQLOperationType.CONNECT == operation) {
			ConnectionProperties prop = request.getConnectionProperties();
			return connect(prop);
		} else if (SQLOperationType.CLOSE == operation) {
			ConnectionProperties prop = request.getConnectionProperties();
			return close();
		}
		ex = Executors.newSingleThreadExecutor();
		long time = System.currentTimeMillis();
		try {
			switch (operation) {
			case RESOURCE_MIRRORING:
				MirroringRequest mirroring_request = (MirroringRequest) request;
				retCode = createMirroredTableTo(mirroring_request);
				break;
			case COUNT:
				int retCount = 0;
				try {
					notifyExecute(SQLExecutionStatus.BEFORE_EXECUTE);
					Future<Integer> futurec = ex.submit(new NotesRunnerCount(
							classManager, request.getConnectionProperties(),
							treq.getIDBTable()));
					retCount = futurec.get();
					treq.setResultCount(retCount);
				} finally {
					ex.shutdown();
					time = System.currentTimeMillis() - time;
					notifyExecute(SQLExecutionStatus.AFTER_EXECUTE,
							String.valueOf(treq.getReturnedRowCount()),
							String.valueOf(time));
				}
				break;
			case RESOURCE_CAPTION:
				getMetaData((GettingResourceRequest) request);
				break;
			case EXPLAIN_PLAN:
				break;
			case SELECT_SESSION:
				break;
			case SELECT_LOCK:
				break;
			case UNID_EXECUTE_QUERY:
				notifyExecute(SQLExecutionStatus.BEFORE_EXECUTE);
				Set<String> s = new LinkedHashSet<String>();
				for (String unid : dreq.getSQLStatements()) {
					if (unid.trim().length() > 0) {
						s.add(unid);
					}
				}
				Future<ResultSetDataHolder2> future = ex
						.submit(new NotesRunnerSelection(classManager, dreq,
								request.getConnectionProperties(), dreq
										.getIDBTable(), s, isMakeBlobData(),
								false));
				try {
					dreq.setRDH(future.get());
				} finally {
					ex.shutdown();
					time = System.currentTimeMillis() - time;
					notifyExecute(SQLExecutionStatus.AFTER_EXECUTE,
							String.valueOf(dreq.getReturnedRowCount()),
							String.valueOf(time));
				}
				break;

			case SELECT_ALL:
				notifyExecute(SQLExecutionStatus.BEFORE_EXECUTE);
				future = ex.submit(new NotesRunnerSelection(classManager, dreq,
						request.getConnectionProperties(), dreq.getIDBTable(),
						dreq.getDominoSearchQuery(), dreq.getMaxRownum(), this
								.isMakeBlobData(), false));
				try {
					dreq.setRDH(future.get());
				} finally {
					ex.shutdown();
					time = System.currentTimeMillis() - time;
					notifyExecute(SQLExecutionStatus.AFTER_EXECUTE,
							String.valueOf(treq.getReturnedRowCount()),
							String.valueOf(time));
				}
				break;
			case CREATE_TRIG_FNC_PROC:
				break;
			case WORST_SQL:
				break;
			case CHECK_VALIDATION:
				break;
			case SHOW_ACL:
				notifyExecute(SQLExecutionStatus.BEFORE_EXECUTE);
				try {
					Future<ACL> futureAcl = ex.submit(new NotesRunnerAcl(
							classManager, dreq.getConnectionProperties(), dreq
									.getSchema()));

					dreq.setAcl(futureAcl.get());
				} finally {
					ex.shutdown();
					time = System.currentTimeMillis() - time;
					notifyExecute(SQLExecutionStatus.AFTER_EXECUTE, "0",
							String.valueOf(time));
				}
				break;
			case PREPARED_EXECUTE_QUERY:
				notifyExecute(SQLExecutionStatus.BEFORE_EXECUTE);
				try {
					future = ex.submit(new NotesRunnerSelection(classManager,
							dreq, dreq.getConnectionProperties(), dreq
									.getIDBTable(),
							dreq.getDominoSearchQuery(), dreq.getMaxRownum(),
							this.isMakeBlobData(), false));

					dreq.setRDH(future.get());
				} finally {
					ex.shutdown();
					time = System.currentTimeMillis() - time;
					notifyExecute(SQLExecutionStatus.AFTER_EXECUTE,
							String.valueOf(treq.getReturnedRowCount()),
							String.valueOf(time));
				}
				break;

			case PREPARED_EXECUTE:
				break;
			default:
				System.out.println("what's this operation ? " + operation);
				break;
			}
		} catch (Exception e) {
			notifyExecute(SQLExecutionStatus.EXCEPTION, e.getMessage());
			throw cnvSQLException(e);
		}

		return retCode;

	}

	@Override
	public ResultSetDataHolder2 getAllData2(IDBTable table, Request request)
			throws SQLException {
		ExecutorService ex = Executors.newSingleThreadExecutor();
		int maxRow = Integer.MAX_VALUE;
		if (request != null && request instanceof MirroringRequest) {
			MirroringRequest mreq = (MirroringRequest) request;
			maxRow = mreq.maxRowNum;
		}

		notifyExecute(SQLExecutionStatus.BEFORE_EXECUTE);
		Future<ResultSetDataHolder2> future = ex
				.submit(new NotesRunnerSelection(classManager, request, request
						.getConnectionProperties(), table,
						StringUtil.EMPTY_STRING, maxRow, this.isMakeBlobData(),
						false));
		long time = System.currentTimeMillis();
		long count = 0L;
		try {
			ResultSetDataHolder2 rdh = future.get();
			count = rdh.getRowCount();
			if (rdh != null) {
				rdh.setTableName(table.getComment());
				rdh.setTableNameE(table.getName());
			}
			return rdh;
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		} finally {
			ex.shutdown();
			time = System.currentTimeMillis() - time;
			notifyExecute(SQLExecutionStatus.AFTER_EXECUTE,
					String.valueOf(count), String.valueOf(time));
		}
		return null;
	}

	static SQLException cnvSQLException(Throwable e) {
		if (e instanceof ExecutionException) {
			Throwable c = e.getCause();
			if (c instanceof InvocationTargetException) {
				InvocationTargetException iv = (InvocationTargetException) c;
				e = iv.getTargetException();
			}
		}
		SQLException se = new SQLException("DominoSQLException["
				+ e.getLocalizedMessage() + "]", DOMINO_SQL_EXCEPTION_STAUS, e);
		return se;
	}

	public boolean connect(ConnectionProperties properties) throws SQLException {
		this._info = properties;
		addAuthenticate(properties.getAdminAuthenticate());
		addAuthenticate(properties.getAuthenticate());
		init(properties.getHost());
		ExecutorService ex = Executors.newSingleThreadExecutor();
		Future<Boolean> futurec = ex.submit(new NotesRunnerConnectCheck(
				classManager, properties));
		try {
			futurec.get();
		} catch (Exception e) {
			throw cnvSQLException(e);
		} finally {
			ex.shutdown();
		}
		if (fConnectionListenerList != null) {
			for (SqlBasicListener listener : fConnectionListenerList) {
				listener.connected();
			}
		}
		properties.setConnected(true);
		return true;
	}

	@Override
	public boolean close() throws SQLException {
		if (fConnectionListenerList != null) {
			for (SqlBasicListener listener : fConnectionListenerList) {
				listener.disconnected();
			}
		}
		this._info.setConnected(false);
		return false;
	}

	// ----------------------------------------------------------------
	// [5] MIRRORING
	// ----------------------------------------------------------------
	@Override
	public boolean supportResultSet() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean createMirroredTableTo_FaseOnSchema(
			MirroringRequest mirroring_request) throws SQLException {
		fLogger.info("start.");

		Collection<String> schemaCollection = mirroring_request
				.getMappingToSchemaNames();
		mirroring_request.beginTask("Schema Migraion Start",
				schemaCollection.size());
		int cnt = 0;
		boolean retCode = true;

		for (String schemaName : schemaCollection) {
			try {
				int dropCnt = 0;
				int createCnt = 0;
				cnt++;
				String text = String.format("DB Migtaion NOW %s.%s [%d/%d]",
						mirroring_request.getConnectionProperties()
								.getDisplayString(), schemaName, cnt,
						schemaCollection.size());
				mirroring_request.subTask(text);
				if (mirroring_request.isCanceled()) {
					return false;
				}
				mirroring_request.worked(1);
				boolean existsSchema = existsSchema(schemaName);
				mirroring_request.subTask(String.format(
						"Exists Schema Check... exists[%b]", existsSchema));
				if (existsSchema && mirroring_request.dropSchema) {
					mirroring_request.subTask(String.format("Drop Schema [%s]",
							schemaName));
					dropSchema(schemaName, mirroring_request.cascade);
					fLogger.info(String.format("Schema droped"));
					dropCnt++;
				}
				if (!existsSchema || mirroring_request.dropSchema) {
					mirroring_request.subTask(String.format(
							"CREATE Schema [%s]", schemaName));
					createSchema(schemaName);
					fLogger.info(String.format("schema created"));
					createCnt++;
				}

				mirroring_request.addResultRecord(new String[] { "SCHEMA", "-",
						schemaName, StringUtil.EMPTY_STRING, "○",
						String.format("C:%d D:%d", createCnt, dropCnt), "-" });
			} catch (SQLException e) {
				retCode = false;
				mirroring_request.addResultRecord(new String[] { "SCHEMA", "-",
						schemaName, StringUtil.EMPTY_STRING, "×", "-",
						e.getMessage() });
				if (!mirroring_request.continuing) {
					throw e;
				}
			} finally {
				mirroring_request.worked(1);
			}
		}

		System.out.println(mirroring_request.getRdh());
		fLogger.info("end. retCode=" + retCode);
		return retCode;
	}

	@Override
	public boolean createMirroredTableTo_FaseOnTable(
			MirroringRequest mirroring_request) throws SQLException {
		fLogger.fatal("domino database is read only...");
		return false;
	}

	protected boolean createSchema(String schemaName) throws SQLException {
		fLogger.fatal("domino database is read only...");
		return false;
	}

	protected boolean dropSchema(String schemaName, boolean cascade)
			throws SQLException {
		fLogger.fatal("domino database is read only...");
		return false;
	}

	@Override
	public String getDefaultSchemaName() {
		fLogger.fatal("domino database has nothing default schema...");
		return StringUtil.EMPTY_STRING;
	}

	public boolean migration(ITransactionSQL osql, DBCrossRefference original,
			boolean drop, boolean cascade) throws SQLException {
		fLogger.fatal("domino database is read only...");
		return false;
	}

	public boolean existsTable(String schemaName, String tableName)
			throws SQLException {
		System.err.println("do override!!!!!!!!!!!");
		return false;
	}

	public boolean migration(ITransactionSQL osql, IDBSequence original,
			boolean drop, boolean cascade, boolean noSchema)
			throws SQLException {
		fLogger.fatal("domino database is read only...");
		return false;
	}

	@Override
	public boolean existsSchema(String name) throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void setTableColumns(String schema, IDBTable table)
			throws SQLException {
		NotesRunnerMeta meta = new NotesRunnerMeta(this.classManager,
				this._info, table);

		ExecutorService ex = Executors.newSingleThreadExecutor();
		Future<DBRoot> future = ex.submit(meta);
		try {
			future.get();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
