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

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.drill.exec.expr.fn.DrillFuncHolder;
import org.apache.drill.exec.expr.fn.FunctionImplementationRegistry;
import org.apache.drill.exec.expr.fn.FunctionLookupContext;
import org.apache.drill.exec.expr.fn.registry.FunctionHolder;
import org.apache.drill.exec.ops.ExecutorFragmentContext;
import org.apache.drill.exec.store.pojo.NonNullable;

/**
 * List functions as a System Table
 */
public class FunctionsIterator implements Iterator<Object> {
  private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(FunctionsIterator.class);

  private final Iterator<FunctionInfo> sortedIterator;

  public FunctionsIterator(ExecutorFragmentContext context) {
    Map<String, FunctionInfo> functionMap = new HashMap<String, FunctionInfo>();
    //Access Registry for function list
    FunctionLookupContext functionLookupContext = context.getFunctionRegistry();
    //Check true instance type
    if (functionLookupContext instanceof FunctionImplementationRegistry) {
      @SuppressWarnings("resource")
      FunctionImplementationRegistry functionImplRegistry = (FunctionImplementationRegistry) functionLookupContext;
      Map<String, List<FunctionHolder>> jarFunctionListMap = functionImplRegistry.getAllJarsWithFunctionsHolders();

      for (String jarName : jarFunctionListMap.keySet()) {
        for (FunctionHolder dfhEntry : jarFunctionListMap.get(jarName)) {
          populateFunctionMap(functionMap, jarName, dfhEntry.getHolder());
        }
      }

      List<FunctionInfo> functionList = new ArrayList<FunctionsIterator.FunctionInfo>(functionMap.values());
      functionList.sort((FunctionInfo o1, FunctionInfo o2) -> {
        int result = o1.name.compareTo(o2.name);
        if (result == 0) {
          result = o1.signature.compareTo(o2.signature);
        }
        if (result == 0) {
          return o1.returnType.compareTo(o2.returnType);
        }
        return result;
      });

      sortedIterator = functionList.iterator();
    } else {
      //Logging error
      logger.error("Function Registry was found to be of {} instead of {}. No functions could be loaded ",
          FunctionImplementationRegistry.class, functionLookupContext.getClass());
      sortedIterator = Collections.emptyIterator();
    }

  }

  /**
   * Populate the map of functionInfo based on the functionSignatureKey for a given Jar-DrillFunctionHolder
   * @param functionMap map to populate
   * @param jarName name of the source jar
   * @param dfh functionHolder that carries all the registered names and the signature
   */
  private void populateFunctionMap(Map<String, FunctionInfo> functionMap, String jarName, DrillFuncHolder dfh) {
    String registeredNames[] = dfh.getRegisteredNames();
    String signature = dfh.getInputParameters();
    String returnType = dfh.getReturnType().getMinorType().toString();
    boolean isInternal = dfh.isInternal();
    for (String name : registeredNames) {
      //Generate a unique key for a function holder as 'functionName#functionSignature'
      //Bumping capacity from default 16 to 64 (since key is expected to be bigger, and reduce probability of resizing)
      String funcSignatureKey = new StringBuilder(64).append(name).append('#').append(signature).toString();
      functionMap.put(funcSignatureKey, new FunctionInfo(name, signature, returnType, jarName, isInternal));
    }
  }

  @Override
  public boolean hasNext() {
    return sortedIterator.hasNext();
  }

  @Override
  public FunctionInfo next() {
    return sortedIterator.next();
  }

  /**
   * Representation of an entry in the System table - Functions
   */
  public static class FunctionInfo {
    @NonNullable
    public final String name;
    @NonNullable
    public final String signature;
    @NonNullable
    public final String returnType;
    @NonNullable
    public final String source;
    @NonNullable
    public final boolean internal;

    public FunctionInfo(String funcName, String funcSignature, String funcReturnType, String jarName) {
      this(funcName, funcSignature, funcReturnType, jarName, false);
    }

    public FunctionInfo(String funcName, String funcSignature, String funcReturnType, String jarName, boolean isInternal) {
      this.name = funcName;
      this.signature = funcSignature;
      this.returnType = funcReturnType;
      this.source = jarName;
      this.internal = isInternal;
    }
  }
}
