/*******************************************************************************
 * 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.olingo.odata2.core.uri.expression;

import java.util.ArrayList;
import java.util.List;

import org.apache.olingo.odata2.api.edm.EdmSimpleType;
import org.apache.olingo.odata2.api.edm.EdmType;
import org.apache.olingo.odata2.api.edm.EdmTypeKind;

/**
 * Parameter set is a vector of 1 or more EDM types, it is used to store the possible
 * input and return types of a <i>OData filter operator</i> or <i>OData filter method</i>
 * @see InfoMethod
 * @see InfoBinaryOperator
 * @see InfoUnaryOperator
 * 
 */
@SuppressWarnings("javadoc")
public class ParameterSet {
  private EdmType returnType = null;
  private ArrayList<EdmSimpleType> types = new ArrayList<EdmSimpleType>();
  private EdmSimpleType furtherType = null;

  public ParameterSet(final EdmType returnType, final EdmSimpleType type1) {
    this.returnType = returnType;
    types.add(type1);
  }

  public ParameterSet(final EdmSimpleType returnType, final EdmSimpleType type1, final EdmSimpleType type2) {
    this.returnType = returnType;
    types.add(type1);
    types.add(type2);
  }

  public ParameterSet(final EdmSimpleType returnType, final EdmSimpleType type1, final EdmSimpleType type2,
      final EdmSimpleType type3) {
    this.returnType = returnType;
    types.add(type1);
    types.add(type2);
    types.add(type3);
  }

  public EdmType getReturnType() {
    return returnType;
  }

  public EdmSimpleType getFurtherType() {
    return furtherType;
  }

  public ParameterSet setFurtherType(final EdmSimpleType furtherType) {
    this.furtherType = furtherType;
    return this;
  }

  /**
   * Compares a list of EdmTypes with the EdmTypes stored in {@link #types}.
   * The lists are compared sequentially, e.g index N of actualParameterTypes with index N of {@link #types}.
   * If the input list contains more elements than stored in {@link #types} (which is allowed when validating the
   * <i>concat</i> method
   * which takes a variable number of input parameters), the actual parameter type is compared against the
   * {@link #furtherType}.
   * @param actualParameterTypes
   * @param allowPromotion
   * @return true if equals
   * @throws ExpressionParserInternalError
   */
  public boolean equals(final List<EdmType> actualParameterTypes, final boolean allowPromotion)
      throws ExpressionParserInternalError {
    int actSize = actualParameterTypes.size();
    int paramSize = types.size();

    if (actSize < paramSize) {
      return false;
      // throw FilterParserInternalError.createINVALID_TYPE_COUNT(); //to few actual Parameters
    }

    // This may happen if the number of supplied actual method parameters is higher then than the number
    // of allowed method parameters but this should be checked before, hence this is an internal error in the parser
    if ((actSize > paramSize) && (furtherType == null)) {
      return false;
      // throw FilterParserInternalError.createINVALID_TYPE_COUNT();
    }

    for (int i = 0; i < actSize; i++) {
      EdmType actType = actualParameterTypes.get(i);
      if (actType == null) {
        return false;
      }

      EdmSimpleType paramType = null;
      if (i < paramSize) {
        paramType = types.get(i);
      } else {
        paramType = furtherType; // for methods with variable amount of method parameters
      }

      if (!actType.equals(paramType)) {
        // this the parameter type does not fit and if it is not allowed to promoted the actual parameter then
        // this parameter combination does not fit
        if (!allowPromotion) {
          return false;
        }

        // Its allowed to promoted the actual parameter!!!

        // Promotion only allowed for simple types
        if (actType.getKind() != EdmTypeKind.SIMPLE) {
          return false; // Tested with TestParserExceptions.testAdditionalStuff CASE 8
        }

        // The type simply don't match
        if (!paramType.isCompatible((EdmSimpleType) actType)) {
          return false;
        }
      }
    }
    return true;
  }
}
