package com.alibaba.druid.sql.builder.impl;

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLOrderBy;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectGroupByClause;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem;
import com.alibaba.druid.sql.ast.statement.SQLSelectQuery;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.builder.SQLSelectBuilder;
import com.alibaba.druid.sql.parser.SQLParserUtils;
import java.util.List;

public class SQLSelectBuilderImpl implements SQLSelectBuilder {
   private SQLSelectStatement stmt;
   private DbType dbType;

   public SQLSelectBuilderImpl(DbType dbType) {
      this(new SQLSelectStatement(), dbType);
   }

   public SQLSelectBuilderImpl(String sql, DbType dbType) {
      List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);
      if (stmtList.size() == 0) {
         throw new IllegalArgumentException("not support empty-statement :" + sql);
      } else if (stmtList.size() > 1) {
         throw new IllegalArgumentException("not support multi-statement :" + sql);
      } else {
         SQLSelectStatement stmt = (SQLSelectStatement)stmtList.get(0);
         this.stmt = stmt;
         this.dbType = dbType;
      }
   }

   public SQLSelectBuilderImpl(SQLSelectStatement stmt, DbType dbType) {
      this.stmt = stmt;
      this.dbType = dbType;
   }

   public SQLSelect getSQLSelect() {
      if (this.stmt.getSelect() == null) {
         this.stmt.setSelect(this.createSelect());
      }

      return this.stmt.getSelect();
   }

   public SQLSelectStatement getSQLSelectStatement() {
      return this.stmt;
   }

   public SQLSelectBuilderImpl select(String... columns) {
      SQLSelectQueryBlock queryBlock = this.getQueryBlock();

      for(String column : columns) {
         SQLSelectItem selectItem = SQLUtils.toSelectItem(column, this.dbType);
         queryBlock.addSelectItem(selectItem);
      }

      return this;
   }

   public SQLSelectBuilderImpl selectWithAlias(String column, String alias) {
      SQLSelectQueryBlock queryBlock = this.getQueryBlock();
      SQLExpr columnExpr = SQLUtils.toSQLExpr(column, this.dbType);
      SQLSelectItem selectItem = new SQLSelectItem(columnExpr, alias);
      queryBlock.addSelectItem(selectItem);
      return this;
   }

   public SQLSelectBuilderImpl from(String table) {
      return this.from(table, null);
   }

   public SQLSelectBuilderImpl from(String table, String alias) {
      SQLSelectQueryBlock queryBlock = this.getQueryBlock();
      SQLExprTableSource from = new SQLExprTableSource(new SQLIdentifierExpr(table), alias);
      queryBlock.setFrom((SQLTableSource)from);
      return this;
   }

   public SQLSelectBuilderImpl orderBy(String... columns) {
      SQLSelect select = this.getSQLSelect();
      SQLOrderBy orderBy = select.getOrderBy();
      if (orderBy == null) {
         orderBy = this.createOrderBy();
         select.setOrderBy(orderBy);
      }

      for(String column : columns) {
         SQLSelectOrderByItem orderByItem = SQLUtils.toOrderByItem(column, this.dbType);
         orderBy.addItem(orderByItem);
      }

      return this;
   }

   public SQLSelectBuilderImpl groupBy(String expr) {
      SQLSelectQueryBlock queryBlock = this.getQueryBlock();
      SQLSelectGroupByClause groupBy = queryBlock.getGroupBy();
      if (groupBy == null) {
         groupBy = this.createGroupBy();
         queryBlock.setGroupBy(groupBy);
      }

      SQLExpr exprObj = SQLUtils.toSQLExpr(expr, this.dbType);
      groupBy.addItem(exprObj);
      return this;
   }

   public SQLSelectBuilderImpl having(String expr) {
      SQLSelectQueryBlock queryBlock = this.getQueryBlock();
      SQLSelectGroupByClause groupBy = queryBlock.getGroupBy();
      if (groupBy == null) {
         groupBy = this.createGroupBy();
         queryBlock.setGroupBy(groupBy);
      }

      SQLExpr exprObj = SQLUtils.toSQLExpr(expr, this.dbType);
      groupBy.setHaving(exprObj);
      return this;
   }

   public SQLSelectBuilderImpl into(String expr) {
      SQLSelectQueryBlock queryBlock = this.getQueryBlock();
      SQLExpr exprObj = SQLUtils.toSQLExpr(expr, this.dbType);
      queryBlock.setInto(exprObj);
      return this;
   }

   public SQLSelectBuilderImpl where(String expr) {
      SQLSelectQueryBlock queryBlock = this.getQueryBlock();
      SQLExpr exprObj = SQLUtils.toSQLExpr(expr, this.dbType);
      queryBlock.setWhere(exprObj);
      return this;
   }

   public SQLSelectBuilderImpl whereAnd(String expr) {
      SQLSelectQueryBlock queryBlock = this.getQueryBlock();
      queryBlock.addWhere(SQLUtils.toSQLExpr(expr, this.dbType));
      return this;
   }

   public SQLSelectBuilderImpl whereOr(String expr) {
      SQLSelectQueryBlock queryBlock = this.getQueryBlock();
      SQLExpr exprObj = SQLUtils.toSQLExpr(expr, this.dbType);
      SQLExpr newCondition = SQLUtils.buildCondition(SQLBinaryOperator.BooleanOr, exprObj, false, queryBlock.getWhere());
      queryBlock.setWhere(newCondition);
      return this;
   }

   public SQLSelectBuilderImpl limit(int rowCount) {
      return this.limit(rowCount, 0);
   }

   public SQLSelectBuilderImpl limit(int rowCount, int offset) {
      this.getQueryBlock().limit(rowCount, offset);
      return this;
   }

   protected SQLSelectQueryBlock getQueryBlock() {
      SQLSelect select = this.getSQLSelect();
      SQLSelectQuery query = select.getQuery();
      if (query == null) {
         query = SQLParserUtils.createSelectQueryBlock(this.dbType);
         select.setQuery(query);
      }

      if (!(query instanceof SQLSelectQueryBlock)) {
         throw new IllegalStateException("not support from, class : " + query.getClass().getName());
      } else {
         SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock)query;
         return queryBlock;
      }
   }

   protected SQLSelect createSelect() {
      return new SQLSelect();
   }

   protected SQLSelectQuery createSelectQueryBlock() {
      return SQLParserUtils.createSelectQueryBlock(this.dbType);
   }

   protected SQLOrderBy createOrderBy() {
      return new SQLOrderBy();
   }

   protected SQLSelectGroupByClause createGroupBy() {
      return new SQLSelectGroupByClause();
   }

   public String toString() {
      return SQLUtils.toSQLString(this.stmt, (DbType)this.dbType);
   }
}
