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

import org.apache.hadoop.hive.ql.session.SessionState;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFBridge;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFResolver;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDFBridge;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;
import org.apache.hadoop.hive.ql.udf.ptf.TableFunctionResolver;
import org.apache.hadoop.hive.ql.udf.ptf.WindowingTableFunction;
import org.apache.hive.common.util.AnnotationUtils;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * FunctionInfo.
 *
 */
public class FunctionInfo {

  public static enum FunctionType {
    BUILTIN, PERSISTENT, TEMPORARY;
  }

  private final FunctionType functionType;

  private final boolean isInternalTableFunction;

  private final String displayName;

  private final FunctionResource[] resources;

  private String className;

  private GenericUDF genericUDF;

  private GenericUDTF genericUDTF;

  private GenericUDAFResolver genericUDAFResolver;

  private Class<? extends TableFunctionResolver>  tableFunctionResolver;

  private boolean blockedFunction;

  // for persistent function
  // if the function is dropped, all functions registered to sessions are needed to be reloaded
  private AtomicBoolean discarded;

  public FunctionInfo(String displayName, String className, FunctionResource... resources) {
    this.functionType = FunctionType.PERSISTENT;
    this.displayName = displayName;
    this.className = className;
    this.isInternalTableFunction = false;
    this.resources = resources;
    this.discarded = new AtomicBoolean(false);  // shared to all session functions
  }

  public FunctionInfo(FunctionType functionType, String displayName,
      GenericUDF genericUDF, FunctionResource... resources) {
    this.functionType = functionType;
    this.displayName = displayName;
    this.genericUDF = genericUDF;
    this.isInternalTableFunction = false;
    this.resources = resources;
  }

  public FunctionInfo(FunctionType functionType, String displayName,
      GenericUDAFResolver genericUDAFResolver, FunctionResource... resources) {
    this.functionType = functionType;
    this.displayName = displayName;
    this.genericUDAFResolver = genericUDAFResolver;
    this.isInternalTableFunction = false;
    this.resources = resources;
  }

  public FunctionInfo(FunctionType functionType, String displayName,
      GenericUDTF genericUDTF, FunctionResource... resources) {
    this.functionType = functionType;
    this.displayName = displayName;
    this.genericUDTF = genericUDTF;
    this.isInternalTableFunction = false;
    this.resources = resources;
  }

  public FunctionInfo(FunctionType functionType, String displayName, Class<? extends TableFunctionResolver> tFnCls,
      FunctionResource... resources) {
    this.functionType = functionType;
    this.displayName = displayName;
    this.tableFunctionResolver = tFnCls;
    PartitionTableFunctionDescription def = AnnotationUtils.getAnnotation(
        tableFunctionResolver, PartitionTableFunctionDescription.class);
    this.isInternalTableFunction = def != null && def.isInternal();
    this.resources = resources;
  }

  /**
   * Get a new GenericUDF object for the function.
   */
  public GenericUDF getGenericUDF() {
    // GenericUDF is stateful - we have to make a copy here
    if (genericUDF == null) {
      return null;
    }
    return FunctionRegistry.cloneGenericUDF(genericUDF);
  }

  /**
   * Get a new GenericUDTF object for the function.
   */
  public GenericUDTF getGenericUDTF() {
    // GenericUDTF is stateful too, copy
    if (genericUDTF == null) {
      return null;
    }
    return FunctionRegistry.cloneGenericUDTF(genericUDTF);
  }

  /**
   * Get the GenericUDAFResolver object for the function.
   */
  public GenericUDAFResolver getGenericUDAFResolver() {
    return genericUDAFResolver;
  }



  /**
   * Get the Class of the UDF.
   */
  public Class<?> getFunctionClass() {
    if (isGenericUDF()) {
      if (genericUDF instanceof GenericUDFBridge) {
        return ((GenericUDFBridge) genericUDF).getUdfClass();
      } else {
        return genericUDF.getClass();
      }
    } else if (isGenericUDAF()) {
      if (genericUDAFResolver instanceof GenericUDAFBridge) {
        return ((GenericUDAFBridge) genericUDAFResolver).getUDAFClass();
      } else {
        return genericUDAFResolver.getClass();
      }
    } else if (isGenericUDTF()) {
      return genericUDTF.getClass();
    }
    if(isTableFunction()) {
      return this.tableFunctionResolver;
    }
    return null;
  }

  /**
   * Get the display name for this function. This should be transferred into
   * exprNodeGenericUDFDesc, and will be used as the first parameter to
   * GenericUDF.getDisplayName() call, instead of hard-coding the function name.
   * This will solve the problem of displaying only one name when a udf is
   * registered under 2 names.
   */
  public String getDisplayName() {
    return displayName;
  }

  /**
   * Native functions cannot be unregistered.
   */
  public boolean isNative() {
    return functionType == FunctionType.BUILTIN || functionType == FunctionType.PERSISTENT;
  }

  /**
   * Internal table functions cannot be used in the language.
   * {@link WindowingTableFunction}
   */
  public boolean isInternalTableFunction() {
    return isInternalTableFunction;
  }

  /**
   * @return TRUE if the function is a GenericUDF
   */
  public boolean isGenericUDF() {
    return null != genericUDF;
  }

  /**
   * @return TRUE if the function is a GenericUDAF
   */
  public boolean isGenericUDAF() {
    return null != genericUDAFResolver;
  }

  /**
   * @return TRUE if the function is a GenericUDTF
   */
  public boolean isGenericUDTF() {
    return null != genericUDTF;
  }

  /**
   * @return TRUE if the function is a Table Function
   */
  public boolean isTableFunction() {
    return null != tableFunctionResolver;
  }

  public boolean isBlockedFunction() {
    return blockedFunction;
  }

  public void setBlockedFunction(boolean blockedFunction) {
    this.blockedFunction = blockedFunction;
  }

  public boolean isBuiltIn() {
    return functionType == FunctionType.BUILTIN;
  }

  public boolean isPersistent() {
    return functionType == FunctionType.PERSISTENT;
  }

  public String getClassName() {
    return className;
  }

  public FunctionResource[] getResources() {
    return resources;
  }

  public void discarded() {
    if (discarded != null) {
      discarded.set(true);
    }
  }

  // for persistent function
  public boolean isDiscarded() {
    return discarded != null && discarded.get();
  }

  // for persistent function
  public void shareStateWith(FunctionInfo function) {
    if (function != null) {
      function.discarded = discarded;
    }
  }

  public FunctionType getFunctionType() {
    return functionType;
  }

  public static class FunctionResource {
    private final SessionState.ResourceType resourceType;
    private final String resourceURI;
    public FunctionResource(SessionState.ResourceType resourceType, String resourceURI) {
      this.resourceType = resourceType;
      this.resourceURI = resourceURI;
    }
    public SessionState.ResourceType getResourceType() {
      return resourceType;
    }
    public String getResourceURI() {
      return resourceURI;
    }
    @Override
    public String toString() {
      return resourceType + ":" + resourceURI;
    }
  }
}
