package hiro.yoshioka.ast.sql.util;

import hiro.yoshioka.ast.sql.AbsSQLParser;
import hiro.yoshioka.ast.sql.AbsSimpleNode;
import hiro.yoshioka.ast.sql.IBasicDL;
import hiro.yoshioka.ast.sql.IDDL;
import hiro.yoshioka.ast.sql.IDML;
import hiro.yoshioka.ast.sql.IExplainPlan;
import hiro.yoshioka.ast.sql.ISubQuery;
import hiro.yoshioka.ast.sql.RowColumn;
import hiro.yoshioka.ast.sql.oracle.util.WolfParserUtil;
import hiro.yoshioka.sql.resource.IDBTable;
import hiro.yoshioka.util.StringUtil;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public abstract class ParserUtil implements IParserUtil {
	static Pattern NO_CR_PATTERN = Pattern
			.compile(
					"\\s*CREATE\\s+(OR\\s+REPLACE\\s+)?(FUNCTION|PROCEDURE|TRIGGER|PACKAGE).*",
					Pattern.CASE_INSENSITIVE);

	protected Log fLogger = LogFactory.getLog(this.getClass());

	protected String fSQL_Statement;

	protected AbsSQLParser fParser;

	protected RowColumn fRowColumn;

	protected BackWord fBackWord;

	protected boolean flg;

	protected boolean doneParse;

	public static String[] TOKEN_IMAGES = StringUtil.EMPTY_STRING_ARRAY;

	public static void main(String[] args) {
		String lines = "-- hogepiyo\r\n select * from dual                                 ";

		WolfParserUtil util = new WolfParserUtil(lines);
		System.out.println(util.startsWithSelect());
	}

	static Pattern P_AFTER = Pattern.compile("(SELECT|AND|OR)([ ,\n]+)",
			Pattern.CASE_INSENSITIVE);
	static Pattern P_BEFORE = Pattern.compile(
			"\\s+(((LEFT|INNER|RIGHT)\\s+)?(JOIN)\\s+)",
			Pattern.CASE_INSENSITIVE);
	static Pattern P_BOTH = Pattern.compile(
			"(FROM|UPDATE|INSERT|WHERE|ORDER BY|GROUP BY)([ \n]+)",
			Pattern.CASE_INSENSITIVE);

	public static String breakKeyWord(String statement) {
		statement = statement.replaceAll("\r\n", "\n");
		statement = statement.replaceAll("\r", "\n");

		Matcher m = P_AFTER.matcher(statement);
		StringBuffer buf = new StringBuffer();
		while (m.find()) {
			m.appendReplacement(buf, "$1$2\n  ");
		}
		m.appendTail(buf);
		statement = buf.toString().replaceAll(" +\n", "\n");
		m = P_BEFORE.matcher(statement);
		buf = new StringBuffer();
		while (m.find()) {
			m.appendReplacement(buf, "\n$1");
		}
		m.appendTail(buf);
		statement = buf.toString();
		m = P_BOTH.matcher(statement);
		buf = new StringBuffer();
		while (m.find()) {
			m.appendReplacement(buf, "\n$1\n  ");
		}
		m.appendTail(buf);
		statement = buf.toString();

		statement = statement
				.replaceAll(
						"\\s+(((LEFT|INNER|RIGHT|left|inner|right)\\s+)?(JOIN|join)\\s+)",
						StringUtil.LINE_SEPARATOR + "  $1");
		statement = statement.replaceAll("[\n]+", StringUtil.LINE_SEPARATOR);

		return statement;
	}

	@Override
	public AbsSimpleNode getRoot() {
		if (fParser != null && fParser.nodeCreated()) {
			return fParser.getRoot();
		}
		return null;
	}

	public BackWord getBackWord() {
		return fBackWord;
	}

	public abstract AbsSimpleNode[] getFoldingNodes();

	public abstract BindInfo[] binds();

	protected void setTokenImages(String[] tokenImage) {
		List<String> list = new ArrayList<String>();
		for (int i = 0; i < tokenImage.length; i++) {
			String image = tokenImage[i];
			image = image.replaceAll(StringUtil.DOUBLE_QUOTE_STRING,
					StringUtil.EMPTY_STRING);
			image = image.replaceAll("\\p{Cntrl}", StringUtil.EMPTY_STRING);
			image = image.replaceAll("<.*>", "");
			if (image.length() > 0) {
				list.add(image);
			}
		}
		TOKEN_IMAGES = list.toArray(new String[list.size()]);
	}

	public ParserUtil(File file) throws FileNotFoundException {
		this(new java.io.FileInputStream(file));
		BufferedReader in = new BufferedReader(new FileReader(file));

		try {
			StringBuffer buf = new StringBuffer();
			String line;
			for (; (line = in.readLine()) != null;) {
				buf.append(line).append(StringUtil.LINE_SEPARATOR);
			}
			this.fSQL_Statement = buf.toString();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
		}
	}

	public boolean updatableStatement() {
		if (!canDoBatchExecute() && doQuery()) {
			IDML obj = getDML().getDMLObject();
			if (fLogger.isDebugEnabled()) {
				fLogger.debug(obj.getClass().toString());
			}
			if (obj instanceof ISubQuery) {
				return ((ISubQuery) obj).updatableStatement();
			}
		}
		return false;
	}

	public void dumpNodes() {
		AbsSimpleNode node = fParser.getRoot();
		node.dump2(" ");

	}

	public void dumpSQLStatement() {
		StringUtil.dumpWhiteSpace(fSQL_Statement);
	}

	public ParserUtil(String string) {
		fSQL_Statement = string;
		fParser = createParser(new StringReader(string));
		disable_tracing();
	}

	public abstract AbsSQLParser createParser(StringReader reader);

	/**
	 * @param stream
	 */
	public ParserUtil(FileInputStream stream) {
		fParser = createParser(stream);
		disable_tracing();
	}

	public abstract AbsSQLParser createParser(FileInputStream stream);

	public String getSQLStatement() {
		if (fSQL_Statement.trim().endsWith(";")) {
			return fSQL_Statement.substring(0, fSQL_Statement.lastIndexOf(';'));
		}
		return fSQL_Statement;
	}

	public void enable_tracing() {
		if (fParser != null) {
			fParser.enable_tracing();
		}
	}

	public void disable_tracing() {
		if (fParser != null) {
			fParser.disable_tracing();
		}
	}

	public void dump() {
		if (fParser != null) {
			fParser.getRoot().dump(">>");
		}
	}

	public boolean wasParseSuccess() {
		return flg;
	}

	public boolean parse() {
		if (!doneParse) {
			doneParse = true;
			try {
				flg = fParser.parse(null);
				flg = flg && fParser.nodeCreated();
			} catch (Exception e) {
				flg = false;
			} catch (Error e) {
				flg = false;
			}

		}
		fLogger.info("--- Parsing result[" + flg + "] ---");

		return flg;
	}

	@Override
	public void setRowColumn(RowColumn rowColumn) {
		fRowColumn = rowColumn;
		fBackWord = new BackWord(fSQL_Statement, fRowColumn);
	}

	@Override
	public BackWord getBackWordAt(RowColumn rowColumn) {
		return new BackWord(fSQL_Statement, rowColumn);
	}

	IBasicDL getASTBasicDL() {
		if (fParser == null || !fParser.nodeCreated()) {
			return null;
		}
		AbsSimpleNode node = (AbsSimpleNode) fParser.getRoot();
		AbsSimpleNode n;
		n = node.getChild(0);
		if (n instanceof IExplainPlan) {
			n = node.getChild(1);
		}
		if (n instanceof IDML) {
			return ((IDML) n).getDMLObject();
		} else if (n instanceof IDDL) {
			n = n.getChild(0);
			if (fLogger.isDebugEnabled()) {
				fLogger.debug("n class " + n.getClass());
			}
			return (IDDL) n;
		} else {
			n = n.getChild(0);
			if (fLogger.isDebugEnabled()) {
				fLogger.debug("n class " + n.getClass());
			}
			return (IBasicDL) n;
		}
	}

	public IBasicDL[] getBasicDLs() {
		if (!doneParse) {
			parse();
		}
		ArrayList<IBasicDL> retList = new ArrayList<IBasicDL>();
		AbsSimpleNode node = (AbsSimpleNode) fParser.getRoot();
		AbsSimpleNode[] n1 = node.getChildren();
		for (int i = 0; i < n1.length; i++) {
			if (n1[i] instanceof IBasicDL) {
				retList.add((IBasicDL) n1[i]);
			}
			AbsSimpleNode[] n2 = n1[i].getChildren();
			for (int j = 0; j < n2.length; j++) {
				if (n2[j] instanceof IBasicDL) {
					retList.add((IBasicDL) n2[j]);
				}
			}
		}

		return retList.toArray(new IBasicDL[retList.size()]);
	}

	public IDML getDML() {
		if (!doneParse) {
			parse();
		}
		IBasicDL n = getASTBasicDL();
		if (n != null && n.isDML()) {
			return (IDML) n;
		}
		return null;
	}

	public IDBTable getFirstTable() {
		IDML obj = getDML();
		if (obj != null) {
			ASTEditorTableListHolder lis = obj.getEditorTableListHolder();
			if (lis.size() > 0) {
				return lis.getFirstTable();
			}
		}
		return null;
	}

	protected boolean matcheClassPattern(List target, String pattern) {
		if (target == null || target.size() == 0) {
			return false;
		}
		StringBuffer buf = new StringBuffer(getClassName(target.get(0)));
		for (int i = 1; i < target.size(); i++) {
			buf.append(" ").append(getClassName(target.get(i)));
		}
		fLogger.info(buf.toString());
		return buf.toString().matches(pattern);
	}

	public String getClassName(Object o) {
		String str = o.getClass().getName();
		return str.substring(str.lastIndexOf('.') + 1);
	}

	/**
	 * @return
	 */
	public Iterator getExpectedTokens() {
		return fParser.getExpectedTokens();
	}

	/**
	 * @return
	 */
	public boolean doQuery() {
		try {
			if (isDML()) {
				return getDML().isSelectStatement();
			}
			if (doneParse && !flg) {
				if (fLogger.isInfoEnabled()) {
					fLogger.info("Parse失敗のため簡易解析します TRIM後の出だしがSELECTか？");
				}
				return startsWithSelect();
			}
			return false;
		} catch (Exception e) {
			fLogger.error("Parse失敗のため簡易解析します TRIM後の出だしがSELECTか？");
			return startsWithSelect();
		}
	}

	public boolean beginTransaction() {
		IDML dml = getDML();
		if (dml != null && dml.beginTransaction()) {
			if (fLogger.isInfoEnabled()) {
				fLogger.info("トランザクション開始");
			}
			return true;
		}
		return false;
	}

	/**
	 * @return
	 */
	public boolean startsWithSelect() {
		String sel = StringUtil.cnvWithoutComment(fSQL_Statement).toUpperCase()
				.trim();
		if (sel.startsWith("SELECT") || sel.startsWith("SHOW")) {
			return true;
		}
		return false;
	}

	/**
	 * @return
	 */
	public boolean startsWithTrigFncProc() {
		Matcher m = NO_CR_PATTERN.matcher(fSQL_Statement);
		if (m.find()) {
			return true;
		}
		return false;
	}

	/**
	 * @return
	 */
	public boolean canDoBatchExecute() {
		if (!doneParse) {
			parse();
		}
		if (!flg) {
			return false;
		}
		try {
			IBasicDL[] bases = getBasicDLs();
			for (int i = 0; i < bases.length; i++) {
				if (bases[i] instanceof IDML) {
					if (((IDML) bases[i]).isSelectStatement()) {
						return false;
					}
				}
			}
			return bases.length > 1;
		} catch (Exception e) {
		}
		return false;
	}

	public boolean isDML() {
		if (getDML() != null) {
			return true;
		}
		return false;
	}

	public String[] getSQLStatements() {
		String[] ret = getSQLStatement().split(";");
		List<String> list = new ArrayList<String>();
		for (int i = 0; i < ret.length; i++) {
			if (ret[i].trim().length() > 0) {
				list.add(ret[i]);
			}
		}
		return list.toArray(new String[list.size()]);
	}

	public String getDMode() {
		IBasicDL n = getASTBasicDL();
		if (n.isDDL()) {
			return "DDL";
		} else if (n.isDML()) {
			return "DML";
		}
		return "DOL";
	}

	/**
	 * @return
	 */
	public int length() {
		return fSQL_Statement.length();
	}

	public AbsSimpleNode[] getNodesPaths(AbsSimpleNode node) {
		ArrayList<AbsSimpleNode> retList = new ArrayList<AbsSimpleNode>();
		addParents(node, retList);
		return retList.toArray(new AbsSimpleNode[retList.size()]);
	}

	private void addParents(AbsSimpleNode node, ArrayList<AbsSimpleNode> retList) {
		retList.add(0, node);
		if (node.getParent() != null) {
			addParents(node.getParent(), retList);
		}
	}

	@Override
	public AbsSimpleNode[] getErrorNodes(AbsSimpleNode node) {
		ArrayList<AbsSimpleNode> retList = new ArrayList<AbsSimpleNode>();
		searchErrorNodes(node, retList);
		return retList.toArray(new AbsSimpleNode[retList.size()]);
	}

	private static void searchErrorNodes(AbsSimpleNode node,
			ArrayList<AbsSimpleNode> retList) {
		if (node == null) {
			return;
		}
		AbsSimpleNode[] sn = node.getChildren();

		if (node.fInfomation != null && node.fInfomation.hasError()) {
			retList.add(node);
		}
		for (int i = 0; i < sn.length; i++) {
			searchErrorNodes(sn[i], retList);
		}
	}

}