/*
 * 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.
 */
package org.apache.hadoop.hive.ql.udf.generic;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;

import org.apache.hadoop.hive.ql.exec.FunctionRegistry;
import org.apache.hadoop.hive.ql.exec.UDAF;
import org.apache.hadoop.hive.ql.exec.UDAFEvaluator;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.parse.SemanticException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDFUtils.ConversionHelper;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.ObjectInspectorOptions;
import org.apache.hadoop.hive.serde2.typeinfo.TypeInfo;
import org.apache.hadoop.util.ReflectionUtils;

/**
 * This class is a bridge between GenericUDAF and UDAF. Old UDAF can be used
 * with the GenericUDAF infrastructure through this bridge.
 */
public class GenericUDAFBridge extends AbstractGenericUDAFResolver {

  UDAF udaf;

  public GenericUDAFBridge(UDAF udaf) {
    this.udaf = udaf;
  }

  public Class<? extends UDAF> getUDAFClass() {
    return udaf.getClass();
  }

  @Override
  public GenericUDAFEvaluator getEvaluator(TypeInfo[] parameters) throws SemanticException {

    Class<? extends UDAFEvaluator> udafEvaluatorClass = udaf.getResolver()
        .getEvaluatorClass(Arrays.asList(parameters));

    return new GenericUDAFBridgeEvaluator(udafEvaluatorClass);
  }

  /**
   * GenericUDAFBridgeEvaluator.
   *
   */
  public static class GenericUDAFBridgeEvaluator extends GenericUDAFEvaluator
      implements Serializable {

    private static final long serialVersionUID = 1L;

    // Used by serialization only
    public GenericUDAFBridgeEvaluator() {
    }

    public Class<? extends UDAFEvaluator> getUdafEvaluator() {
      return udafEvaluator;
    }

    public void setUdafEvaluator(Class<? extends UDAFEvaluator> udafEvaluator) {
      this.udafEvaluator = udafEvaluator;
    }

    public GenericUDAFBridgeEvaluator(
        Class<? extends UDAFEvaluator> udafEvaluator) {
      this.udafEvaluator = udafEvaluator;
    }

    Class<? extends UDAFEvaluator> udafEvaluator;

    transient ObjectInspector[] parameterOIs;
    transient Object result;

    transient Method iterateMethod;
    transient Method mergeMethod;
    transient Method terminatePartialMethod;
    transient Method terminateMethod;

    transient ConversionHelper conversionHelper;

    @Override
    public ObjectInspector init(Mode m, ObjectInspector[] parameters) throws HiveException {
      super.init(m, parameters);
      parameterOIs = parameters;

      // Get the reflection methods from ue
      for (Method method : udafEvaluator.getMethods()) {
        method.setAccessible(true);
        if (method.getName().equals("iterate")) {
          iterateMethod = method;
        }
        if (method.getName().equals("merge")) {
          mergeMethod = method;
        }
        if (method.getName().equals("terminatePartial")) {
          terminatePartialMethod = method;
        }
        if (method.getName().equals("terminate")) {
          terminateMethod = method;
        }
      }

      // Input: do Java/Writable conversion if needed
      Method aggregateMethod = null;
      if (mode == Mode.PARTIAL1 || mode == Mode.COMPLETE) {
        aggregateMethod = iterateMethod;
      } else {
        aggregateMethod = mergeMethod;
      }
      conversionHelper = new ConversionHelper(aggregateMethod, parameters);

      // Output: get the evaluate method
      Method evaluateMethod = null;
      if (mode == Mode.PARTIAL1 || mode == Mode.PARTIAL2) {
        evaluateMethod = terminatePartialMethod;
      } else {
        evaluateMethod = terminateMethod;
      }
      // Get the output ObjectInspector from the return type.
      Type returnType = evaluateMethod.getGenericReturnType();
      try {
        return ObjectInspectorFactory.getReflectionObjectInspector(returnType,
            ObjectInspectorOptions.JAVA);
      } catch (RuntimeException e) {
        throw new HiveException("Cannot recognize return type " + returnType
            + " from " + evaluateMethod, e);
      }
    }

    /** class for storing UDAFEvaluator value. */
    static class UDAFAgg extends AbstractAggregationBuffer {
      UDAFEvaluator ueObject;

      UDAFAgg(UDAFEvaluator ueObject) {
        this.ueObject = ueObject;
      }
    }

    @Override
    public AggregationBuffer getNewAggregationBuffer() {
      return new UDAFAgg((UDAFEvaluator)ReflectionUtils.newInstance(udafEvaluator, null));
    }

    @Override
    public void reset(AggregationBuffer agg) throws HiveException {
      ((UDAFAgg) agg).ueObject.init();
    }

    @Override
    public void iterate(AggregationBuffer agg, Object[] parameters) throws HiveException {
      FunctionRegistry.invoke(iterateMethod, ((UDAFAgg) agg).ueObject,
          conversionHelper.convertIfNecessary(parameters));
    }

    @Override
    public void merge(AggregationBuffer agg, Object partial) throws HiveException {
      FunctionRegistry.invoke(mergeMethod, ((UDAFAgg) agg).ueObject,
          conversionHelper.convertIfNecessary(partial));
    }

    @Override
    public Object terminate(AggregationBuffer agg) throws HiveException {
      return FunctionRegistry.invoke(terminateMethod, ((UDAFAgg) agg).ueObject);
    }

    @Override
    public Object terminatePartial(AggregationBuffer agg) throws HiveException {
      return FunctionRegistry.invoke(terminatePartialMethod,
          ((UDAFAgg) agg).ueObject);
    }

  }

}
