/*
 * Copyright 1999-2017 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.druid.sql.dialect.mysql.visitor.transform;

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.statement.*;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectSubqueryTableSource;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectTableReference;
import com.alibaba.druid.sql.dialect.oracle.visitor.OracleASTVisitorAdapter;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @author wenshao[szujobs@hotmail.com]
 */
public class FromSubqueryResolver extends OracleASTVisitorAdapter {
    private final List<SQLStatement> targetList;
    private final String viewName;
    private final Map<String, String> mappings = new LinkedHashMap<String, String>();

    private int viewNameSeed = 1;

    public FromSubqueryResolver(List<SQLStatement> targetList, String viewName) {
        this.targetList = targetList;
        this.viewName = viewName;
    }

    public boolean visit(OracleSelectSubqueryTableSource x) {
        return visit((SQLSubqueryTableSource) x);
    }

    public boolean visit(SQLSubqueryTableSource x) {
        String subViewName = generateSubViewName();

        SQLObject parent = x.getParent();
        if (parent instanceof SQLSelectQueryBlock) {
            SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) parent;
            queryBlock.setFrom(subViewName, x.getAlias());
        } else if (parent instanceof SQLJoinTableSource) {
            SQLJoinTableSource join = (SQLJoinTableSource) parent;
            if (join.getLeft() == x) {
                join.setLeft(subViewName, x.getAlias());
            } else if (join.getRight() == x) {
                join.setRight(subViewName, x.getAlias());
            }
        }

        SQLCreateViewStatement stmt = new SQLCreateViewStatement();

        stmt.setName(generateSubViewName());

        SQLSelect select = x.getSelect();
        stmt.setSubQuery(select);

        targetList.add(0, stmt);

        stmt.accept(new FromSubqueryResolver(targetList, viewName));

        return false;
    }

    public boolean visit(SQLExprTableSource x) {
        SQLExpr expr = x.getExpr();
        if (expr instanceof SQLIdentifierExpr) {
            SQLIdentifierExpr identifierExpr = (SQLIdentifierExpr) expr;
            String ident = identifierExpr.getName();
            String mappingIdent = mappings.get(ident);
            if (mappingIdent != null) {
                x.setExpr(new SQLIdentifierExpr(mappingIdent));
            }
        }
        return false;
    }

    public boolean visit(OracleSelectTableReference x) {
        return visit((SQLExprTableSource) x);
    }

    private String generateSubViewName() {
        return this.viewName + "_" + targetList.size();
    }

    public static List<SQLStatement> resolve(SQLCreateViewStatement stmt) {
        List<SQLStatement> targetList = new ArrayList<SQLStatement>();
        targetList.add(stmt);

        String viewName = SQLUtils.normalize(stmt.getName().getSimpleName());

        FromSubqueryResolver visitor = new FromSubqueryResolver(targetList, viewName);

        SQLWithSubqueryClause withSubqueryClause = stmt.getSubQuery().getWithSubQuery();
        if (withSubqueryClause != null) {
            stmt.getSubQuery().setWithSubQuery(null);

            for (SQLWithSubqueryClause.Entry entry : withSubqueryClause.getEntries()) {
                String entryName = entry.getAlias();

                SQLCreateViewStatement entryStmt = new SQLCreateViewStatement();
                entryStmt.setOrReplace(true);
                entryStmt.setDbType(stmt.getDbType());

                String entryViewName = visitor.generateSubViewName();
                entryStmt.setName(entryViewName);
                entryStmt.setSubQuery(entry.getSubQuery());

                visitor.targetList.add(0, entryStmt);
                visitor.mappings.put(entryName, entryViewName);

                entryStmt.accept(visitor);
            }
        }

        stmt.accept(visitor);

        DbType dbType = stmt.getDbType();
        for (int i = 0; i < targetList.size() - 1; ++i) {
            SQLCreateViewStatement targetStmt = (SQLCreateViewStatement) targetList.get(i);
            targetStmt.setOrReplace(true);
            targetStmt.setDbType(dbType);
            targetStmt.setAfterSemi(true);
        }

        return targetList;
    }
}
