/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
import org.apache.drill.exec.vector.UntypedNullHolder;
import org.apache.drill.exec.vector.UntypedNullVector;

<@pp.dropOutputFile />
<@pp.changeOutputFile name="/org/apache/drill/exec/expr/BasicTypeHelper.java" />

<#include "/@includes/license.ftl" />

package org.apache.drill.exec.expr;

<#include "/@includes/vv_imports.ftl" />
import org.apache.drill.exec.vector.complex.UnionVector;
import org.apache.drill.common.types.TypeProtos.DataMode;
import org.apache.drill.common.types.TypeProtos.MinorType;
import org.apache.drill.common.types.TypeProtos.MajorType;
import org.apache.drill.exec.record.MaterializedField;
import org.apache.drill.exec.vector.complex.RepeatedMapVector;
import org.apache.drill.exec.util.CallBack;
import org.apache.drill.common.types.Types;
/*
 * This class is generated using freemarker and the ${.template_name} template.
 */
public class BasicTypeHelper {
  static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(BasicTypeHelper.class);

  private static final int WIDTH_ESTIMATE = 50;

  protected static String buildErrorMessage(final String operation, final MinorType type, final DataMode mode) {
    return String.format("Unable to %s for minor type [%s] and mode [%s]", operation, type, mode);
  }

  protected static String buildErrorMessage(final String operation, final MajorType type) {
    return buildErrorMessage(operation, type.getMinorType(), type.getMode());
  }

  public static int getSize(MajorType major) {
    switch (major.getMinorType()) {
<#list vv.types as type>
  <#list type.minor as minor>
    case ${minor.class?upper_case}:
      return ${type.width}<#if minor.class?substring(0, 3) == "Var" ||
                               minor.class?substring(0, 3) == "PRO" ||
                               minor.class?substring(0, 3) == "MSG"> + WIDTH_ESTIMATE</#if>;
  </#list>
</#list>
      case FIXEDCHAR: return major.getPrecision();
      case FIXED16CHAR: return major.getPrecision();
      case FIXEDBINARY: return major.getPrecision();
    }
    throw new UnsupportedOperationException(buildErrorMessage("get size", major));
  }

  public static ValueVector getNewVector(String name, BufferAllocator allocator, MajorType type, CallBack callback){
    MaterializedField field = MaterializedField.create(name, type);
    return getNewVector(field, allocator, callback);
  }
  
  
  public static Class<? extends ValueVector> getValueVectorClass(MinorType type, DataMode mode){
    switch (type) {
    case UNION:
      return UnionVector.class;
    case MAP:
      switch (mode) {
      case OPTIONAL:
      case REQUIRED:
        return MapVector.class;
      case REPEATED:
        return RepeatedMapVector.class;
      }
      
    case LIST:
      switch (mode) {
      case REPEATED:
        return RepeatedListVector.class;
      case REQUIRED:
      case OPTIONAL:
        return ListVector.class;
      }
    
<#list vv.types as type>
  <#list type.minor as minor>
      case ${minor.class?upper_case}:
        switch (mode) {
          case REQUIRED:
            return ${minor.class}Vector.class;
          case OPTIONAL:
            return Nullable${minor.class}Vector.class;
          case REPEATED:
            return Repeated${minor.class}Vector.class;
        }
  </#list>
</#list>
    case GENERIC_OBJECT      :
      return ObjectVector.class  ;
    case NULL:
      return UntypedNullVector.class;
    default:
      break;
    }
    throw new UnsupportedOperationException(buildErrorMessage("get value vector class", type, mode));
  }
  public static Class<?> getReaderClassName( MinorType type, DataMode mode, boolean isSingularRepeated){
    switch (type) {
    case MAP:
      switch (mode) {
      case REQUIRED:
        if (!isSingularRepeated)
          return SingleMapReaderImpl.class;
        else
          return SingleLikeRepeatedMapReaderImpl.class;
      case REPEATED: 
          return RepeatedMapReaderImpl.class;
      }
    case LIST:
      switch (mode) {
      case REQUIRED:
        return SingleListReaderImpl.class;
      case REPEATED:
        return RepeatedListReaderImpl.class;
      }
      
<#list vv.types as type>
  <#list type.minor as minor>
      case ${minor.class?upper_case}:
        switch (mode) {
          case REQUIRED:
            return ${minor.class}ReaderImpl.class;
          case OPTIONAL:
            return Nullable${minor.class}ReaderImpl.class;
          case REPEATED:
            return Repeated${minor.class}ReaderImpl.class;
        }
  </#list>
</#list>
      default:
        break;
      }
      throw new UnsupportedOperationException(buildErrorMessage("get reader class name", type, mode));
  }
  
  public static Class<?> getWriterInterface( MinorType type, DataMode mode){
    switch (type) {
    case UNION: return UnionWriter.class;
    case MAP: return MapWriter.class;
    case LIST: return ListWriter.class;
<#list vv.types as type>
  <#list type.minor as minor>
      case ${minor.class?upper_case}: return ${minor.class}Writer.class;
  </#list>
</#list>
      default:
        break;
      }
      throw new UnsupportedOperationException(buildErrorMessage("get writer interface", type, mode));
  }
  
  public static Class<?> getWriterImpl( MinorType type, DataMode mode){
    switch (type) {
    case UNION:
      return UnionWriter.class;
    case MAP:
      switch (mode) {
      case REQUIRED:
      case OPTIONAL:
        return SingleMapWriter.class;
      case REPEATED:
        return RepeatedMapWriter.class;
      }
    case LIST:
      switch (mode) {
      case REQUIRED:
      case OPTIONAL:
        return UnionListWriter.class;
      case REPEATED:
        return RepeatedListWriter.class;
      }
      
<#list vv.types as type>
  <#list type.minor as minor>
      case ${minor.class?upper_case}:
        switch (mode) {
          case REQUIRED:
            return ${minor.class}WriterImpl.class;
          case OPTIONAL:
            return Nullable${minor.class}WriterImpl.class;
          case REPEATED:
            return Repeated${minor.class}WriterImpl.class;
        }
  </#list>
</#list>
      default:
        break;
      }
      throw new UnsupportedOperationException(buildErrorMessage("get writer implementation", type, mode));
  }

  public static Class<?> getHolderReaderImpl( MinorType type, DataMode mode){
    switch (type) {      
<#list vv.types as type>
  <#list type.minor as minor>
      case ${minor.class?upper_case}:
        switch (mode) {
          case REQUIRED:
            return ${minor.class}HolderReaderImpl.class;
          case OPTIONAL:
            return Nullable${minor.class}HolderReaderImpl.class;
          case REPEATED:
            return Repeated${minor.class}HolderReaderImpl.class;
        }
  </#list>
</#list>
      default:
        break;
      }
      throw new UnsupportedOperationException(buildErrorMessage("get holder reader implementation", type, mode));
  }
  
  public static ValueVector getNewVector(MaterializedField field, BufferAllocator allocator){
    return getNewVector(field, allocator, null);
  }
  public static ValueVector getNewVector(MaterializedField field, BufferAllocator allocator, CallBack callBack){
    MajorType type = field.getType();

    switch (type.getMinorType()) {
    
    case UNION:
      return new UnionVector(field, allocator, callBack);

    case MAP:
      switch (type.getMode()) {
      case REQUIRED:
      case OPTIONAL:
        return new MapVector(field, allocator, callBack);
      case REPEATED:
        return new RepeatedMapVector(field, allocator, callBack);
      }
    case LIST:
      switch (type.getMode()) {
      case REPEATED:
        return new RepeatedListVector(field, allocator, callBack);
      case OPTIONAL:
      case REQUIRED:
        return new ListVector(field, allocator, callBack);
      }
<#list vv.  types as type>
  <#list type.minor as minor>
    case ${minor.class?upper_case}:
      switch (type.getMode()) {
        case REQUIRED:
          return new ${minor.class}Vector(field, allocator);
        case OPTIONAL:
          return new Nullable${minor.class}Vector(field, allocator);
        case REPEATED:
          return new Repeated${minor.class}Vector(field, allocator);
      }
  </#list>
</#list>
    case GENERIC_OBJECT:
      return new ObjectVector(field, allocator)        ;
    case NULL:
      return new UntypedNullVector(field, allocator);
    default:
      break;
    }
    // All ValueVector types have been handled.
    throw new UnsupportedOperationException(buildErrorMessage("get new vector", type));
  }

  public static ValueHolder getValue(ValueVector vector, int index) {
    MajorType type = vector.getField().getType();
    ValueHolder holder;
    switch(type.getMinorType()) {
<#list vv.types as type>
  <#list type.minor as minor>
    case ${minor.class?upper_case} :
      <#if minor.class?starts_with("Var") || minor.class == "IntervalDay" || minor.class == "Interval" ||
        minor.class?starts_with("Decimal28") ||  minor.class?starts_with("Decimal38")>
        switch (type.getMode()) {
          case REQUIRED:
            holder = new ${minor.class}Holder();
            ((${minor.class}Vector) vector).getAccessor().get(index, (${minor.class}Holder)holder);
            return holder;
          case OPTIONAL:
            holder = new Nullable${minor.class}Holder();
            ((Nullable${minor.class}Holder)holder).isSet = ((Nullable${minor.class}Vector) vector).getAccessor().isSet(index);
            if (((Nullable${minor.class}Holder)holder).isSet == 1) {
              ((Nullable${minor.class}Vector) vector).getAccessor().get(index, (Nullable${minor.class}Holder)holder);
            }
            return holder;
        }
      <#else>
      switch (type.getMode()) {
        case REQUIRED:
          holder = new ${minor.class}Holder();
          ((${minor.class}Holder)holder).value = ((${minor.class}Vector) vector).getAccessor().get(index);
          return holder;
        case OPTIONAL:
          holder = new Nullable${minor.class}Holder();
          ((Nullable${minor.class}Holder)holder).isSet = ((Nullable${minor.class}Vector) vector).getAccessor().isSet(index);
          if (((Nullable${minor.class}Holder)holder).isSet == 1) {
            ((Nullable${minor.class}Holder)holder).value = ((Nullable${minor.class}Vector) vector).getAccessor().get(index);
          }
          return holder;
      }
      </#if>
  </#list>
</#list>
    case GENERIC_OBJECT:
      holder = new ObjectHolder();
      ((ObjectHolder)holder).obj = ((ObjectVector) vector).getAccessor().getObject(index)         ;
      break;
    }

    throw new UnsupportedOperationException(buildErrorMessage("get value", type));
  }

  public static void setValue(ValueVector vector, int index, ValueHolder holder) {
    MajorType type = vector.getField().getType();

    switch(type.getMinorType()) {
<#list vv.types as type>
  <#list type.minor as minor>
    case ${minor.class?upper_case} :
      switch (type.getMode()) {
        case REQUIRED:
          ((${minor.class}Vector) vector).getMutator().setSafe(index, (${minor.class}Holder) holder);
          return;
        case OPTIONAL:
          if (((Nullable${minor.class}Holder) holder).isSet == 1) {
            ((Nullable${minor.class}Vector) vector).getMutator().setSafe(index, (Nullable${minor.class}Holder) holder);
          }
          return;
      }
  </#list>
</#list>
    case GENERIC_OBJECT:
      ((ObjectVector) vector).getMutator().setSafe(index, (ObjectHolder) holder);
      return;
    case NULL:
      ((UntypedNullVector) vector).getMutator().setSafe(index, (UntypedNullHolder) holder);
    default:
      throw new UnsupportedOperationException(buildErrorMessage("set value", type));
    }
  }

  public static void setValueSafe(ValueVector vector, int index, ValueHolder holder) {
    MajorType type = vector.getField().getType();

    switch(type.getMinorType()) {
      <#list vv.types as type>
      <#list type.minor as minor>
      case ${minor.class?upper_case} :
      switch (type.getMode()) {
        case REQUIRED:
          ((${minor.class}Vector) vector).getMutator().setSafe(index, (${minor.class}Holder) holder);
          return;
        case OPTIONAL:
          if (((Nullable${minor.class}Holder) holder).isSet == 1) {
            ((Nullable${minor.class}Vector) vector).getMutator().setSafe(index, (Nullable${minor.class}Holder) holder);
          } else {
            ((Nullable${minor.class}Vector) vector).getMutator().isSafe(index);
          }
          return;
      }
      </#list>
      </#list>
      case GENERIC_OBJECT:
        ((ObjectVector) vector).getMutator().setSafe(index, (ObjectHolder) holder);
    case NULL:
      ((UntypedNullVector) vector).getMutator().setSafe(index, (UntypedNullHolder) holder);
    default:
        throw new UnsupportedOperationException(buildErrorMessage("set value safe", type));
    }
  }

  public static boolean compareValues(ValueVector v1, int v1index, ValueVector v2, int v2index) {
    MajorType type1 = v1.getField().getType();
    MajorType type2 = v2.getField().getType();

    if (type1.getMinorType() != type2.getMinorType()
        || type1.getScale() != type1.getScale()
        || type1.getPrecision() != type1.getPrecision()) {
      return false;
    }

    switch(type1.getMinorType()) {
<#list vv.types as type>
  <#list type.minor as minor>
    case ${minor.class?upper_case} :
      if ( ((${minor.class}Vector) v1).getAccessor().get(v1index) == 
           ((${minor.class}Vector) v2).getAccessor().get(v2index) ) 
        return true;
      break;
  </#list>
</#list>
    default:
      break;
    }
    return false;
  }

  /**
   *  Create a ValueHolder of MajorType.
   * @param type
   * @return
   */
  public static ValueHolder createValueHolder(MajorType type) {
    switch(type.getMinorType()) {
      <#list vv.types as type>
      <#list type.minor as minor>
      case ${minor.class?upper_case} :

        switch (type.getMode()) {
          case REQUIRED:
            return new ${minor.class}Holder();
          case OPTIONAL:
            return new Nullable${minor.class}Holder();
          case REPEATED:
            return new Repeated${minor.class}Holder();
        }
      </#list>
      </#list>
      case GENERIC_OBJECT:
        return new ObjectHolder();
    case NULL:
        return new UntypedNullHolder();
      default:
        throw new UnsupportedOperationException(buildErrorMessage("create value holder", type));
    }
  }

  public static boolean isNull(ValueHolder holder) {
    MajorType type = getValueHolderType(holder);

    switch(type.getMinorType()) {
      <#list vv.types as type>
      <#list type.minor as minor>
      case ${minor.class?upper_case} :

      switch (type.getMode()) {
        case REQUIRED:
          return true;
        case OPTIONAL:
          return ((Nullable${minor.class}Holder) holder).isSet == 0;
        case REPEATED:
          return true;
      }
      </#list>
      </#list>
    case NULL:
      return true;
      default:
        throw new UnsupportedOperationException(buildErrorMessage("check is null", type));
    }
  }

  public static ValueHolder deNullify(ValueHolder holder) {
    MajorType type = getValueHolderType(holder);

    switch(type.getMinorType()) {
      <#list vv.types as type>
      <#list type.minor as minor>
      case ${minor.class?upper_case} :

        switch (type.getMode()) {
          case REQUIRED:
            return holder;
          case OPTIONAL:
            if( ((Nullable${minor.class}Holder) holder).isSet == 1) {
              ${minor.class}Holder newHolder = new ${minor.class}Holder();

              <#assign fields = minor.fields!type.fields />
              <#list fields as field>
              newHolder.${field.name} = ((Nullable${minor.class}Holder) holder).${field.name};
              </#list>

              return newHolder;
            } else {
              throw new UnsupportedOperationException("You can not convert a null value into a non-null value!");
            }
          case REPEATED:
            return holder;
        }
      </#list>
      </#list>
      default:
        throw new UnsupportedOperationException(buildErrorMessage("deNullify", type));
    }
  }

  public static ValueHolder nullify(ValueHolder holder) {
    MajorType type = getValueHolderType(holder);

    switch(type.getMinorType()) {
      <#list vv.types as type>
      <#list type.minor as minor>
      case ${minor.class?upper_case} :
        switch (type.getMode()) {
          case REQUIRED:
            Nullable${minor.class}Holder newHolder = new Nullable${minor.class}Holder();
            newHolder.isSet = 1;
            <#assign fields = minor.fields!type.fields />
            <#list fields as field>
            newHolder.${field.name} = ((${minor.class}Holder) holder).${field.name};
            </#list>
            return newHolder;
          case OPTIONAL:
            return holder;
          case REPEATED:
            throw new UnsupportedOperationException("You can not convert repeated type " + type + " to nullable type!");
        }
      </#list>
      </#list>
      default:
        throw new UnsupportedOperationException(buildErrorMessage("nullify", type));
    }
  }

  public static MajorType getValueHolderType(ValueHolder holder) {

    if (0 == 1) {
      return null;
    }
    <#list vv.types as type>
    <#list type.minor as minor>
    <#if minor.class.contains("Decimal")>
      else if (holder instanceof ${minor.class}Holder) {
        return  getType((${minor.class}Holder) holder);
      } else if (holder instanceof Nullable${minor.class}Holder) {
        return  getType((Nullable${minor.class}Holder) holder);
      }
    <#else>
      else if (holder instanceof ${minor.class}Holder) {
        return ((${minor.class}Holder) holder).TYPE;
      } else if (holder instanceof Nullable${minor.class}Holder) {
      return ((Nullable${minor.class}Holder) holder).TYPE;
      }
    </#if>
    </#list>
    </#list>
    else if (holder instanceof UntypedNullHolder) {
      return UntypedNullHolder.TYPE;
    }
    throw new UnsupportedOperationException("ValueHolder is not supported for 'getValueHolderType' method.");

  }

  <#list vv.types as type>
  <#list type.minor as minor>
  <#if minor.class.contains("Decimal")>
  <#list ["Nullable", "", "Repeated"] as dataMode>
  public static MajorType getType(${dataMode}${minor.class}Holder holder) {
    return MajorType.newBuilder()
        .setMinorType(MinorType.${minor.class?upper_case})
      <#if dataMode == "Nullable">
        .setMode(DataMode.OPTIONAL)
        .setScale(holder.scale)
        .setPrecision(holder.precision)
      <#elseif dataMode == "Repeated">
        .setMode(DataMode.REPEATED)
        .setScale(holder.vector.getField().getScale())
        .setPrecision(holder.vector.getField().getPrecision())
      <#else>
        .setMode(DataMode.REQUIRED)
        .setScale(holder.scale)
        .setPrecision(holder.precision)
      </#if>
        .build();
  }
  </#list>
  </#if>
  </#list>
  </#list>

}
