package com.purui.prims.headless.core.translator.calcite.sql.render;


import com.purui.prims.common.jsqlparser.SqlSelectHelper;
import com.purui.prims.common.pojo.ColumnOrder;
import com.purui.prims.headless.api.pojo.MeasureParam;
import com.purui.prims.headless.api.pojo.enums.EngineType;
import com.purui.prims.headless.core.pojo.MetricQueryParam;
import com.purui.prims.headless.core.translator.calcite.s2sql.DataSource;
import com.purui.prims.headless.core.translator.calcite.schema.SemanticSchema;
import com.purui.prims.headless.core.translator.calcite.sql.Renderer;
import com.purui.prims.headless.core.translator.calcite.sql.TableView;
import com.purui.prims.headless.core.translator.calcite.sql.node.SemanticNode;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.validate.SqlValidatorScope;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * process the query result items from query request
 */
public class OutputRender extends Renderer {

    @Override
    public void render(MetricQueryParam metricCommand, List<DataSource> dataSources, SqlValidatorScope scope,
                       SemanticSchema schema, boolean nonAgg) throws Exception {
        TableView selectDataSet = super.tableView;
        EngineType engineType = EngineType.fromString(schema.getSemanticModel().getDatabase().getType());
        for (String dimension : metricCommand.getDimensions()) {
            selectDataSet.getMeasure().add(SemanticNode.parse(dimension, scope, engineType));
        }
        for (String metric : metricCommand.getMetrics()) {
            selectDataSet.getMeasure().add(SemanticNode.parse(metric, scope, engineType));
            // rollOver
            if (metricCommand.getRollOverMap() != null) {
                if (metricCommand.getRollOverMap().containsKey(metric)) {
                    metric = metricCommand.getRollOverMap().get(metric);
                    selectDataSet.getMeasure().add(SemanticNode.parse(metric, scope, engineType));
                }
            }
        }
        // 复合指标，需要转换为最终指标
        for (String metric : metricCommand.getCompositeMetrics().keySet()) {
            String expression = metricCommand.getCompositeMetrics().get(metric);
            // remove metric field
            Set<String> fields = SqlSelectHelper.getColumnFromExpr(expression);
            selectDataSet.getMeasure().removeIf(node -> {
                return tableView.containsNode(node, fields);
            });
            SqlNode metricNode = SemanticNode.parse(expression, scope, engineType);
            selectDataSet.getMeasure().add(SemanticNode.buildAs(metric, metricNode));
        }
        // 复合指标增加统计维度
        if (metricCommand.getCompositeMetrics().size() > 0) {
            for (String dimension : metricCommand.getDimensions()) {
                selectDataSet.getDimension().add(SemanticNode.parse(dimension, scope, engineType));
            }
        }
        // 派生指标，需要转换为最终指标
        for (String metric : metricCommand.getDerivedMetrics().keySet()) {
            MeasureParam measureParam = metricCommand.getDerivedMetrics().get(metric);
            SqlNode metricNode = SemanticNode.parse(measureParam.getBizName(), scope, engineType);
            selectDataSet.getMeasure().add(SemanticNode.buildAs(metric, metricNode));
        }

        if (metricCommand.getLimit() > 0) {
            SqlNode offset = SemanticNode.parse(metricCommand.getLimit().toString(), scope, engineType);
            selectDataSet.setOffset(offset);
        }
        if (!CollectionUtils.isEmpty(metricCommand.getOrder())) {
            List<SqlNode> orderList = new ArrayList<>();
            for (ColumnOrder columnOrder : metricCommand.getOrder()) {
                if (SqlStdOperatorTable.DESC.getName().equalsIgnoreCase(columnOrder.getOrder())) {
                    orderList.add(SqlStdOperatorTable.DESC.createCall(SqlParserPos.ZERO,
                        new SqlNode[] {SemanticNode.parse(columnOrder.getCol(), scope, engineType)}));
                } else {
                    orderList.add(SemanticNode.parse(columnOrder.getCol(), scope, engineType));
                }
            }
            selectDataSet.setOrder(new SqlNodeList(orderList, SqlParserPos.ZERO));
        }
    }
}
