// 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.

#include "gandiva/function_signature.h"

#include <cstddef>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include "arrow/util/checked_cast.h"
#include "arrow/util/hash_util.h"
#include "arrow/util/logging_internal.h"
#include "arrow/util/string.h"

using arrow::internal::AsciiEqualsCaseInsensitive;
using arrow::internal::AsciiToLower;
using arrow::internal::checked_cast;
using arrow::internal::hash_combine;

namespace gandiva {

namespace {

bool DataTypeEquals(const DataTypePtr& left, const DataTypePtr& right) {
  if (left->id() == right->id()) {
    switch (left->id()) {
      case arrow::Type::DECIMAL: {
        // For decimal types, the precision/scale isn't part of the signature.
        auto dleft = checked_cast<arrow::DecimalType*>(left.get());
        auto dright = checked_cast<arrow::DecimalType*>(right.get());
        return (dleft != NULL) && (dright != NULL) &&
               (dleft->byte_width() == dright->byte_width());
      }
      default:
        return left->Equals(right);
    }
  } else {
    return false;
  }
}

}  // namespace

FunctionSignature::FunctionSignature(std::string base_name, DataTypeVector param_types,
                                     DataTypePtr ret_type)
    : base_name_(std::move(base_name)),
      param_types_(std::move(param_types)),
      ret_type_(std::move(ret_type)) {
  DCHECK_GT(base_name_.length(), 0);
  for (auto it = param_types_.begin(); it != param_types_.end(); it++) {
    DCHECK(*it);
  }
  DCHECK(ret_type_);
}

bool FunctionSignature::operator==(const FunctionSignature& other) const {
  if (param_types_.size() != other.param_types_.size() ||
      !DataTypeEquals(ret_type_, other.ret_type_) ||
      !AsciiEqualsCaseInsensitive(base_name_, other.base_name_)) {
    return false;
  }

  for (size_t idx = 0; idx < param_types_.size(); idx++) {
    if (!DataTypeEquals(param_types_[idx], other.param_types_[idx])) {
      return false;
    }
  }
  return true;
}

/// calculated based on name, datatype id of parameters and datatype id
/// of return type.
std::size_t FunctionSignature::Hash() const {
  static const size_t kSeedValue = 17;
  size_t result = kSeedValue;
  hash_combine(result, AsciiToLower(base_name_));
  hash_combine(result, static_cast<size_t>(ret_type_->id()));
  // not using hash_range since we only want to include the id from the data type
  for (auto& param_type : param_types_) {
    hash_combine(result, static_cast<size_t>(param_type->id()));
  }
  return result;
}

std::string FunctionSignature::ToString() const {
  std::stringstream s;

  s << ret_type_->ToString() << " " << base_name_ << "(";
  for (uint32_t i = 0; i < param_types_.size(); i++) {
    if (i > 0) {
      s << ", ";
    }

    s << param_types_[i]->ToString();
  }

  s << ")";
  return s.str();
}

}  // namespace gandiva
