/**
 * 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.ambari.metrics.core.timeline.aggregators;

import org.apache.ambari.metrics.webapp.TimelineWebServices;

import java.util.Arrays;

/**
 * Is used to determine metrics aggregate table.
 *
 * @see org.apache.ambari.metrics.webapp.TimelineWebServices#getTimelineMetrics
 */
public class Function {
  public static Function DEFAULT_VALUE_FUNCTION = new Function(ReadFunction.VALUE, null);
  private static final String SUFFIX_SEPARATOR = "\\._";

  private ReadFunction readFunction = ReadFunction.VALUE;
  private PostProcessingFunction postProcessingFunction = null;

  public Function() {
  }

  public Function(ReadFunction readFunction,
                  PostProcessingFunction ppFunction){
    if (readFunction!=null){
      this.readFunction = readFunction ;
    }
    this.postProcessingFunction = ppFunction;
  }

  /**
   * Segregate post processing function eg: rate from aggregate function,
   * example: avg, in any order
   * @param metricName metric name from request
   * @return @Function
   */
  public static Function fromMetricName(String metricName) {
    // gets postprocessing, and aggregation function
    // ex. Metric._rate._avg
    String[] parts = metricName.split(SUFFIX_SEPARATOR);

    ReadFunction readFunction = ReadFunction.VALUE;
    PostProcessingFunction ppFunction = null;

    if (parts.length <= 1) {
      return new Function(readFunction, null);
    }
    if (parts.length > 3) {
      throw new IllegalArgumentException("Invalid number of functions specified.");
    }

    // Parse functions
    boolean isSuccessful = false; // Best effort
    for (String part : parts) {
      if (ReadFunction.isPresent(part)) {
        readFunction = ReadFunction.getFunction(part);
        isSuccessful = true;
      }
      if (PostProcessingFunction.isPresent(part)) {
        ppFunction = PostProcessingFunction.getFunction(part);
        isSuccessful = true;
      }
    }

    // Throw exception if parsing failed
    if (!isSuccessful) {
      throw new FunctionFormatException("Could not parse provided functions: " +
        "" + Arrays.asList(parts));
    }

    return new Function(readFunction, ppFunction);
  }

  public String getSuffix(){
    return (postProcessingFunction == null)? readFunction.getSuffix() :
      postProcessingFunction.getSuffix() + readFunction.getSuffix();
  }

  public ReadFunction getReadFunction() {
    return readFunction;
  }

  @Override
  public String toString() {
    return "Function{" +
      "readFunction=" + readFunction +
      ", postProcessingFunction=" + postProcessingFunction +
      '}';
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Function)) return false;

    Function function = (Function) o;

    return postProcessingFunction == function.postProcessingFunction
      && readFunction == function.readFunction;

  }

  @Override
  public int hashCode() {
    int result = readFunction.hashCode();
    result = 31 * result + (postProcessingFunction != null ?
      postProcessingFunction.hashCode() : 0);
    return result;
  }

  public enum PostProcessingFunction {
    NONE(""),
    RATE("._rate"),
    DIFF("._diff");

    PostProcessingFunction(String suffix){
      this.suffix = suffix;
    }

    private String suffix = "";

    public String getSuffix(){
      return suffix;
    }

    public static boolean isPresent(String functionName) {
      try {
        PostProcessingFunction.valueOf(functionName.toUpperCase());
      } catch (IllegalArgumentException e) {
        return false;
      }
      return true;
    }

    public static PostProcessingFunction getFunction(String functionName) throws FunctionFormatException {
      if (functionName == null) {
        return NONE;
      }

      try {
        return PostProcessingFunction.valueOf(functionName.toUpperCase());
      } catch (IllegalArgumentException e) {
        throw new FunctionFormatException("Function should be ._rate", e);
      }
    }
  }

  public enum ReadFunction {
    VALUE(""),
    AVG("._avg"),
    MIN("._min"),
    MAX("._max"),
    SUM("._sum");

    private final String suffix;

    ReadFunction(String suffix){
      this.suffix = suffix;
    }

    public String getSuffix() {
      return suffix;
    }

    public static boolean isPresent(String functionName) {
      try {
        ReadFunction.valueOf(functionName.toUpperCase());
      } catch (IllegalArgumentException e) {
        return false;
      }
      return true;
    }

    public static ReadFunction getFunction(String functionName) throws FunctionFormatException {
      if (functionName == null) {
        return VALUE;
      }
      try {
        return ReadFunction.valueOf(functionName.toUpperCase());
      } catch (IllegalArgumentException e) {
        throw new FunctionFormatException(
          "Function should be sum, avg, min, max. Got " + functionName, e);
      }
    }
  }

  public static class FunctionFormatException extends IllegalArgumentException {
    public FunctionFormatException(String message) {
      super(message);
    }

    public FunctionFormatException(String message, Throwable cause) {
      super(message, cause);
    }
  }

}
