/*
 * Copyright (C) 2016-2020 ActionTech.
 * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher.
 */

package com.actiontech.dble.plan.common.item.function.sumfunc;

import com.actiontech.dble.backend.mysql.nio.handler.util.RowDataComparator;
import com.actiontech.dble.net.mysql.RowDataPacket;
import com.actiontech.dble.plan.Order;
import com.actiontech.dble.plan.common.context.NameResolutionContext;
import com.actiontech.dble.plan.common.field.Field;
import com.actiontech.dble.plan.common.item.FieldTypes;
import com.actiontech.dble.plan.common.item.Item;
import com.actiontech.dble.plan.common.item.function.ItemFuncKeyWord;
import com.actiontech.dble.plan.common.time.MySQLTime;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLOrderBy;
import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr;
import com.alibaba.druid.sql.ast.expr.SQLAggregateOption;
import com.alibaba.druid.sql.ast.expr.SQLCharExpr;
import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;


public class ItemFuncGroupConcat extends ItemSum {
    private StringBuilder resultSb;
    private String seperator;
    private List<Order> orders;
    private boolean alwaysNull; // if contains null
    private RowDataComparator rowComparator;
    private Queue<OrderResult> reusltList;
    public ItemFuncGroupConcat(List<Item> selItems, boolean distinct, List<Order> orders, String isSeparator,
                               boolean isPushDown, List<Field> fields) {
        super(selItems, isPushDown, fields);
        seperator = isSeparator;
        this.resultSb = new StringBuilder();
        this.orders = orders;
        this.alwaysNull = false;
        setDistinct(distinct);
    }

    @Override
    public SumFuncType sumType() {
        return SumFuncType.GROUP_CONCAT_FUNC;
    }

    @Override
    public String funcName() {
        return "GROUP_CONCAT";
    }

    @Override
    public ItemResult resultType() {
        return ItemResult.STRING_RESULT;
    }

    @Override
    public FieldTypes fieldType() {
        return FieldTypes.MYSQL_TYPE_VARCHAR;
    }

    @Override
    public void cleanup() {
        super.cleanup();
    }

    @Override
    public void clear() {
        resultSb.setLength(0);
        if (reusltList != null) {
            reusltList.clear();
        }
        nullValue = true;
    }

    @Override
    public Object getTransAggObj() {
        throw new RuntimeException("Group_concat should not use direct groupby!");
    }

    @Override
    public int getTransSize() {
        throw new RuntimeException("Group_concat should not use direct groupby!");
    }

    @Override
    public boolean add(RowDataPacket row, Object tranObject) {
        if (alwaysNull)
            return false;

        StringBuilder rowStr = new StringBuilder();
        for (int i = 0; i < getArgCount(); i++) {
            Item item = args.get(i);
            String s = item.valStr();
            if (item.isNull())
                return false;
            rowStr.append(s);
        }
        if (orders != null) {
            if (sourceFields != null && rowComparator == null) {
                rowComparator = new RowDataComparator(sourceFields, orders);
                reusltList = new PriorityQueue<>(11, new Comparator<OrderResult>() {
                    @Override
                    public int compare(OrderResult o1, OrderResult o2) {
                        RowDataPacket row1 = o1.row;
                        RowDataPacket row2 = o2.row;
                        if (row1 == null || row2 == null) {
                            if (row1 == row2)
                                return 0;
                            if (row1 == null)
                                return -1;
                            return 1;
                        }
                        return rowComparator.compare(row1, row2);
                    }
                });
            }
            reusltList.add(new OrderResult(rowStr.toString(), row));
        } else {
            if (resultSb.length() > 0)
                resultSb.append(seperator);
            resultSb.append(rowStr);
        }
        nullValue = false;
        return false;
    }

    @Override
    public boolean setup() {
        alwaysNull = false;
        for (int i = 0; i < getArgCount(); i++) {
            Item item = args.get(i);
            if (item.canValued()) {
                if (item.isNull()) {
                    alwaysNull = true;
                    return false;
                }
            }
        }
        return false;
    }

    @Override
    public boolean fixFields() {
        super.fixFields();
        nullValue = true;
        fixed = true;
        return false;
    }

    @Override
    public String valStr() {
        if (aggr != null)
            aggr.endup();
        if (nullValue)
            return null;
        if (orders != null) {
            for (OrderResult orderResult : reusltList) {
                if (resultSb.length() > 0)
                    resultSb.append(seperator);
                resultSb.append(orderResult.result);
            }
        }
        return resultSb.toString();
    }

    @Override
    public BigDecimal valReal() {
        String res = valStr();
        if (res == null)
            return BigDecimal.ZERO;
        else
            try {
                return new BigDecimal(res);
            } catch (Exception e) {
                LOGGER.info("group_concat val_real() convert exception, string value is: " + res);
                return BigDecimal.ZERO;
            }
    }

    @Override
    public BigInteger valInt() {
        String res = valStr();
        if (res == null)
            return BigInteger.ZERO;
        else
            try {
                return new BigInteger(res);
            } catch (Exception e) {
                LOGGER.info("group_concat val_int() convert exception, string value is: " + res);
                return BigInteger.ZERO;
            }
    }

    @Override
    public BigDecimal valDecimal() {
        return valDecimalFromString();
    }

    @Override
    public boolean getDate(MySQLTime ltime, long fuzzydate) {
        return getDateFromString(ltime, fuzzydate);
    }

    @Override
    public boolean getTime(MySQLTime ltime) {
        return getTimeFromString(ltime);
    }

    @Override
    public boolean pushDownAdd(RowDataPacket row) {
        throw new RuntimeException("not implement");
    }

    @Override
    public SQLExpr toExpression() {
        SQLAggregateExpr aggregate = new SQLAggregateExpr(funcName());
        if (hasWithDistinct()) {
            aggregate.setOption(SQLAggregateOption.DISTINCT);
        }
        if (orders != null) {
            SQLOrderBy orderBy = new SQLOrderBy();
            for (Order order : orders) {
                SQLSelectOrderByItem orderItem = new SQLSelectOrderByItem(order.getItem().toExpression());
                orderItem.setType(order.getSortOrder());
                orderBy.addItem(orderItem);
            }
            aggregate.putAttribute(ItemFuncKeyWord.ORDER_BY, orderBy);
        }
        for (Item arg : args) {
            aggregate.addArgument(arg.toExpression());
        }
        if (seperator != null) {
            SQLCharExpr sep = new SQLCharExpr(seperator);
            aggregate.putAttribute(ItemFuncKeyWord.SEPARATOR, sep);
        }
        return aggregate;
    }

    @Override
    protected Item cloneStruct(boolean forCalculate, List<Item> calArgs, boolean isPushDown, List<Field> fields) {
        if (!forCalculate) {
            List<Item> argList = cloneStructList(args);
            return new ItemFuncGroupConcat(argList, hasWithDistinct(), this.orders, this.seperator,
                    false, null);
        } else {
            return new ItemFuncGroupConcat(calArgs, hasWithDistinct(), this.orders, this.seperator, isPushDown,
                    fields);
        }
    }

    public final void fixOrders(NameResolutionContext context) {
        if (orders == null) return;
        for (Order order : orders) {
            Item arg = order.getItem();
            Item fixedArg = arg.fixFields(context);
            if (fixedArg == null)
                return ;
            order.setItem(fixedArg);
            getReferTables().addAll(fixedArg.getReferTables());
        }
    }
    public List<Order> getOrders() {
        return orders;
    }

    private static class OrderResult {
        private String result;
        private RowDataPacket row;

        OrderResult(String result, RowDataPacket row) {
            this.result = result;
            this.row = row;

        }
    }
}