//===-- lib/Evaluate/constant.cpp -----------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "flang/Evaluate/constant.h"
#include "flang/Evaluate/expression.h"
#include "flang/Evaluate/shape.h"
#include "flang/Evaluate/type.h"
#include <string>

namespace Fortran::evaluate {

ConstantBounds::ConstantBounds(const ConstantSubscripts &shape)
    : shape_(shape), lbounds_(shape_.size(), 1) {}

ConstantBounds::ConstantBounds(ConstantSubscripts &&shape)
    : shape_(std::move(shape)), lbounds_(shape_.size(), 1) {}

ConstantBounds::~ConstantBounds() = default;

void ConstantBounds::set_lbounds(ConstantSubscripts &&lb) {
  CHECK(lb.size() == shape_.size());
  lbounds_ = std::move(lb);
  for (std::size_t j{0}; j < shape_.size(); ++j) {
    if (shape_[j] == 0) {
      lbounds_[j] = 1;
    }
  }
}

ConstantSubscripts ConstantBounds::ComputeUbounds(
    std::optional<int> dim) const {
  if (dim) {
    CHECK(*dim < Rank());
    return {lbounds_[*dim] + (shape_[*dim] - 1)};
  } else {
    ConstantSubscripts ubounds(Rank());
    for (int i{0}; i < Rank(); ++i) {
      ubounds[i] = lbounds_[i] + (shape_[i] - 1);
    }
    return ubounds;
  }
}

void ConstantBounds::SetLowerBoundsToOne() {
  for (auto &n : lbounds_) {
    n = 1;
  }
}

Constant<SubscriptInteger> ConstantBounds::SHAPE() const {
  return AsConstantShape(shape_);
}

bool ConstantBounds::HasNonDefaultLowerBound() const {
  for (auto n : lbounds_) {
    if (n != 1) {
      return true;
    }
  }
  return false;
}

ConstantSubscript ConstantBounds::SubscriptsToOffset(
    const ConstantSubscripts &index) const {
  CHECK(GetRank(index) == GetRank(shape_));
  ConstantSubscript stride{1}, offset{0};
  int dim{0};
  for (auto j : index) {
    auto lb{lbounds_[dim]};
    auto extent{shape_[dim++]};
    CHECK(j >= lb && j - lb < extent);
    offset += stride * (j - lb);
    stride *= extent;
  }
  return offset;
}

std::optional<uint64_t> TotalElementCount(const ConstantSubscripts &shape) {
  uint64_t size{1};
  for (auto dim : shape) {
    CHECK(dim >= 0);
    uint64_t osize{size};
    size = osize * dim;
    if (size > std::numeric_limits<decltype(dim)>::max() ||
        (dim != 0 && size / dim != osize)) {
      return std::nullopt;
    }
  }
  return static_cast<uint64_t>(GetSize(shape));
}

bool ConstantBounds::IncrementSubscripts(
    ConstantSubscripts &indices, const std::vector<int> *dimOrder) const {
  int rank{GetRank(shape_)};
  CHECK(GetRank(indices) == rank);
  CHECK(!dimOrder || static_cast<int>(dimOrder->size()) == rank);
  for (int j{0}; j < rank; ++j) {
    ConstantSubscript k{dimOrder ? (*dimOrder)[j] : j};
    auto lb{lbounds_[k]};
    CHECK(indices[k] >= lb);
    if (++indices[k] - lb < shape_[k]) {
      return true;
    } else {
      CHECK(indices[k] - lb == std::max<ConstantSubscript>(shape_[k], 1));
      indices[k] = lb;
    }
  }
  return false; // all done
}

std::optional<std::vector<int>> ValidateDimensionOrder(
    int rank, const std::vector<int> &order) {
  std::vector<int> dimOrder(rank);
  if (static_cast<int>(order.size()) == rank) {
    std::bitset<common::maxRank> seenDimensions;
    for (int j{0}; j < rank; ++j) {
      int dim{order[j]};
      if (dim < 1 || dim > rank || seenDimensions.test(dim - 1)) {
        return std::nullopt;
      }
      dimOrder[j] = dim - 1;
      seenDimensions.set(dim - 1);
    }
    return dimOrder;
  } else {
    return std::nullopt;
  }
}

bool HasNegativeExtent(const ConstantSubscripts &shape) {
  for (ConstantSubscript extent : shape) {
    if (extent < 0) {
      return true;
    }
  }
  return false;
}

template <typename RESULT, typename ELEMENT>
ConstantBase<RESULT, ELEMENT>::ConstantBase(
    std::vector<Element> &&x, ConstantSubscripts &&sh, Result res)
    : ConstantBounds(std::move(sh)), result_{res}, values_(std::move(x)) {
  CHECK(TotalElementCount(shape()) && size() == *TotalElementCount(shape()));
}

template <typename RESULT, typename ELEMENT>
ConstantBase<RESULT, ELEMENT>::~ConstantBase() {}

template <typename RESULT, typename ELEMENT>
bool ConstantBase<RESULT, ELEMENT>::operator==(const ConstantBase &that) const {
  return shape() == that.shape() && values_ == that.values_;
}

template <typename RESULT, typename ELEMENT>
auto ConstantBase<RESULT, ELEMENT>::Reshape(
    const ConstantSubscripts &dims) const -> std::vector<Element> {
  std::optional<uint64_t> optN{TotalElementCount(dims)};
  CHECK_MSG(optN, "Overflow in TotalElementCount");
  uint64_t n{*optN};
  CHECK(!empty() || n == 0);
  std::vector<Element> elements;
  auto iter{values().cbegin()};
  while (n-- > 0) {
    elements.push_back(*iter);
    if (++iter == values().cend()) {
      iter = values().cbegin();
    }
  }
  return elements;
}

template <typename RESULT, typename ELEMENT>
std::size_t ConstantBase<RESULT, ELEMENT>::CopyFrom(
    const ConstantBase<RESULT, ELEMENT> &source, std::size_t count,
    ConstantSubscripts &resultSubscripts, const std::vector<int> *dimOrder) {
  std::size_t copied{0};
  ConstantSubscripts sourceSubscripts{source.lbounds()};
  while (copied < count) {
    values_.at(SubscriptsToOffset(resultSubscripts)) =
        source.values_.at(source.SubscriptsToOffset(sourceSubscripts));
    copied++;
    source.IncrementSubscripts(sourceSubscripts);
    IncrementSubscripts(resultSubscripts, dimOrder);
  }
  return copied;
}

template <typename T>
auto Constant<T>::At(const ConstantSubscripts &index) const -> Element {
  return Base::values_.at(Base::SubscriptsToOffset(index));
}

template <typename T>
auto Constant<T>::Reshape(ConstantSubscripts &&dims) const -> Constant {
  return {Base::Reshape(dims), std::move(dims)};
}

template <typename T>
std::size_t Constant<T>::CopyFrom(const Constant<T> &source, std::size_t count,
    ConstantSubscripts &resultSubscripts, const std::vector<int> *dimOrder) {
  return Base::CopyFrom(source, count, resultSubscripts, dimOrder);
}

// Constant<Type<TypeCategory::Character, KIND> specializations
template <int KIND>
Constant<Type<TypeCategory::Character, KIND>>::Constant(
    const Scalar<Result> &str)
    : values_{str}, length_{static_cast<ConstantSubscript>(values_.size())} {}

template <int KIND>
Constant<Type<TypeCategory::Character, KIND>>::Constant(Scalar<Result> &&str)
    : values_{std::move(str)}, length_{static_cast<ConstantSubscript>(
                                   values_.size())} {}

template <int KIND>
Constant<Type<TypeCategory::Character, KIND>>::Constant(ConstantSubscript len,
    std::vector<Scalar<Result>> &&strings, ConstantSubscripts &&sh)
    : ConstantBounds(std::move(sh)), length_{len} {
  CHECK(TotalElementCount(shape()) &&
      strings.size() == *TotalElementCount(shape()));
  values_.assign(strings.size() * length_,
      static_cast<typename Scalar<Result>::value_type>(' '));
  ConstantSubscript at{0};
  for (const auto &str : strings) {
    auto strLen{static_cast<ConstantSubscript>(str.size())};
    if (strLen > length_) {
      values_.replace(at, length_, str.substr(0, length_));
    } else {
      values_.replace(at, strLen, str);
    }
    at += length_;
  }
  CHECK(at == static_cast<ConstantSubscript>(values_.size()));
}

template <int KIND>
Constant<Type<TypeCategory::Character, KIND>>::~Constant() {}

template <int KIND>
bool Constant<Type<TypeCategory::Character, KIND>>::empty() const {
  return size() == 0;
}

template <int KIND>
std::size_t Constant<Type<TypeCategory::Character, KIND>>::size() const {
  if (length_ == 0) {
    std::optional<uint64_t> n{TotalElementCount(shape())};
    CHECK(n);
    return *n;
  } else {
    return static_cast<ConstantSubscript>(values_.size()) / length_;
  }
}

template <int KIND>
auto Constant<Type<TypeCategory::Character, KIND>>::At(
    const ConstantSubscripts &index) const -> Scalar<Result> {
  auto offset{SubscriptsToOffset(index)};
  return values_.substr(offset * length_, length_);
}

template <int KIND>
auto Constant<Type<TypeCategory::Character, KIND>>::Substring(
    ConstantSubscript lo, ConstantSubscript hi) const
    -> std::optional<Constant> {
  std::vector<Element> elements;
  ConstantSubscript n{GetSize(shape())};
  ConstantSubscript newLength{0};
  if (lo > hi) { // zero-length results
    while (n-- > 0) {
      elements.emplace_back(); // ""
    }
  } else if (lo < 1 || hi > length_) {
    return std::nullopt;
  } else {
    newLength = hi - lo + 1;
    for (ConstantSubscripts at{lbounds()}; n-- > 0; IncrementSubscripts(at)) {
      elements.emplace_back(At(at).substr(lo - 1, newLength));
    }
  }
  return Constant{newLength, std::move(elements), ConstantSubscripts{shape()}};
}

template <int KIND>
auto Constant<Type<TypeCategory::Character, KIND>>::Reshape(
    ConstantSubscripts &&dims) const -> Constant<Result> {
  std::optional<uint64_t> optN{TotalElementCount(dims)};
  CHECK(optN);
  uint64_t n{*optN};
  CHECK(!empty() || n == 0);
  std::vector<Element> elements;
  ConstantSubscript at{0},
      limit{static_cast<ConstantSubscript>(values_.size())};
  while (n-- > 0) {
    elements.push_back(values_.substr(at, length_));
    at += length_;
    if (at == limit) { // subtle: at > limit somehow? substr() will catch it
      at = 0;
    }
  }
  return {length_, std::move(elements), std::move(dims)};
}

template <int KIND>
std::size_t Constant<Type<TypeCategory::Character, KIND>>::CopyFrom(
    const Constant<Type<TypeCategory::Character, KIND>> &source,
    std::size_t count, ConstantSubscripts &resultSubscripts,
    const std::vector<int> *dimOrder) {
  CHECK(length_ == source.length_);
  if (length_ == 0) {
    // It's possible that the array of strings consists of all empty strings.
    // If so, constant folding will result in a string that's completely empty
    // and the length_ will be zero, and there's nothing to do.
    return count;
  } else {
    std::size_t copied{0};
    std::size_t elementBytes{length_ * sizeof(decltype(values_[0]))};
    ConstantSubscripts sourceSubscripts{source.lbounds()};
    while (copied < count) {
      auto *dest{&values_.at(SubscriptsToOffset(resultSubscripts) * length_)};
      const auto *src{&source.values_.at(
          source.SubscriptsToOffset(sourceSubscripts) * length_)};
      std::memcpy(dest, src, elementBytes);
      copied++;
      source.IncrementSubscripts(sourceSubscripts);
      IncrementSubscripts(resultSubscripts, dimOrder);
    }
    return copied;
  }
}

// Constant<SomeDerived> specialization
Constant<SomeDerived>::Constant(const StructureConstructor &x)
    : Base{x.values(), Result{x.derivedTypeSpec()}} {}

Constant<SomeDerived>::Constant(StructureConstructor &&x)
    : Base{std::move(x.values()), Result{x.derivedTypeSpec()}} {}

Constant<SomeDerived>::Constant(const semantics::DerivedTypeSpec &spec,
    std::vector<StructureConstructorValues> &&x, ConstantSubscripts &&s)
    : Base{std::move(x), std::move(s), Result{spec}} {}

static std::vector<StructureConstructorValues> AcquireValues(
    std::vector<StructureConstructor> &&x) {
  std::vector<StructureConstructorValues> result;
  for (auto &&structure : std::move(x)) {
    result.emplace_back(std::move(structure.values()));
  }
  return result;
}

Constant<SomeDerived>::Constant(const semantics::DerivedTypeSpec &spec,
    std::vector<StructureConstructor> &&x, ConstantSubscripts &&shape)
    : Base{AcquireValues(std::move(x)), std::move(shape), Result{spec}} {}

std::optional<StructureConstructor>
Constant<SomeDerived>::GetScalarValue() const {
  if (Rank() == 0) {
    return StructureConstructor{result().derivedTypeSpec(), values_.at(0)};
  } else {
    return std::nullopt;
  }
}

StructureConstructor Constant<SomeDerived>::At(
    const ConstantSubscripts &index) const {
  return {result().derivedTypeSpec(), values_.at(SubscriptsToOffset(index))};
}

bool Constant<SomeDerived>::operator==(
    const Constant<SomeDerived> &that) const {
  return result().derivedTypeSpec() == that.result().derivedTypeSpec() &&
      shape() == that.shape() && values_ == that.values_;
}

auto Constant<SomeDerived>::Reshape(ConstantSubscripts &&dims) const
    -> Constant {
  return {result().derivedTypeSpec(), Base::Reshape(dims), std::move(dims)};
}

std::size_t Constant<SomeDerived>::CopyFrom(const Constant<SomeDerived> &source,
    std::size_t count, ConstantSubscripts &resultSubscripts,
    const std::vector<int> *dimOrder) {
  return Base::CopyFrom(source, count, resultSubscripts, dimOrder);
}

bool ComponentCompare::operator()(SymbolRef x, SymbolRef y) const {
  return semantics::SymbolSourcePositionCompare{}(x, y);
}

#ifdef _MSC_VER // disable bogus warning about missing definitions
#pragma warning(disable : 4661)
#endif
INSTANTIATE_CONSTANT_TEMPLATES
} // namespace Fortran::evaluate
