package org.infinispan.objectfilter.impl.syntax;

import java.text.ParseException;
import java.time.Instant;
import java.util.Date;
import java.util.Map;

import org.infinispan.objectfilter.impl.util.DateHelper;

/**
 * A constant comparable value, to be used as right or left side in a comparison expression.
 *
 * @author anistor@redhat.com
 * @since 7.0
 */
public final class ConstantValueExpr implements ValueExpr {

   public static final class ParamPlaceholder implements Comparable {

      private final String name;

      public ParamPlaceholder(String name) {
         if (name == null) {
            throw new IllegalArgumentException("name cannot be null");
         }
         this.name = name;
      }

      public String getName() {
         return name;
      }

      @Override
      public String toString() {
         return ":" + getName();
      }

      @Override
      public int compareTo(Object o) {
         return name.compareTo(((ParamPlaceholder) o).getName());
      }

      @Override
      public int hashCode() {
         return name.hashCode();
      }

      @Override
      public boolean equals(Object obj) {
         return obj == this || obj != null && obj.getClass() == ParamPlaceholder.class && name.equals(((ParamPlaceholder) obj).getName());
      }
   }

   private final Comparable constantValue;

   public ConstantValueExpr(Comparable constantValue) {
      if (constantValue == null) {
         throw new IllegalArgumentException("constantValue cannot be null");
      }
      this.constantValue = constantValue;
   }

   public Comparable getConstantValue() {
      if (isParameter()) {
         throw new IllegalStateException("The value is a parameter " + constantValue);
      }
      return constantValue;
   }

   public boolean isParameter() {
      return constantValue instanceof ParamPlaceholder;
   }

   public Comparable getConstantValueAs(Class<?> targetType, Map<String, Object> namedParameters) {
      Comparable value;
      if (constantValue instanceof ParamPlaceholder) {
         String paramName = ((ParamPlaceholder) constantValue).getName();
         if (namedParameters == null) {
            throw new IllegalStateException("Missing value for parameter " + paramName);
         }
         value = (Comparable) namedParameters.get(paramName);
         if (value == null) {
            throw new IllegalStateException("Missing value for parameter " + paramName);
         }
      } else {
         value = constantValue;
      }
      Class<?> type = value.getClass();
      if (type != targetType) {
         if (targetType == String.class) {
            return value.toString();
         } else if (targetType == Boolean.class) {
            if (type == String.class) {
               return Boolean.valueOf((String) value);
            }
         } else if (targetType == Double.class) {
            if (type == String.class) {
               return Double.valueOf((String) value);
            } else if (Number.class.isAssignableFrom(type)) {
               return ((Number) value).doubleValue();
            }
         } else if (targetType == Float.class) {
            if (type == String.class) {
               return Float.valueOf((String) value);
            } else if (Number.class.isAssignableFrom(type)) {
               return ((Number) value).floatValue();
            }
         } else if (targetType == Long.class) {
            if (type == String.class) {
               return Long.valueOf((String) value);
            } else if (Number.class.isAssignableFrom(type)) {
               return ((Number) value).longValue();
            }
         } else if (targetType == Integer.class) {
            if (type == String.class) {
               return Integer.valueOf((String) value);
            } else if (Number.class.isAssignableFrom(type)) {
               return ((Number) value).intValue();
            }
         } else if (targetType == Short.class) {
            if (type == String.class) {
               return Short.valueOf((String) value);
            } else if (Number.class.isAssignableFrom(type)) {
               return ((Number) value).shortValue();
            }
         } else if (targetType == Byte.class) {
            if (type == String.class) {
               return Byte.valueOf((String) value);
            } else if (Number.class.isAssignableFrom(type)) {
               return ((Number) value).byteValue();
            }
         } else if (targetType == Date.class) {
            if (type == String.class) {
               try {
                  return DateHelper.getJpaDateFormat().parse((String) value);
               } catch (ParseException e) {
                  throw new RuntimeException(e);
               }
            } else if (Number.class.isAssignableFrom(type)) {
               return new Date(((Number) value).longValue());
            }
         } else if (targetType == Instant.class) {
            if (type == String.class) {
               return Instant.parse((String) value);
            } else if (Number.class.isAssignableFrom(type)) {
               return Instant.ofEpochMilli(((Number) value).longValue());
            }
         }

         //todo [anistor] continue with other commonsense conversions

         //todo [anistor] report illegal conversion
      }
      return value;
   }

   @Override
   public <T> T acceptVisitor(Visitor<?, ?> visitor) {
      return (T) visitor.visit(this);
   }

   @Override
   public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      ConstantValueExpr other = (ConstantValueExpr) o;
      return constantValue.equals(other.constantValue);
   }

   @Override
   public int hashCode() {
      return constantValue.hashCode();
   }

   @Override
   public String toString() {
      String strVal;
      if (constantValue instanceof ParamPlaceholder) {
         strVal = constantValue.toString();
      } else if (constantValue instanceof String) {
         strVal = "\"" + constantValue + "\"";
      } else if (constantValue instanceof Character) {
         strVal = "'" + constantValue + "'";
      } else if (constantValue instanceof Date) {
         strVal = DateHelper.getJpaDateFormat().format((Date) constantValue);
      } else if (constantValue instanceof Instant) {
         strVal = constantValue.toString();
      } else {
         strVal = String.valueOf(constantValue);
      }
      return "CONST(" + strVal + ')';
   }

   @Override
   public void appendQueryString(StringBuilder sb) {
      if (constantValue instanceof ParamPlaceholder) {
         sb.append(constantValue);
      } else if (constantValue instanceof String) {
         sb.append("\"").append(constantValue).append("\"");
      } else if (constantValue instanceof Character) {
         sb.append("'").append(constantValue).append("'");
      } else if (constantValue instanceof Date) {
         sb.append("'").append(DateHelper.getJpaDateFormat().format((Date) constantValue)).append("'");
      } else if (constantValue instanceof Instant) {
         sb.append("'").append(constantValue).append("'");
      } else {
         sb.append(constantValue);
      }
   }
}
