/*
 * 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.visitor.functions;

import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr;
import com.alibaba.druid.sql.visitor.SQLEvalVisitor;
import com.alibaba.druid.sql.visitor.SQLEvalVisitorUtils;
import com.alibaba.druid.util.Utils;

import java.math.BigDecimal;
import java.math.BigInteger;

import static com.alibaba.druid.sql.visitor.SQLEvalVisitor.EVAL_VALUE;
import static com.alibaba.druid.sql.visitor.SQLEvalVisitor.EVAL_VALUE_NULL;

public class OneParamFunctions implements Function {
    public static final OneParamFunctions instance = new OneParamFunctions();

    public Object eval(SQLEvalVisitor visitor, SQLMethodInvokeExpr x) {
        if (x.getArguments().isEmpty()) {
            return SQLEvalVisitor.EVAL_ERROR;
        }

        SQLExpr param = x.getArguments().get(0);
        param.accept(visitor);

        Object paramValue = param.getAttributes().get(EVAL_VALUE);
        if (paramValue == null) {
            return SQLEvalVisitor.EVAL_ERROR;
        }

        if (paramValue == EVAL_VALUE_NULL) {
            return EVAL_VALUE_NULL;
        }

        String method = x.getMethodName();
        if ("md5".equalsIgnoreCase(method)) {
            String text = paramValue.toString();
            return Utils.md5(text);
        }

        if ("bit_count".equalsIgnoreCase(method)) {
            if (paramValue instanceof BigInteger) {
                return ((BigInteger) paramValue).bitCount();
            }

            if (paramValue instanceof BigDecimal) {
                BigDecimal decimal = (BigDecimal) paramValue;
                BigInteger bigInt = decimal.setScale(0, BigDecimal.ROUND_HALF_UP).toBigInteger();
                return bigInt.bitCount();
            }
            Long val = SQLEvalVisitorUtils.castToLong(paramValue);
            return Long.bitCount(val);
        }

        if ("soundex".equalsIgnoreCase(method)) {
            String text = paramValue.toString();
            return soundex(text);
        }

        if ("space".equalsIgnoreCase(method)) {
            int intVal = SQLEvalVisitorUtils.castToInteger(paramValue);
            char[] chars = new char[intVal];
            for (int i = 0; i < chars.length; ++i) {
                chars[i] = ' ';
            }
            return new String(chars);
        }

        throw new UnsupportedOperationException(method);
    }

    public static String soundex(String str) {
        if (str == null) {
            return null;
        }
        str = clean(str);
        if (str.length() == 0) {
            return str;
        }
        char[] out = {'0', '0', '0', '0'};
        char last, mapped;
        int incount = 1, count = 1;
        out[0] = str.charAt(0);
        // getMappingCode() throws IllegalArgumentException
        last = getMappingCode(str, 0);
        while ((incount < str.length()) && (count < out.length)) {
            mapped = getMappingCode(str, incount++);
            if (mapped != 0) {
                if ((mapped != '0') && (mapped != last)) {
                    out[count++] = mapped;
                }
                last = mapped;
            }
        }
        return new String(out);
    }

    static String clean(String str) {
        if (str == null || str.length() == 0) {
            return str;
        }
        int len = str.length();
        char[] chars = new char[len];
        int count = 0;
        for (int i = 0; i < len; i++) {
            if (Character.isLetter(str.charAt(i))) {
                chars[count++] = str.charAt(i);
            }
        }
        if (count == len) {
            return str.toUpperCase(java.util.Locale.ENGLISH);
        }
        return new String(chars, 0, count).toUpperCase(java.util.Locale.ENGLISH);
    }

    private static char getMappingCode(String str, int index) {
        // map() throws IllegalArgumentException
        char mappedChar = map(str.charAt(index));
        // HW rule check
        if (index > 1 && mappedChar != '0') {
            char hwChar = str.charAt(index - 1);
            if ('H' == hwChar || 'W' == hwChar) {
                char preHWChar = str.charAt(index - 2);
                char firstCode = map(preHWChar);
                if (firstCode == mappedChar || 'H' == preHWChar || 'W' == preHWChar) {
                    return 0;
                }
            }
        }
        return mappedChar;
    }

    private static char map(char ch) {
        String soundexMapping = "01230120022455012623010202";
        int index = ch - 'A';
        if (index < 0 || index >= soundexMapping.length()) {
            throw new IllegalArgumentException("The character is not mapped: " + ch);
        }
        return soundexMapping.charAt(index);
    }

}
