package com.chenyang.druid.sql.visitor.functions;

import com.chenyang.druid.sql.ast.SQLExpr;
import com.chenyang.druid.sql.ast.expr.SQLMethodInvokeExpr;
import com.chenyang.druid.sql.visitor.SQLEvalVisitor;
import com.chenyang.druid.sql.visitor.SQLEvalVisitorUtils;
import com.chenyang.druid.util.Utils;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Locale;

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

   public Object eval(SQLEvalVisitor visitor, SQLMethodInvokeExpr x) {
      if (x.getArguments().size() == 0) {
         return SQLEvalVisitor.EVAL_ERROR;
      } else {
         SQLExpr param = (SQLExpr)x.getArguments().get(0);
         param.accept(visitor);
         Object paramValue = param.getAttributes().get("eval.value");
         if (paramValue == null) {
            return SQLEvalVisitor.EVAL_ERROR;
         } else if (paramValue == SQLEvalVisitor.EVAL_VALUE_NULL) {
            return SQLEvalVisitor.EVAL_VALUE_NULL;
         } else {
            String method = x.getMethodName();
            if ("md5".equalsIgnoreCase(method)) {
               String text = paramValue.toString();
               return Utils.md5(text);
            } else if ("bit_count".equalsIgnoreCase(method)) {
               if (paramValue instanceof BigInteger) {
                  return ((BigInteger)paramValue).bitCount();
               } else if (paramValue instanceof BigDecimal) {
                  BigDecimal decimal = (BigDecimal)paramValue;
                  BigInteger bigInt = decimal.setScale(0, 4).toBigInteger();
                  return bigInt.bitCount();
               } else {
                  Long val = SQLEvalVisitorUtils.castToLong(paramValue);
                  return Long.bitCount(val);
               }
            } else if ("soundex".equalsIgnoreCase(method)) {
               String text = paramValue.toString();
               return soundex(text);
            } else if (!"space".equalsIgnoreCase(method)) {
               throw new UnsupportedOperationException(method);
            } else {
               int intVal = SQLEvalVisitorUtils.castToInteger(paramValue);
               char[] chars = new char[intVal];

               for(int i = 0; i < chars.length; ++i) {
                  chars[i] = ' ';
               }

               return new String(chars);
            }
         }
      }
   }

   public static String soundex(String str) {
      if (str == null) {
         return null;
      } else {
         str = clean(str);
         if (str.length() == 0) {
            return str;
         } else {
            char[] out = new char[]{'0', '0', '0', '0'};
            int incount = 1;
            int count = 1;
            out[0] = str.charAt(0);
            char last = getMappingCode(str, 0);

            while(incount < str.length() && count < out.length) {
               char 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) {
         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(Locale.ENGLISH);
         } else {
            return (new String(chars, 0, count)).toUpperCase(Locale.ENGLISH);
         }
      } else {
         return str;
      }
   }

   private static char getMappingCode(String str, int index) {
      char mappedChar = map(str.charAt(index));
      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 '\u0000';
            }
         }
      }

      return mappedChar;
   }

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