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

/**
 *
 */
package com.actiontech.dble.plan.common.item.function.timefunc;

import com.actiontech.dble.plan.common.field.Field;
import com.actiontech.dble.plan.common.item.Item;
import com.actiontech.dble.plan.common.item.function.primary.ItemIntFunc;
import com.actiontech.dble.plan.common.ptr.LongPtr;
import com.actiontech.dble.plan.common.time.MySQLTime;
import com.actiontech.dble.plan.common.time.MyTime;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.expr.SQLExtractExpr;
import com.alibaba.druid.sql.ast.expr.SQLIntervalUnit;

import java.math.BigInteger;
import java.util.List;

public class ItemExtract extends ItemIntFunc {
    private SQLIntervalUnit intType;
    private boolean dateValue;

    public ItemExtract(Item a, SQLIntervalUnit intType, int charsetIndex) {
        super(a, charsetIndex);
        this.intType = intType;
    }

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

    @Override
    public Functype functype() {
        return Functype.EXTRACT_FUNC;
    }

    @Override
    public void fixLengthAndDec() {
        maybeNull = true; // If wrong date
        switch (intType) {
            case YEAR:
                maxLength = 4;
                dateValue = true;
                break;
            case YEAR_MONTH:
                maxLength = 6;
                dateValue = true;
                break;
            case QUARTER:
                maxLength = 2;
                dateValue = true;
                break;
            case MONTH:
                maxLength = 2;
                dateValue = true;
                break;
            case WEEK:
                maxLength = 2;
                dateValue = true;
                break;
            case DAY:
                maxLength = 2;
                dateValue = true;
                break;
            case DAY_HOUR:
                maxLength = 9;
                dateValue = false;
                break;
            case DAY_MINUTE:
                maxLength = 11;
                dateValue = false;
                break;
            case DAY_SECOND:
                maxLength = 13;
                dateValue = false;
                break;
            case HOUR:
                maxLength = 2;
                dateValue = false;
                break;
            case HOUR_MINUTE:
                maxLength = 4;
                dateValue = false;
                break;
            case HOUR_SECOND:
                maxLength = 6;
                dateValue = false;
                break;
            case MINUTE:
                maxLength = 2;
                dateValue = false;
                break;
            case MINUTE_SECOND:
                maxLength = 4;
                dateValue = false;
                break;
            case SECOND:
                maxLength = 2;
                dateValue = false;
                break;
            case MICROSECOND:
                maxLength = 2;
                dateValue = false;
                break;
            case DAY_MICROSECOND:
                maxLength = 20;
                dateValue = false;
                break;
            case HOUR_MICROSECOND:
                maxLength = 13;
                dateValue = false;
                break;
            case MINUTE_MICROSECOND:
                maxLength = 11;
                dateValue = false;
                break;
            case SECOND_MICROSECOND:
                maxLength = 9;
                dateValue = false;
                break;
            default:
                break;
        }
    }

    @Override
    public BigInteger valInt() {
        MySQLTime ltime = new MySQLTime();
        int weekFormat;
        long neg;
        if (dateValue) {
            if (getArg0Date(ltime, MyTime.TIME_FUZZY_DATE))
                return BigInteger.ZERO;
            neg = 1;
        } else {
            if (getArg0Time(ltime))
                return BigInteger.ZERO;
            neg = ltime.isNeg() ? -1 : 1;
        }
        switch (intType) {
            case YEAR:
                return BigInteger.valueOf(ltime.getYear());
            case YEAR_MONTH:
                return BigInteger.valueOf(ltime.getYear() * 100L + ltime.getMonth());
            case QUARTER:
                return BigInteger.valueOf((ltime.getMonth() + 2) / 3);
            case MONTH:
                return BigInteger.valueOf(ltime.getMonth());
            case WEEK: {
                weekFormat = MyTime.WEEK_MONDAY_FIRST;
                long ret = MyTime.calcWeek(ltime, MyTime.weekMode(weekFormat), new LongPtr(0));
                return BigInteger.valueOf(ret);

            }
            case DAY:
                return BigInteger.valueOf(ltime.getDay());
            case DAY_HOUR:
                return BigInteger.valueOf((ltime.getDay() * 100L + ltime.getHour()) * neg);
            case DAY_MINUTE:
                return (BigInteger.valueOf((ltime.getDay() * 10000L + ltime.getHour() * 100L + ltime.getMinute()) * neg));
            case DAY_SECOND:
                return BigInteger.valueOf(
                        (ltime.getDay() * 1000000L + (ltime.getHour() * 10000L + ltime.getMinute() * 100 + ltime.getSecond())) * neg);
            case HOUR:
                return BigInteger.valueOf(ltime.getHour() * neg);
            case HOUR_MINUTE:
                return BigInteger.valueOf((ltime.getHour() * 100 + ltime.getMinute()) * neg);
            case HOUR_SECOND:
                return BigInteger.valueOf((ltime.getHour() * 10000 + ltime.getMinute() * 100 + ltime.getSecond()) * neg);
            case MINUTE:
                return BigInteger.valueOf(ltime.getMinute() * neg);
            case MINUTE_SECOND:
                return BigInteger.valueOf((ltime.getMinute() * 100 + ltime.getSecond()) * neg);
            case SECOND:
                return BigInteger.valueOf(ltime.getSecond() * neg);
            case MICROSECOND:
                return BigInteger.valueOf(ltime.getSecondPart() * neg);
            case DAY_MICROSECOND:
                return BigInteger.valueOf(
                        ((ltime.getDay() * 1000000L + ltime.getHour() * 10000L + ltime.getMinute() * 100 + ltime.getSecond()) * 1000000L +
                                ltime.getSecondPart()) * neg);
            case HOUR_MICROSECOND:
                return BigInteger.valueOf(
                        ((ltime.getHour() * 10000L + ltime.getMinute() * 100 + ltime.getSecond()) * 1000000L + ltime.getSecondPart()) * neg);
            case MINUTE_MICROSECOND:
                return BigInteger.valueOf((((ltime.getMinute() * 100 + ltime.getSecond())) * 1000000L + ltime.getSecondPart()) * neg);
            case SECOND_MICROSECOND:
                return BigInteger.valueOf((ltime.getSecond() * 1000000L + ltime.getSecondPart()) * neg);
            default:
                break;
        }
        return BigInteger.ZERO; // Impossible
    }

    @Override
    public SQLExpr toExpression() {
        SQLExtractExpr extract = new SQLExtractExpr();
        extract.setValue(args.get(0).toExpression());
        extract.setUnit(intType);
        return extract;
    }

    @Override
    protected Item cloneStruct(boolean forCalculate, List<Item> calArgs, boolean isPushDown, List<Field> fields) {
        List<Item> newArgs = null;
        if (!forCalculate)
            newArgs = cloneStructList(args);
        else
            newArgs = calArgs;
        return new ItemExtract(newArgs.get(0), intType, charsetIndex);
    }
}
