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

package com.actiontech.dble.plan.common;

import com.actiontech.dble.plan.common.field.FieldUtil;
import com.actiontech.dble.plan.common.item.FieldTypes;
import com.actiontech.dble.plan.common.item.Item;
import com.actiontech.dble.plan.common.item.Item.ItemResult;
import com.actiontech.dble.plan.common.item.Item.ItemType;
import com.actiontech.dble.plan.common.ptr.BoolPtr;
import com.actiontech.dble.plan.common.time.MySQLTime;
import com.actiontech.dble.plan.common.time.MySQLTimeStatus;
import com.actiontech.dble.plan.common.time.MySQLTimestampType;
import com.actiontech.dble.plan.common.time.MyTime;

import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;

public final class MySQLcom {
    private MySQLcom() {
    }

    public static final double M_PI = Math.PI;
    public static final int DBL_DIG = 6;
    public static final int FLT_DIG = 10;
    public static final int DECIMAL_LONGLONG_DIGITS = 22;

    /**
     * maximum length of buffer in our big digits (uint32).
     */
    public static final int DECIMAL_BUFF_LENGTH = 9;

    /* the number of digits that my_decimal can possibly contain */
    public static final int DECIMAL_MAX_POSSIBLE_PRECISION = (DECIMAL_BUFF_LENGTH * 9);

    /**
     * maximum guaranteed precision of number in decimal digits (number of our
     * digits * number of decimal digits in one our big digit - number of
     * decimal digits in one our big digit decreased by 1 (because we always put
     * decimal point on the border of our big digits))
     */
    public static final int DECIMAL_MAX_PRECISION = (DECIMAL_MAX_POSSIBLE_PRECISION - 8 * 2);

    public static final int DECIMAL_MAX_SCALE = 30;
    public static final int DECIMAL_NOT_SPECIFIED = 31;

    public static final BigInteger BI64BACK = new BigInteger("18446744073709551616");

    public static String setInt(long num, boolean unsignedFlag) {
        return String.valueOf(num);
    }

    public static String setReal(double num, int decimals) {
        BigDecimal bd = new BigDecimal(num);
        bd = bd.setScale(decimals);
        double db = bd.doubleValue();
        return String.valueOf(db);
    }

    public static BigDecimal int2Decimal(long num, boolean unsigned) {
        return BigDecimal.valueOf(num);
    }

    public static BigDecimal double2Decimal(double num, int decimals) {
        BigDecimal bd = new BigDecimal(num);
        return bd.setScale(decimals);
    }

    public static double str2Double(String str) {
        try {
            double db = Double.parseDouble(str);
            return db;
        } catch (Exception e) {
            return 0.0;
        }
    }

    public static long str2Long(String str) {
        try {
            long l = Long.parseLong(str);
            return l;
        } catch (Exception e) {
            return 0;
        }
    }

    public static BigDecimal str2Decimal(String str) {
        try {
            double db = Double.parseDouble(str);
            BigDecimal bd = BigDecimal.valueOf(db);
            return bd;
        } catch (Exception e) {
            return BigDecimal.ZERO;
        }
    }

    /**
     * str to longlong
     *
     * @param cs    str ->char array
     * @param start
     * @param end
     * @param error
     * @return
     */
    public static BigInteger myStrtoll10(char[] cs, int start, int end, BoolPtr error) {
        String tmp = new String(cs, start, end - start);
        error.set(false);
        try {
            return new BigInteger(tmp);
        } catch (Exception e) {
            error.set(true);
            return BigInteger.ZERO;
        }
    }

    /**
     * binary compare, num need to <= b1.size && <= b2.size
     *
     * @param b1
     * @param b2
     * @param num
     * @return
     */
    public static int memcmp(byte[] b1, byte[] b2, int num) {
        for (int i = 0; i < num; i++) {
            if (b1[i] < b2[i]) {
                return -1;
            } else if (b1[i] == b2[i]) {
                continue;
            } else {
                return 1;
            }
        }
        return 0;
    }

    public static int memcmp(byte[] aPtr, byte[] bPtr) {
        int aLen = aPtr.length, bLen = bPtr.length;
        if (aLen >= bLen)
            return memcmp0(aPtr, bPtr);
        else
            return -memcmp(bPtr, aPtr);
    }

    public static BigInteger getUnsignedLong(long l) {
        BigInteger bi = BigInteger.valueOf(l);
        BigInteger bmask = new BigInteger("FFFFFFFFFFFFFFFF", 16);
        return bi.and(bmask);
    }

    /**
     * @return converted value. 0 on error and on zero-dates -- check 'failure'
     * @brief Convert date provided in a string to its packed temporal int
     * representation.
     * @param[in] thd thread handle
     * @param[in] str a string to convert
     * @param[in] warn_type type of the timestamp for issuing the warning
     * @param[in] warn_name field name for issuing the warning
     * @param[out] error_arg could not extract a DATE or DATETIME
     * @details Convert date provided in the string str to the int
     * representation. If the string contains wrong date or doesn't
     * contain it at all then a warning is issued. The warn_type and
     * the warn_name arguments are used as the name and the type of the
     * field when issuing the warning.
     */
    public static long getDateFromStr(String str, MySQLTimestampType warntype, BoolPtr error) {
        MySQLTime ltime = new MySQLTime();
        MySQLTimeStatus status = new MySQLTimeStatus();
        error.set(MyTime.strToDatetime(str, str.length(), ltime, MyTime.TIME_FUZZY_DATE, status));
        if (error.get())
            return 0;
        return MyTime.timeToLonglongDatetimePacked(ltime);

    }
    /*
     * Collects different types for comparison of first item with each other
     * items
     *
     * SYNOPSIS collectCmpTypes() items Array of items to collect types from
     * nitems Number of items in the array skip_nulls Don't collect types of
     * NULL items if TRUE
     *
     * DESCRIPTION This function collects different result types for comparison
     * of the first item in the list with each of the remaining items in the
     * 'items' array.
     *
     * RETURN 0 - if row type incompatibility has been detected (see
     * cmp_row_type) Bitmap of collected types - otherwise show how many types there are
     */

    public static int collectCmpTypes(List<Item> items, boolean skipnulls) {
        int foundtypes = 0;
        ItemResult leftResult = items.get(0).resultType();
        for (int i = 1; i < items.size(); i++) {
            if (skipnulls && items.get(i).type() == Item.ItemType.NULL_ITEM)
                continue;
            if (leftResult == ItemResult.ROW_RESULT || items.get(i).resultType() == ItemResult.ROW_RESULT &&
                    cmpRowType(items.get(0), items.get(i)) != 0)
                return 0;
            foundtypes |= 1 << MySQLcom.itemCmpType(leftResult, items.get(i).resultType()).ordinal();
        }
        /*
         * Even if all right-hand items are NULLs and we are skipping them all,
         * we need at least one type bit in the found_type bitmask.
         */
        if (skipnulls && foundtypes == 0)
            foundtypes |= 1 << leftResult.ordinal();
        return foundtypes;
    }

    public static int cmpRowType(Item item1, Item item2) {
        // TODO
        return 0;
    }

    public static ItemResult itemCmpType(ItemResult a, ItemResult b) {

        if (a == ItemResult.STRING_RESULT && b == ItemResult.STRING_RESULT)
            return ItemResult.STRING_RESULT;
        if (a == ItemResult.INT_RESULT && b == ItemResult.INT_RESULT)
            return ItemResult.INT_RESULT;
        if ((a == ItemResult.INT_RESULT || a == ItemResult.DECIMAL_RESULT) &&
                (b == ItemResult.INT_RESULT || b == ItemResult.DECIMAL_RESULT))
            return ItemResult.DECIMAL_RESULT;
        return ItemResult.REAL_RESULT;
    }

    public static FieldTypes aggFieldType(List<Item> items, int startIndex, int nitems) {
        if (nitems == 0 || items.get(startIndex).resultType() == ItemResult.ROW_RESULT)
            return FieldTypes.valueOf("-1");
        FieldTypes res = items.get(startIndex).fieldType();
        for (int i = 1; i < nitems; i++)
            res = FieldUtil.fieldTypeMerge(res, items.get(startIndex + i).fieldType());
        return res;
    }

    public static ItemResult aggResultType(List<Item> items, int startIndex, int size) {
        ItemResult type = ItemResult.STRING_RESULT;
        /* Skip beginning NULL items */
        int index = 0, indexEnd;
        Item item;
        for (index = startIndex, indexEnd = startIndex + size; index < indexEnd; index++) {
            item = items.get(index);
            if (item.type() != ItemType.NULL_ITEM) {
                type = item.resultType();
                index++;
                break;
            }
        }
        /* Combine result types. Note: NULL items don't affect the result */
        for (; index < indexEnd; index++) {
            item = items.get(index);
            if (item.type() != ItemType.NULL_ITEM)
                type = itemStoreType(type, item);
        }
        return type;
    }

    public static ItemResult itemStoreType(ItemResult a, Item item) {
        ItemResult b = item.resultType();

        if (a == ItemResult.STRING_RESULT || b == ItemResult.STRING_RESULT)
            return ItemResult.STRING_RESULT;
        else if (a == ItemResult.REAL_RESULT || b == ItemResult.REAL_RESULT)
            return ItemResult.REAL_RESULT;
        else if (a == ItemResult.DECIMAL_RESULT || b == ItemResult.DECIMAL_RESULT)
            return ItemResult.DECIMAL_RESULT;
        else
            return ItemResult.INT_RESULT;
    }

    public static byte[] long2Byte(BigInteger bi) {
        long x = -1;
        if (bi.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0)
            x = bi.subtract(BI64BACK).longValue();
        else
            x = bi.longValue();
        int retLen = -1;
        byte[] bb = new byte[8];
        int index = -1;
        bb[++index] = (byte) (x >> 56);
        if (bb[index] != 0)
            retLen = 8;
        bb[++index] = (byte) (x >> 48);
        if (bb[index] != 0 && retLen == -1)
            retLen = 7;
        bb[++index] = (byte) (x >> 40);
        if (bb[index] != 0 && retLen == -1)
            retLen = 6;
        bb[++index] = (byte) (x >> 32);
        if (bb[index] != 0 && retLen == -1)
            retLen = 5;
        bb[++index] = (byte) (x >> 24);
        if (bb[index] != 0 && retLen == -1)
            retLen = 4;
        bb[++index] = (byte) (x >> 16);
        if (bb[index] != 0 && retLen == -1)
            retLen = 3;
        bb[++index] = (byte) (x >> 8);
        if (bb[index] != 0 && retLen == -1)
            retLen = 2;
        bb[++index] = (byte) x;
        if (retLen == -1)
            retLen = 1;
        return Arrays.copyOfRange(bb, bb.length - retLen, bb.length);
    }

    public static void memcpy(byte[] aPtr, int aStart, byte[] bPtr) {
        assert (aPtr.length - aStart + 1 == bPtr.length);
        System.arraycopy(bPtr, 0, aPtr, aStart, bPtr.length);
    }

    /**
     * compare two byte array that the size of a_ptr >=b_ptr
     *
     * @param aPtr
     * @param bPtr
     * @return
     */
    private static int memcmp0(byte[] aPtr, byte[] bPtr) {
        int aLen = aPtr.length, bLen = bPtr.length;
        for (int i = 0; i < aLen - bLen; i++) {
            if (aPtr[i] != 0)
                return 1;
        }
        int aStart = aLen - bLen;
        for (int i = 0; i < bLen; i++) {
            byte aByte = aPtr[aStart + i];
            byte bByte = bPtr[i];
            if (aByte > bByte)
                return 1;
            else if (aByte < bByte)
                return -1;
        }
        return 0;
    }

    /**
     * parser rowpacket, all rowpacket's data is String
     *
     * @param charsetName
     * @param buff
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String getFullString(String charsetName, byte[] buff) throws UnsupportedEncodingException {
        if (buff == null || charsetName == null)
            return null;
        if (Charset.isSupported(charsetName)) {
            return new String(buff, charsetName);
        } else {
            String msg = "unsupported character set :" + charsetName;
            throw new UnsupportedEncodingException(msg);
        }
    }

    public static final long[] LOG_10_INT = new long[]{1, 10, 100, 1000, 10000L, 100000L, 1000000L, 10000000L, 100000000L,
            1000000000L, 10000000000L, 100000000000L, 1000000000000L, 10000000000000L, 100000000000000L,
            1000000000000000L, 10000000000000000L, 100000000000000000L, 1000000000000000000L};

    public static long pow10(int index) {
        return (long) Math.pow(10, index);
    }

    public static final String NULLS = null;

    public static int checkWord(String[] nameArray, char[] cs, int offset, int count) {
        String val = new String(cs, offset, count);
        for (int index = 0; index < nameArray.length; index++) {
            if (val.equalsIgnoreCase(nameArray[index]))
                return index;
        }
        return 0;
    }
}
