// 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/precompiled/decimal_ops.h"
#include "gandiva/precompiled/types.h"

extern "C" {

FORCE_INLINE
void add_large_decimal128_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                                     int32_t x_scale, int64_t y_high, uint64_t y_low,
                                     int32_t y_precision, int32_t y_scale,
                                     int32_t out_precision, int32_t out_scale,
                                     int64_t* out_high, uint64_t* out_low) {
  gandiva::BasicDecimalScalar128 x(x_high, x_low, x_precision, x_scale);
  gandiva::BasicDecimalScalar128 y(y_high, y_low, y_precision, y_scale);

  arrow::BasicDecimal128 out = gandiva::decimalops::Add(x, y, out_precision, out_scale);
  *out_high = out.high_bits();
  *out_low = out.low_bits();
}

FORCE_INLINE
void multiply_decimal128_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                                    int32_t x_scale, int64_t y_high, uint64_t y_low,
                                    int32_t y_precision, int32_t y_scale,
                                    int32_t out_precision, int32_t out_scale,
                                    int64_t* out_high, uint64_t* out_low) {
  gandiva::BasicDecimalScalar128 x(x_high, x_low, x_precision, x_scale);
  gandiva::BasicDecimalScalar128 y(y_high, y_low, y_precision, y_scale);
  bool overflow;

  // TODO ravindra: generate error on overflows (ARROW-4570).
  arrow::BasicDecimal128 out =
      gandiva::decimalops::Multiply(x, y, out_precision, out_scale, &overflow);
  *out_high = out.high_bits();
  *out_low = out.low_bits();
}

FORCE_INLINE
void divide_decimal128_decimal128(int64_t context, int64_t x_high, uint64_t x_low,
                                  int32_t x_precision, int32_t x_scale, int64_t y_high,
                                  uint64_t y_low, int32_t y_precision, int32_t y_scale,
                                  int32_t out_precision, int32_t out_scale,
                                  int64_t* out_high, uint64_t* out_low) {
  gandiva::BasicDecimalScalar128 x(x_high, x_low, x_precision, x_scale);
  gandiva::BasicDecimalScalar128 y(y_high, y_low, y_precision, y_scale);
  bool overflow;

  // TODO ravindra: generate error on overflows (ARROW-4570).
  arrow::BasicDecimal128 out =
      gandiva::decimalops::Divide(context, x, y, out_precision, out_scale, &overflow);
  *out_high = out.high_bits();
  *out_low = out.low_bits();
}

FORCE_INLINE
void mod_decimal128_decimal128(int64_t context, int64_t x_high, uint64_t x_low,
                               int32_t x_precision, int32_t x_scale, int64_t y_high,
                               uint64_t y_low, int32_t y_precision, int32_t y_scale,
                               int32_t out_precision, int32_t out_scale,
                               int64_t* out_high, uint64_t* out_low) {
  gandiva::BasicDecimalScalar128 x(x_high, x_low, x_precision, x_scale);
  gandiva::BasicDecimalScalar128 y(y_high, y_low, y_precision, y_scale);
  bool overflow;

  // TODO ravindra: generate error on overflows (ARROW-4570).
  arrow::BasicDecimal128 out =
      gandiva::decimalops::Mod(context, x, y, out_precision, out_scale, &overflow);
  *out_high = out.high_bits();
  *out_low = out.low_bits();
}

FORCE_INLINE
int32_t compare_decimal128_decimal128_internal(int64_t x_high, uint64_t x_low,
                                               int32_t x_precision, int32_t x_scale,
                                               int64_t y_high, uint64_t y_low,
                                               int32_t y_precision, int32_t y_scale) {
  gandiva::BasicDecimalScalar128 x(x_high, x_low, x_precision, x_scale);
  gandiva::BasicDecimalScalar128 y(y_high, y_low, y_precision, y_scale);

  return gandiva::decimalops::Compare(x, y);
}

FORCE_INLINE
void abs_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision, int32_t x_scale,
                    int32_t out_precision, int32_t out_scale, int64_t* out_high,
                    uint64_t* out_low) {
  gandiva::BasicDecimal128 x(x_high, x_low);
  x.Abs();
  *out_high = x.high_bits();
  *out_low = x.low_bits();
}

FORCE_INLINE
void ceil_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision, int32_t x_scale,
                     int32_t out_precision, int32_t out_scale, int64_t* out_high,
                     uint64_t* out_low) {
  gandiva::BasicDecimalScalar128 x({x_high, x_low}, x_precision, x_scale);

  bool overflow = false;
  auto out = gandiva::decimalops::Ceil(x, &overflow);
  *out_high = out.high_bits();
  *out_low = out.low_bits();
}

FORCE_INLINE
void floor_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                      int32_t x_scale, int32_t out_precision, int32_t out_scale,
                      int64_t* out_high, uint64_t* out_low) {
  gandiva::BasicDecimalScalar128 x({x_high, x_low}, x_precision, x_scale);

  bool overflow = false;
  auto out = gandiva::decimalops::Floor(x, &overflow);
  *out_high = out.high_bits();
  *out_low = out.low_bits();
}

FORCE_INLINE
void round_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                      int32_t x_scale, int32_t out_precision, int32_t out_scale,
                      int64_t* out_high, uint64_t* out_low) {
  gandiva::BasicDecimalScalar128 x({x_high, x_low}, x_precision, x_scale);

  bool overflow = false;
  auto out = gandiva::decimalops::Round(x, out_precision, 0, 0, &overflow);
  *out_high = out.high_bits();
  *out_low = out.low_bits();
}

FORCE_INLINE
void round_decimal128_int32(int64_t x_high, uint64_t x_low, int32_t x_precision,
                            int32_t x_scale, int32_t rounding_scale,
                            int32_t out_precision, int32_t out_scale, int64_t* out_high,
                            uint64_t* out_low) {
  gandiva::BasicDecimalScalar128 x({x_high, x_low}, x_precision, x_scale);

  bool overflow = false;
  auto out =
      gandiva::decimalops::Round(x, out_precision, out_scale, rounding_scale, &overflow);
  *out_high = out.high_bits();
  *out_low = out.low_bits();
}

FORCE_INLINE
void truncate_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                         int32_t x_scale, int32_t out_precision, int32_t out_scale,
                         int64_t* out_high, uint64_t* out_low) {
  gandiva::BasicDecimalScalar128 x({x_high, x_low}, x_precision, x_scale);

  bool overflow = false;
  auto out = gandiva::decimalops::Truncate(x, out_precision, 0, 0, &overflow);
  *out_high = out.high_bits();
  *out_low = out.low_bits();
}

FORCE_INLINE
void truncate_decimal128_int32(int64_t x_high, uint64_t x_low, int32_t x_precision,
                               int32_t x_scale, int32_t rounding_scale,
                               int32_t out_precision, int32_t out_scale,
                               int64_t* out_high, uint64_t* out_low) {
  gandiva::BasicDecimalScalar128 x({x_high, x_low}, x_precision, x_scale);

  bool overflow = false;
  auto out = gandiva::decimalops::Truncate(x, out_precision, out_scale, rounding_scale,
                                           &overflow);
  *out_high = out.high_bits();
  *out_low = out.low_bits();
}

FORCE_INLINE
double castFLOAT8_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                             int32_t x_scale) {
  gandiva::BasicDecimalScalar128 x({x_high, x_low}, x_precision, x_scale);

  bool overflow = false;
  return gandiva::decimalops::ToDouble(x, &overflow);
}

FORCE_INLINE
int64_t castBIGINT_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                              int32_t x_scale) {
  gandiva::BasicDecimalScalar128 x({x_high, x_low}, x_precision, x_scale);

  bool overflow = false;
  return gandiva::decimalops::ToInt64(x, &overflow);
}

FORCE_INLINE
void castDECIMAL_int64(int64_t in, int32_t x_precision, int32_t x_scale,
                       int64_t* out_high, uint64_t* out_low) {
  bool overflow = false;
  auto out = gandiva::decimalops::FromInt64(in, x_precision, x_scale, &overflow);
  *out_high = out.high_bits();
  *out_low = out.low_bits();
}

FORCE_INLINE
void castDECIMAL_int32(int32_t in, int32_t x_precision, int32_t x_scale,
                       int64_t* out_high, uint64_t* out_low) {
  castDECIMAL_int64(in, x_precision, x_scale, out_high, out_low);
}

FORCE_INLINE
void castDECIMAL_float64(double in, int32_t x_precision, int32_t x_scale,
                         int64_t* out_high, uint64_t* out_low) {
  bool overflow = false;
  auto out = gandiva::decimalops::FromDouble(in, x_precision, x_scale, &overflow);
  *out_high = out.high_bits();
  *out_low = out.low_bits();
}

FORCE_INLINE
void castDECIMAL_float32(float in, int32_t x_precision, int32_t x_scale,
                         int64_t* out_high, uint64_t* out_low) {
  castDECIMAL_float64(in, x_precision, x_scale, out_high, out_low);
}

FORCE_INLINE
bool castDecimal_internal(int64_t x_high, uint64_t x_low, int32_t x_precision,
                          int32_t x_scale, int32_t out_precision, int32_t out_scale,
                          int64_t* out_high, int64_t* out_low) {
  gandiva::BasicDecimalScalar128 x({x_high, x_low}, x_precision, x_scale);
  bool overflow = false;
  auto out = gandiva::decimalops::Convert(x, out_precision, out_scale, &overflow);
  *out_high = out.high_bits();
  *out_low = out.low_bits();
  return overflow;
}

FORCE_INLINE
void castDECIMAL_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                            int32_t x_scale, int32_t out_precision, int32_t out_scale,
                            int64_t* out_high, int64_t* out_low) {
  castDecimal_internal(x_high, x_low, x_precision, x_scale, out_precision, out_scale,
                       out_high, out_low);
}

FORCE_INLINE
void castDECIMALNullOnOverflow_decimal128(int64_t x_high, uint64_t x_low,
                                          int32_t x_precision, int32_t x_scale,
                                          bool x_isvalid, bool* out_valid,
                                          int32_t out_precision, int32_t out_scale,
                                          int64_t* out_high, int64_t* out_low) {
  *out_valid = true;

  if (!x_isvalid) {
    *out_valid = false;
    return;
  }

  if (castDecimal_internal(x_high, x_low, x_precision, x_scale, out_precision, out_scale,
                           out_high, out_low)) {
    *out_valid = false;
  }
}

FORCE_INLINE
int32_t hash32_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                          int32_t x_scale, gdv_boolean x_isvalid) {
  return x_isvalid
             ? hash32_buf(gandiva::BasicDecimal128(x_high, x_low).ToBytes().data(), 16, 0)
             : 0;
}

FORCE_INLINE
int32_t hash_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                        int32_t x_scale, gdv_boolean x_isvalid) {
  return hash32_decimal128(x_high, x_low, x_precision, x_scale, x_isvalid);
}

FORCE_INLINE
int64_t hash64_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                          int32_t x_scale, gdv_boolean x_isvalid) {
  return x_isvalid
             ? hash64_buf(gandiva::BasicDecimal128(x_high, x_low).ToBytes().data(), 16, 0)
             : 0;
}

FORCE_INLINE
int32_t hash32WithSeed_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                                  int32_t x_scale, gdv_boolean x_isvalid, int32_t seed,
                                  gdv_boolean seed_isvalid) {
  if (!x_isvalid) {
    return seed;
  }
  return hash32_buf(gandiva::BasicDecimal128(x_high, x_low).ToBytes().data(), 16, seed);
}

FORCE_INLINE
int64_t hash64WithSeed_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                                  int32_t x_scale, gdv_boolean x_isvalid, int64_t seed,
                                  gdv_boolean seed_isvalid) {
  if (!x_isvalid) {
    return seed;
  }
  return hash64_buf(gandiva::BasicDecimal128(x_high, x_low).ToBytes().data(), 16, seed);
}

FORCE_INLINE
int32_t hash32AsDouble_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                                  int32_t x_scale, gdv_boolean x_isvalid) {
  return x_isvalid
             ? hash32_buf(gandiva::BasicDecimal128(x_high, x_low).ToBytes().data(), 16, 0)
             : 0;
}

FORCE_INLINE
int64_t hash64AsDouble_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                                  int32_t x_scale, gdv_boolean x_isvalid) {
  return x_isvalid
             ? hash64_buf(gandiva::BasicDecimal128(x_high, x_low).ToBytes().data(), 16, 0)
             : 0;
}

FORCE_INLINE
int32_t hash32AsDoubleWithSeed_decimal128(int64_t x_high, uint64_t x_low,
                                          int32_t x_precision, int32_t x_scale,
                                          gdv_boolean x_isvalid, int32_t seed,
                                          gdv_boolean seed_isvalid) {
  if (!x_isvalid) {
    return seed;
  }
  return hash32_buf(gandiva::BasicDecimal128(x_high, x_low).ToBytes().data(), 16, seed);
}

FORCE_INLINE
int64_t hash64AsDoubleWithSeed_decimal128(int64_t x_high, uint64_t x_low,
                                          int32_t x_precision, int32_t x_scale,
                                          gdv_boolean x_isvalid, int64_t seed,
                                          gdv_boolean seed_isvalid) {
  if (!x_isvalid) {
    return seed;
  }
  return hash64_buf(gandiva::BasicDecimal128(x_high, x_low).ToBytes().data(), 16, seed);
}

FORCE_INLINE
gdv_boolean isnull_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                              int32_t x_scale, gdv_boolean x_isvalid) {
  return !x_isvalid;
}

FORCE_INLINE
gdv_boolean isnotnull_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                                 int32_t x_scale, gdv_boolean x_isvalid) {
  return x_isvalid;
}

FORCE_INLINE
gdv_boolean isnumeric_decimal128(int64_t x_high, uint64_t x_low, int32_t x_precision,
                                 int32_t x_scale, gdv_boolean x_isvalid) {
  return x_isvalid;
}

FORCE_INLINE
gdv_boolean is_not_distinct_from_decimal128_decimal128(
    int64_t x_high, uint64_t x_low, int32_t x_precision, int32_t x_scale,
    gdv_boolean x_isvalid, int64_t y_high, uint64_t y_low, int32_t y_precision,
    int32_t y_scale, gdv_boolean y_isvalid) {
  if (x_isvalid != y_isvalid) {
    return false;
  }
  if (!x_isvalid) {
    return true;
  }
  return 0 == compare_decimal128_decimal128_internal(x_high, x_low, x_precision, x_scale,
                                                     y_high, y_low, y_precision, y_scale);
}

FORCE_INLINE
gdv_boolean is_distinct_from_decimal128_decimal128(int64_t x_high, uint64_t x_low,
                                                   int32_t x_precision, int32_t x_scale,
                                                   gdv_boolean x_isvalid, int64_t y_high,
                                                   uint64_t y_low, int32_t y_precision,
                                                   int32_t y_scale,
                                                   gdv_boolean y_isvalid) {
  return !is_not_distinct_from_decimal128_decimal128(x_high, x_low, x_precision, x_scale,
                                                     x_isvalid, y_high, y_low,
                                                     y_precision, y_scale, y_isvalid);
}

FORCE_INLINE
void castDECIMAL_utf8(int64_t context, const char* in, int32_t in_length,
                      int32_t out_precision, int32_t out_scale, int64_t* out_high,
                      uint64_t* out_low) {
  int64_t dec_high_from_str;
  uint64_t dec_low_from_str;
  int32_t precision_from_str;
  int32_t scale_from_str;
  int32_t status =
      gdv_fn_dec_from_string(context, in, in_length, &precision_from_str, &scale_from_str,
                             &dec_high_from_str, &dec_low_from_str);
  if (status != 0) {
    return;
  }

  gandiva::BasicDecimalScalar128 x({dec_high_from_str, dec_low_from_str},
                                   precision_from_str, scale_from_str);
  bool overflow = false;
  auto out = gandiva::decimalops::Convert(x, out_precision, out_scale, &overflow);
  *out_high = out.high_bits();
  *out_low = out.low_bits();
}

FORCE_INLINE
char* castVARCHAR_decimal128_int64(int64_t context, int64_t x_high, uint64_t x_low,
                                   int32_t x_precision, int32_t x_scale,
                                   int64_t out_len_param, int32_t* out_length) {
  int32_t full_dec_str_len;
  char* dec_str =
      gdv_fn_dec_to_string(context, x_high, x_low, x_scale, &full_dec_str_len);
  int32_t trunc_dec_str_len =
      out_len_param < full_dec_str_len ? out_len_param : full_dec_str_len;
  *out_length = trunc_dec_str_len;
  return dec_str;
}

}  // extern "C"
