package hiro.yoshioka.ast.sql.oracle.util;

import hiro.yoshioka.ast.sql.AbsSimpleNode;
import hiro.yoshioka.ast.sql.IToken;
import hiro.yoshioka.ast.sql.oracle.ASTAlterMulti;
import hiro.yoshioka.ast.sql.oracle.ASTAlterSession;
import hiro.yoshioka.ast.sql.oracle.ASTAlterTable;
import hiro.yoshioka.ast.sql.oracle.ASTColumnConstraint;
import hiro.yoshioka.ast.sql.oracle.ASTComment;
import hiro.yoshioka.ast.sql.oracle.ASTCondition;
import hiro.yoshioka.ast.sql.oracle.ASTCreate;
import hiro.yoshioka.ast.sql.oracle.ASTCreateFunction;
import hiro.yoshioka.ast.sql.oracle.ASTCreateProcedure;
import hiro.yoshioka.ast.sql.oracle.ASTCreateTable;
import hiro.yoshioka.ast.sql.oracle.ASTDDL;
import hiro.yoshioka.ast.sql.oracle.ASTDML;
import hiro.yoshioka.ast.sql.oracle.ASTDecodeExpression;
import hiro.yoshioka.ast.sql.oracle.ASTDeleteStatement;
import hiro.yoshioka.ast.sql.oracle.ASTFlashBackClause;
import hiro.yoshioka.ast.sql.oracle.ASTFrom;
import hiro.yoshioka.ast.sql.oracle.ASTGroupByClause;
import hiro.yoshioka.ast.sql.oracle.ASTInsertColumns;
import hiro.yoshioka.ast.sql.oracle.ASTInsertStatement;
import hiro.yoshioka.ast.sql.oracle.ASTJoinType;
import hiro.yoshioka.ast.sql.oracle.ASTJoinedTable;
import hiro.yoshioka.ast.sql.oracle.ASTOrderByClause;
import hiro.yoshioka.ast.sql.oracle.ASTPhysicalPropertiesClause;
import hiro.yoshioka.ast.sql.oracle.ASTQueryTableExpressionClause;
import hiro.yoshioka.ast.sql.oracle.ASTRelationalList;
import hiro.yoshioka.ast.sql.oracle.ASTRelationalProperties;
import hiro.yoshioka.ast.sql.oracle.ASTSelectColumns;
import hiro.yoshioka.ast.sql.oracle.ASTSelectIntoStatement;
import hiro.yoshioka.ast.sql.oracle.ASTSelectSecond;
import hiro.yoshioka.ast.sql.oracle.ASTSelectThird;
import hiro.yoshioka.ast.sql.oracle.ASTSetClause;
import hiro.yoshioka.ast.sql.oracle.ASTStart;
import hiro.yoshioka.ast.sql.oracle.ASTSub_program_body;
import hiro.yoshioka.ast.sql.oracle.ASTTableConstraint;
import hiro.yoshioka.ast.sql.oracle.ASTTableRefference;
import hiro.yoshioka.ast.sql.oracle.ASTUnionPart;
import hiro.yoshioka.ast.sql.oracle.ASTUpdateStatement;
import hiro.yoshioka.ast.sql.oracle.ASTUsingIndexClause;
import hiro.yoshioka.ast.sql.oracle.ASTValueClause;
import hiro.yoshioka.ast.sql.oracle.ASTValueClauses;
import hiro.yoshioka.ast.sql.oracle.ASTWhereClause;
import hiro.yoshioka.ast.sql.oracle.ASTtableIDDot;
import hiro.yoshioka.ast.sql.oracle.SimpleNode;
import hiro.yoshioka.ast.sql.oracle.Token;
import hiro.yoshioka.ast.sql.oracle.WolfSQLParserConstants;
import hiro.yoshioka.ast.sql.oracle.WolfSQLParserTreeConstants;
import hiro.yoshioka.ast.sql.util.ASTFormatingInfo;
import hiro.yoshioka.ast.sql.util.StringFormat;
import hiro.yoshioka.util.CSVUtil;
import hiro.yoshioka.util.StringUtil;

public class CodeFormatingVisitor extends DefaultSQLNodeVisitor {
	ASTFormatingInfo _info;

	int fDepth = 0;

	int fSubquery = 0;

	StringBuffer fDepthString = new StringBuffer("");

	StringBuffer _buf = new StringBuffer();

	private boolean fFollow;

	private void deep() {
		fDepth++;
		fDepthString.setLength(0);
		for (int i = 0; i < fDepth; i++) {
			fDepthString.append(_info.getIndentString());
		}
	}

	private void undeep() {
		fDepth--;

		fDepthString.setLength(0);
		for (int i = 0; i < fDepth; i++) {
			fDepthString.append(_info.getIndentString());
		}
	}

	private void appendFollow(String text, boolean deep) {
		append(text, deep);
		fFollow = true;
	}

	private void append(String text, boolean deep) {
		if (deep && !fFollow) {
			_buf.append(fDepthString);
		}
		_buf.append(text);
		fFollow = false;
	}

	private void append(String text) {
		append(text, true);
	}

	private void appendln(String text, boolean deep) {
		append(text, deep);
		_buf.append(StringUtil.LINE_SEPARATOR);
	}

	private void appendln(String text) {
		appendln(text, true);
	}

	private void lnAppend(String text) {
		_buf.append(StringUtil.LINE_SEPARATOR);
		append(text);
	}

	public Object doJob(SimpleNode node, Object data) {
		SimpleNode n;
		for (int ord = 0; ord < node.jjtGetNumChildren(); ord++) {
			n = (SimpleNode) node.jjtGetChild(ord);
			n.jjtAccept(this, data);
		}
		return data;
	}

	public Object childrenAppendStr(SimpleNode node, Object data, String c) {
		SimpleNode n;

		boolean callStringFormat = true;
		boolean isSelectColumnsNode = node.getID() == WolfSQLParserTreeConstants.JJTSELECTCOLUMNS;
		boolean isRelationListNode = node.getID() == WolfSQLParserTreeConstants.JJTRELATIONALLIST;

		if (isSelectColumnsNode || isRelationListNode) {
			AbsSimpleNode[] cld = node.getChildren();
			if (_info.selectColumnCommentOn) {
				callStringFormat = false;
			} else {
				for (int i = 0; i < cld.length; i++) {
					ASTDecodeExpression dn = (ASTDecodeExpression) getFirstChild(
							cld[i],
							WolfSQLParserTreeConstants.JJTDECODEEXPRESSION);
					if (dn != null) {
						callStringFormat = false;
						break;
					}
				}
			}
			if (!callStringFormat) {
				int maxColName = 0;
				int maxAsName = 0;
				for (int i = 0; i < cld.length; i++) {
					String specialToken = "";
					ASTDecodeExpression dn = (ASTDecodeExpression) getFirstChild(
							cld[i],
							WolfSQLParserTreeConstants.JJTDECODEEXPRESSION);

					if (dn == null || !_info.expandDecode()) {
						int size = getLine(cld[i].getChild(0)).length();
						if (maxColName < size) {
							maxColName = size;
						}
					}
					if (cld[i].getChildren().length >= 2) {
						int size = getLine(cld[i].getChild(1)).length();
						if (maxAsName < size) {
							maxAsName = size;
						}
					}
				}

				for (int i = 0; i < cld.length; i++) {
					String specialToken = "";
					ASTDecodeExpression dn = (ASTDecodeExpression) getFirstChild(
							cld[i],
							WolfSQLParserTreeConstants.JJTDECODEEXPRESSION);

					if (isSelectColumnsNode && maxAsName > 0) {
						if (dn == null || !_info.expandDecode()) {
							String colName = getLine(cld[i].getChild(0));
							if (cld[i].getChildren().length >= 2
									&& isSelectColumnsNode) {
								String as = getLine(cld[i].getChild(1));
								append(String.format("%-" + maxColName
										+ "s AS %-" + maxAsName + "s ",
										colName, as));
							} else {
								append(String.format("%-" + maxColName
										+ "s    %-" + maxAsName + "s ",
										colName, StringUtil.EMPTY_STRING));
							}
						} else {
							dn.jjtAccept(this, data);
							if (cld[i].getChildren().length >= 2
									&& isSelectColumnsNode) {
								String as = getLine(cld[i].getChild(1));
								append(String.format(" AS %-" + maxAsName
										+ "s ", as));
							}
						}
					} else {
						String colName = getLine(cld[i].getChild(0));
						append(String.format("%-" + maxColName + "s ", colName));
					}
					if (cld[i].getFirstToken().getSpecialToken() != null) {
						specialToken = cld[i].getFirstToken().getSpecialToken()
								.getImage().replaceAll("[\r\n]+", "");
					}
					if (i < cld.length - 1) {
						appendln(c + specialToken);
					} else {
						appendln(specialToken + StringUtil.EMPTY_STRING);
					}
				}
				return data;
			}
		}
		AbsSimpleNode[] cld = node.getChildren();
		String[] targets = new String[cld.length];
		for (int i = 0; i < cld.length; i++) {
			targets[i] = getLine(cld[i]);
		}

		String[] result = StringFormat.format(_info.left, _info.mode,
				_info.col_num, _info.max_col_len, targets, c);
		for (int i = 0; i < result.length; i++) {
			appendln(result[i]);
		}

		return data;
	}

	public Object childrenAppendComma(SimpleNode node, Object data) {
		return childrenAppendStr(node, data, "" + CSVUtil.SEPARETOR_COMMA);
	}

	String getLine(AbsSimpleNode node) {
		String stext = _info.getSQLText();
		String line = new BackWord(stext).backWord(node.getFirstToken(), node
				.getLastToken());
		if (_info.continuingBlank2aBlank()) {
			return StringUtil.continuingBlank2aBlank(line);
		} else {
			return line;
		}
	}

	public class BackWord {
		String fTargetText;

		public BackWord(String targetText) {
			fTargetText = targetText;
		}

		public String backWord(IToken begin, IToken end) {
			String lsp = StringUtil.getThisLineSeparator(fTargetText);
			String[] lineData = fTargetText.split(lsp, -1);
			StringBuffer ret = new StringBuffer();
			for (int i = begin.getBeginLine() - 1; i <= end.getEndLine() - 1; i++) {
				if (i == begin.getBeginLine() - 1) {
					if (i == end.getEndLine() - 1) {
						ret
								.append(lineData[i].substring(begin
										.getBeginColumn() - 1, end
										.getEndColumn()));
					} else {
						ret.append(lineData[i]
								.substring(begin.getBeginColumn() - 1));
					}
				} else if (i > begin.getBeginLine() - 1) {
					if (i == end.getEndLine() - 1) {
						ret
								.append(lineData[i].substring(0, end
										.getEndColumn()));
					} else {
						ret.append(lineData[i]);
					}
				} else {

					System.out.println("ELSE[" + lineData[i]);
				}
			}
			return ret.toString();
		}

	}

	public Object visit(ASTStart node, Object data) {
		Token tkn = (Token) node.getFirstToken();
		_info = (ASTFormatingInfo) data;
		super.visit(node, data);
		return _buf.toString();
	}

	@Override
	public Object visit(ASTDDL node, Object data) {
		Token tkn = (Token) node.getFirstToken();
		if (tkn.getSpecialToken() != null) {
			appendln(tkn.getSpecialTokensImage());
		}
		super.visit(node, data);
		return _buf.toString();
	}

	public Object visit(ASTDML node, Object data) {
		Token tkn = (Token) node.getFirstToken();
		if (tkn.getSpecialToken() != null) {
			appendln(tkn.getSpecialTokensImage());
		}
		super.visit(node, data);
		return _buf.toString();
	}

	public Object visit(ASTSelectThird node, Object data) {
		boolean writtenWhere = false;
		boolean writtenGroup = false;
		boolean writtenOrder = false;

		appendln("SELECT ");

		AbsSimpleNode[] nodes = node.getChildren();
		for (int i = 0; i < nodes.length; i++) {
			((SimpleNode) nodes[i]).jjtAccept(this, data);
		}
		return null;
	}

	@Override
	public Object visit(ASTSelectIntoStatement node, Object data) {
		appendln(getLine(node) + " ");
		return super.visit(node, data);
	}

	public Object visit(ASTSelectSecond node, Object data) {
		fSubquery++;
		if (fSubquery > _info.getSubqueryFold()) {
			append(getLine(node));
		} else {
			AbsSimpleNode first = node.getChild(0);
			if (node.getFirstToken().getKind() == WolfSQLParserConstants.OPENPAREN) {
				appendln("(");
				deep();
				((SimpleNode) node.getChild(0)).jjtAccept(this, data);
				undeep();
				appendln(")");
			} else {
				((SimpleNode) node.getChild(0)).jjtAccept(this, data);
			}
		}
		fSubquery--;
		return null;
	}

	public Object visit(ASTUnionPart node, Object data) {
		IToken t = node.getSecondToken();
		if (t != null && t.getImage().equalsIgnoreCase("ALL")) {
			appendln("UNION ALL");
		} else {
			appendln(node.getFirstToken().toString());
		}
		return null;
	}

	public Object visit(ASTDeleteStatement node, Object data) {
		if ("FROM".equalsIgnoreCase(node.getSecondToken().toString())) {
			append(node.getFirstToken().toString().toUpperCase() + " ");
			appendln(node.getSecondToken().toString().toUpperCase() + " ");
		} else {
			appendln(node.getFirstToken().toString().toUpperCase() + " ");
		}
		SimpleNode n;
		deep();
		n = (SimpleNode) node.getChild(0);
		appendln(getLine(n));
		undeep();
		if (node.jjtGetNumChildren() > 1) {
			n = (SimpleNode) node.getChild(1);
			n.jjtAccept(this, data);
		}
		return null;
	}

	/**
	 * CREATE文の作成
	 */
	public Object visit(ASTCreate node, Object data) {
		append("CREATE ");
		if (node.getSecondToken().getKind() == WolfSQLParserConstants.OR) {
			append(node.getSecondToken().getImage() + " ");
			append(node.getThirdToken().getImage() + " ");
		}
		return ((SimpleNode) node.getChild(0)).jjtAccept(this, data);

	}

	public Object visit(ASTAlterTable node, Object data) {
		append("ALTER TABLE ");
		AbsSimpleNode[] children = node.getChildren();
		if (children[0] instanceof ASTtableIDDot) {// with schema
			append(getLine(children[0]));
			appendln(getLine(children[1]));
			deep();
			((SimpleNode) children[2]).jjtAccept(this, data);
		} else {
			appendln(getLine(children[0]));
			deep();
			((SimpleNode) children[1]).jjtAccept(this, data);
		}
		undeep();
		return data;

	}

	public Object visit(ASTAlterSession node, Object data) {
		append("ALTER SESSION ");
		AbsSimpleNode[] children = node.getChildren();
		if (children.length > 0) { // set clause
			appendln("SET");
			deep();
			appendln(getLine(children[0]));
			undeep();
		}
		return data;

	}

	public Object visit(ASTAlterMulti node, Object data) {
		if (WolfSQLParserConstants.ADD == node.getFirstToken().getKind()) {
			append("ADD ");
			if (WolfSQLParserConstants.OPENPAREN == node.getSecondToken()
					.getKind()) {
				appendln("(");
				deep();
				childrenAppendComma(node, data);
				undeep();
				appendln(")");
			} else {
				childrenAppendComma(node, data);
			}
		} else {
			appendln("(");
			deep();
			childrenAppendComma(node, data);
			undeep();
			appendln(")");
		}

		return data;
	}

	/**
	 * CREATE TABLE文の作成
	 */
	public Object visit(ASTCreateTable node, Object data) {
		if (WolfSQLParserConstants.GLOBAL == node.getFirstToken().getKind()) {
			append("GLOBAL TEMPORARY ");
		}
		append("TABLE ");

		AbsSimpleNode[] children = node.getChildren();

		for (int i = 0; i < children.length; i++) {
			if (children[i].getID() == WolfSQLParserTreeConstants.JJTIDDOT) {
				append(getLine(children[i]));
			} else if (children[i].getID() == WolfSQLParserTreeConstants.JJTTABLEIDENTIFIER) {
				appendln(getLine(children[i]));
			} else {
				((SimpleNode) children[i]).jjtAccept(this, data);
			}
		}

		return data;

	}

	public Object visit(ASTCreateFunction node, Object data) {
		append(node.getFirstToken().getImage() + " ");

		AbsSimpleNode[] children = node.getChildren();

		for (int i = 0; i < children.length; i++) {
			if (children[i].getID() == WolfSQLParserTreeConstants.JJTIDDOT) {
				append(getLine(children[i]));
			} else if (children[i].getID() == WolfSQLParserTreeConstants.JJTTABLEIDENTIFIER) {
				appendln(getLine(children[i]));
			} else if (children[i].getID() == WolfSQLParserTreeConstants.JJTEXPR) {
				appendln("RETURN " + getLine(children[i]) + ";");
			} else {
				((SimpleNode) children[i]).jjtAccept(this, data);
			}
		}
		appendln("END;");

		return data;

	}

	public Object visit(ASTCreateProcedure node, Object data) {
		append(node.getFirstToken().getImage() + " ");

		AbsSimpleNode[] children = node.getChildren();

		for (int i = 0; i < children.length; i++) {
			if (children[i].getID() == WolfSQLParserTreeConstants.JJTIDDOT) {
				append(getLine(children[i]));
			} else if (children[i].getID() == WolfSQLParserTreeConstants.JJTTABLEIDENTIFIER) {
				appendln(getLine(children[i]));
			} else {
				((SimpleNode) children[i]).jjtAccept(this, data);
			}
		}
		appendln("END;");

		return data;

	}

	@Override
	public Object visit(ASTSub_program_body node, Object data) {
		AbsSimpleNode[] children = node.getChildren();
		if (children[0].getID() == WolfSQLParserTreeConstants.JJTASSIGNMENTSTATEMENTS) {
			((SimpleNode) children[0]).jjtAccept(this, data);
			appendln("BEGIN");
			deep();
			for (int i = 1; i < children.length; i++) {
				((SimpleNode) children[i]).jjtAccept(this, data);
			}
		} else {
			appendln("BEGIN");
			deep();
			for (int i = 0; i < children.length; i++) {
				((SimpleNode) children[i]).jjtAccept(this, data);
			}
		}
		undeep();
		return data;
	}

	public Object visit(ASTRelationalList node, Object data) {
		appendln("(");
		deepSplitComma(node, data);
		appendln(")");
		return data;
	}

	@Override
	public Object visit(ASTPhysicalPropertiesClause node, Object data) {
		deep();
		IToken t = node.getFirstToken();
		if (t.getKind() == WolfSQLParserConstants.ORGANIZATION) {
			append(t.getImage() + " ");
			IToken t2 = node.getSecondToken();
			appendln(t2.getImage() + " ");
		} else if (t.getKind() == WolfSQLParserConstants.CLUSTER) {
			appendln(t.getImage()
					+ "  some day  i support this statement sorryyyyyyyyyyyyyyyyyyyyyyyyyyyy");
		}

		childrenAppendStr(node, data, " ");
		undeep();
		return data;
	}

	public Object visit(ASTRelationalProperties node, Object data) {
		AbsSimpleNode[] children = node.getChildren();

		if (children[0].getID() == WolfSQLParserTreeConstants.JJTTABLECONSTRAINT) {
			System.out.println("call JJTTABLECONSTRAINT children[0].getID()="
					+ children[0].getID());
			return ((ASTColumnConstraint) children[0]).jjtAccept(this, data);
		} else { // Identifier() FullType() [ <DEFAULT2> ] (
			// ColumnConstraint() )*
			append(getLine(children[0]) + " " + getLine(children[1]));
			if (node.fDefault) {
				append("DEFAULT");
			}
			for (int i = 2; i < children.length; i++) {
				append(getLine(children[i]));
			}
		}

		return data;
	}

	public Object visit(ASTTableConstraint node, Object data) {
		AbsSimpleNode[] children = node.getChildren();
		IToken t = node.getFirstToken();
		int iChildIndex = 0;

		if (t.getKind() == WolfSQLParserConstants.CONSTRAINT) {
			append("CONSTRAINT " + getLine(children[iChildIndex++]));
			t = node.getThirdToken();
		}
		// ( <UNIQUE> | ( <PRIMARY> <KEY> ) ) <OPENPAREN> Identifier() ( <COMMA>
		// Identifier() )* <CLOSEPAREN>
		if (t.getKind() == WolfSQLParserConstants.UNIQUE
				|| t.getKind() == WolfSQLParserConstants.PRIMARY) {
			if (t.getKind() == WolfSQLParserConstants.UNIQUE) {
				append("UNIQUE ");
			} else if (t.getKind() == WolfSQLParserConstants.PRIMARY) {
				append("PRIMARY KEY ");
			}
			append("( ");
			for (boolean flg = false; iChildIndex < children.length - 1; iChildIndex++, flg = true) {
				if (flg) {
					append(",");
				}
				append(getLine(children[iChildIndex]));
			}
			append(") ");
		} else if (t.getKind() == WolfSQLParserConstants.CHECK) {
			append("CHECK ");
		} else {
			append(getLine(children[iChildIndex++]));
		}
		System.out.println("☆ミ　ConstraintStateのはず " + children[iChildIndex]);
		((ASTColumnConstraint) children[iChildIndex]).jjtAccept(this, data);

		return data;
	}

	public Object visit(ASTUsingIndexClause node, Object data) {
		appendln("USING INDEX");
		deep();
		AbsSimpleNode[] children = node.getChildren();

		for (int i = 0; i < children.length; i++) {
			appendln(getLine(children[i]));
		}
		undeep();
		return data;
	}

	public Object visit(ASTUpdateStatement node, Object data) {
		append("UPDATE ");

		SimpleNode n;

		for (int i = 0; i < node.jjtGetNumChildren(); i++) {
			n = (SimpleNode) node.getChild(i);
			switch (n.getID()) {
			case WolfSQLParserTreeConstants.JJTHINT:
				n.jjtAccept(this, data);
				break;
			case WolfSQLParserTreeConstants.JJTQUERYTABLEEXPRESSIONCLAUSE:
				appendln(getLine(n));
				break;
			case WolfSQLParserTreeConstants.JJTSETCLAUSE:
				n.jjtAccept(this, data);
				break;
			case WolfSQLParserTreeConstants.JJTWHERECLAUSE:
				n.jjtAccept(this, data);
				break;
			case WolfSQLParserTreeConstants.JJTRETURNINGCLAUSE:
				appendln(getLine(n));
				break;
			default:
				break;
			}
		}

		return null;
	}

	public Object visit(ASTInsertStatement node, Object data) {
		append("INSERT INTO ");
		SimpleNode n;
		for (int i = 0; i < node.jjtGetNumChildren(); i++) {
			n = (SimpleNode) node.jjtGetChild(i);
			n.jjtAccept(this, data);
		}
		return null;
	}

	public Object visit(ASTComment node, Object data) {
		StringBuffer head = new StringBuffer();
		head.append(node.getFirstToken()).append(" ");
		head.append(node.getSecondToken()).append(" ");
		head.append(node.getThirdToken()).append(" ");
		append(head.toString());

		if (node.jjtGetNumChildren() > 0) {
			append(getLine(node.getChild(0)));
		}
		StringBuffer tail = new StringBuffer();
		append(" IS ");
		append(node.getLastToken().toString());

		return null;
	}

	public Object visit(ASTValueClauses node, Object data) {
		SimpleNode n;

		for (int i = 0; i < node.jjtGetNumChildren(); i++) {
			if (i > 0) {
				appendln(",");
			}
			n = (SimpleNode) node.getChild(i);
			switch (n.getID()) {
			case WolfSQLParserTreeConstants.JJTVALUECLAUSE:
				n.jjtAccept(this, data);
				break;
			case WolfSQLParserTreeConstants.JJTSUBQUERY:
				appendln(getLine(n));
				break;
			default:
				break;
			}
		}
		return null;
	}

	public Object visit(ASTValueClause node, Object data) {
		appendln("VALUES (");
		SimpleNode child = (SimpleNode) node.getChild(0);
		if (child.getID() == WolfSQLParserTreeConstants.JJTEXPRESSIONLIST) {
			deepSplitComma(child, data);
		} else {
			child.jjtAccept(this, data);
		}
		appendln(")");
		return null;
	}

	public Object visit(ASTInsertColumns node, Object data) {
		appendln("(");
		deep();
		childrenAppendComma(node, data);
		undeep();
		appendln(")");
		return null;
	}

	public Object visit(ASTSelectColumns node, Object data) {
		return deepSplitComma(node, data);
	}

	public Object visit(ASTSetClause node, Object data) {
		appendln(node.getFirstToken().toString().toUpperCase());
		return deepSplitComma(node, data);
	}

	private Object deepSplitComma(SimpleNode node, Object data) {
		deep();
		if (node.jjtGetNumChildren() <= 0) {
			appendln(node.getFirstToken().toString());
		} else {
			childrenAppendComma(node, data);
		}
		undeep();
		return data;
	}

	private AbsSimpleNode getFirstChild(AbsSimpleNode node, int childNodeID) {
		if (node.getID() == childNodeID) {
			return node;
		}
		SimpleNode n = (SimpleNode) node;
		if (n.jjtGetNumChildren() > 0) {
			return getFirstChild((AbsSimpleNode) n.jjtGetChild(0), childNodeID);
		}
		return null;
	}

	public Object visit(ASTDecodeExpression node, Object data) {
		// <DECODE> <OPENPAREN> Expr() ( <COMMA> Expr() )* <CLOSEPAREN>
		appendln("DECODE(");
		deepSplitComma(node, data);
		appendln(")");
		return data;
	}

	private void appendBetweenComma(SimpleNode node, Object data, int startIndex) {
		SimpleNode n;
		System.out.println("♪♪♪♪♪node:" + node.getClass() + "/"
				+ node.jjtGetNumChildren() + "/ st:" + startIndex);
		for (int ord = startIndex; ord < node.jjtGetNumChildren(); ord++) {
			n = (SimpleNode) node.jjtGetChild(ord);
			if (ord > startIndex) {
				append(",");
			}
			append(getLine(n));
		}
	}

	public Object visit(ASTFrom node, Object data) {
		appendln("FROM");
		deep();
		SimpleNode n;
		if (node.jjtGetNumChildren() <= 0) {
			return data;
		}
		for (int ord = 0; ord < node.jjtGetNumChildren(); ord++) {
			n = (SimpleNode) node.jjtGetChild(ord);

			n.jjtAccept(this, data);
			String comma = ",";
			if (ord + 1 == node.jjtGetNumChildren()) {
				comma = "";
			}
			append(comma, false);
		}
		appendln("", false);
		undeep();
		return data;
	}

	public Object visit(ASTTableRefference node, Object data) {
		SimpleNode n;
		for (int ord = 0; ord < node.jjtGetNumChildren(); ord++) {
			n = (SimpleNode) node.jjtGetChild(ord);
			if (ord >= 1) {
				appendln("", false);
			}
			n.jjtAccept(this, data);
		}
		return data;
	}

	public Object visit(ASTQueryTableExpressionClause node, Object data) {
		SimpleNode n = (SimpleNode) node.jjtGetChild(0);
		if (n.getID() == WolfSQLParserTreeConstants.JJTSUBQUERY) {
			appendln("(");
			deep();
			n.jjtAccept(this, data);
			undeep();
			append(")");
		} else {
			// [ IDDot() ] Identifier() ( AtID() )?
			StringBuffer buf = new StringBuffer();
			if (node.getUpperSchema().length() > 0) {
				buf.append(node.getSchemaString()).append(".");
			}
			buf.append(node.getTableNameString());
			append(buf.toString());
		}
		AbsSimpleNode[] children = node.getChildren();
		for (int i = 1; i < children.length; i++) {
			if (children[i].getID() == WolfSQLParserTreeConstants.JJTFLASHBACKCLAUSE) {
				((SimpleNode) children[i]).jjtAccept(this, data);
				break;
			}
		}
		if (!"".equals(node.fAliasString)) {
			append(node.fAliasString);
		}
		return data;
	}

	@Override
	public Object visit(ASTFlashBackClause node, Object data) {
		StringBuffer buf = new StringBuffer();
		int idx = 0;
		if (node.isVersion) {
			buf.append(" VERSIONS BETWEEN ");
			if (node.isScan) {
				buf.append("SCAN ");
			} else {
				buf.append("TIMESTAMP ");
			}
			if (node.isMin) {
				buf.append("MINVALUE ");
			} else {
				buf.append(getLine(node.getChild(idx++)));
			}
			buf.append(" AND ");
			if (node.isMax) {
				buf.append("MAXVALUE ");
			} else {
				buf.append(getLine(node.getChild(idx++)));
			}
		} else {
			buf.append(" AS OF ");
			if (node.isScan) {
				buf.append("SCAN ");
			} else {
				buf.append("TIMESTAMP ");
			}
			buf.append(getLine(node.getChild(idx++)));
		}
		buf.append(" ");
		lnAppend(buf.toString());
		return data;
	}

	private boolean isSubqueryTable(SimpleNode node) {
		AbsSimpleNode n = node.getChild(0);
		if (n.getID() == WolfSQLParserTreeConstants.JJTQUERYTABLEEXPRESSIONCLAUSE) {
			n = n.getChild(0);
			if (n.getID() == WolfSQLParserTreeConstants.JJTSUBQUERY) {
				return true;
			}
		}
		return false;
	}

	/**
	 * ASTJoinedTableのフォーマット。<BR>
	 * JoinType() <JOIN> TableRefference(list)
	 */
	public Object visit(ASTJoinedTable node, Object data) {
		SimpleNode n = (SimpleNode) node.jjtGetChild(0);
		if (n.getID() == WolfSQLParserTreeConstants.JJTJOINTYPE) {
			n.jjtAccept(this, data); // LEFT OUTERとか
			appendFollow("JOIN ", true); // JOIN
			if (node.jjtGetNumChildren() >= 2) {
				n = (SimpleNode) node.jjtGetChild(1); // TableReference

				if (isSubqueryTable(n)) {
					n.jjtAccept(this, data);
				} else {
					appendFollow(getLine(n) + " ", true);
				}
				if (node.jjtGetNumChildren() >= 3) {
					n = (SimpleNode) node.jjtGetChild(2);
					if (n.getID() == WolfSQLParserTreeConstants.JJTCONDITION) {
						appendFollow("ON ", true); // ON Condition
						if (n.jjtGetNumChildren() > 1) {
							appendln("");
							deep();
						}
						n.jjtAccept(this, data);
						if (n.jjtGetNumChildren() > 1) {
							undeep();
						}
					} else {
						append("USING (");
						appendBetweenComma(node, data, 2);
						append(")");
					}
				}
			}

		} else {
			append(getLine(node));
		}
		return data;
	}

	public Object visit(ASTJoinType node, Object data) {
		appendFollow(node.getFirstToken().getImage() + " ", true);
		if (node.getLastToken().getKind() == WolfSQLParserConstants.OUTER) {
			appendFollow(node.getLastToken().getImage() + " ", true);
		}
		return data;
	}

	public Object visit(ASTWhereClause node, Object data) {
		appendln("WHERE");
		deep();
		SimpleNode n;
		if (node.jjtGetNumChildren() <= 0) {
			return data;
		}
		n = (SimpleNode) node.getChild(0);
		if (_info.isSimpleCodition()) {
			appendln(getLine(n));
		} else {
			n.jjtAccept(this, data);

		}
		undeep();
		return data;
	}

	public Object visit(ASTCondition node, Object data) {
		SimpleNode n;
		IToken[] tokens = node.getTokens();

		n = (SimpleNode) node.getChild(0);

		if (_info.afterAndOr() && node.jjtGetNumChildren() > 1) {
			appendln(getLine(n) + " " + ((Token) tokens[0]).image);
		} else {
			appendln(getLine(n));
		}

		for (int ord = 1; ord < node.jjtGetNumChildren(); ord++) {
			n = (SimpleNode) node.jjtGetChild(ord);
			String tokenString = "";
			if (_info.afterAndOr()) {
				if (tokens.length - 1 >= ord) {
					tokenString = " " + ((Token) tokens[ord]).image;
				}
				appendln(getLine(n) + tokenString);
			} else {
				tokenString = ((Token) tokens[ord - 1]).image + " ";
				appendln(tokenString + getLine(n));
			}
		}
		return data;
	}

	public Object visit(ASTOrderByClause node, Object data) {
		appendln("ORDER BY");

		deep();
		childrenAppendComma(node, data);
		undeep();
		return data;
	}

	public Object visit(ASTGroupByClause node, Object data) {
		appendln("GROUP BY");

		deep();
		IToken t3 = node.getThirdToken();
		if (WolfSQLParserConstants.CUBE == t3.getKind()
				|| WolfSQLParserConstants.ROLLUP == t3.getKind()) {
			appendln(t3.getImage() + " (");
			deep();
			childrenAppendComma(node, data);
			undeep();
			appendln(")");
		} else {
			childrenAppendComma(node, data);
		}

		undeep();
		return data;
	}

}
