// Copyright 2019 Google LLC
//
// Licensed 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
//
//     https://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.

#ifndef SANDBOXED_API_VAR_ARRAY_H_
#define SANDBOXED_API_VAR_ARRAY_H_

#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <string>
#include <type_traits>

#include "absl/base/macros.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "sandboxed_api/rpcchannel.h"
#include "sandboxed_api/util/status_macros.h"
#include "sandboxed_api/var_abstract.h"
#include "sandboxed_api/var_ptr.h"

namespace sapi::v {

// Class representing an array.
template <class T>
class Array : public Var {
 public:
  // The array is not owned by this object.
  Array(T* arr, size_t nelem)
      : arr_(arr),
        nelem_(nelem),
        total_size_(nelem_ * sizeof(T)),
        buffer_owned_(false) {
    SetLocal(const_cast<std::remove_const_t<T>*>(arr_));
  }

  // The array is allocated and owned by this object.
  explicit Array(size_t nelem)
      : nelem_(nelem), total_size_(nelem_ * sizeof(T)), buffer_owned_(true) {
    void* storage = malloc(sizeof(T) * nelem);
    CHECK(storage != nullptr);
    SetLocal(storage);
    arr_ = static_cast<T*>(storage);
  }

  virtual ~Array() {
    if (buffer_owned_) {
      free(const_cast<std::remove_const_t<T>*>(arr_));
    }
  }

  T& operator[](size_t v) const { return arr_[v]; }
  T* GetData() const { return arr_; }

  size_t GetNElem() const { return nelem_; }
  size_t GetSize() const final { return total_size_; }
  Type GetType() const final { return Type::kArray; }
  std::string GetTypeString() const final { return "Array"; }
  std::string ToString() const override {
    return absl::StrCat("Array, elem size: ", sizeof(T),
                        " B., total size: ", total_size_,
                        " B., nelems: ", GetNElem());
  }

  // Resizes the local and remote buffer using realloc(). Note that this will
  // make all pointers to the current data (inside and outside of the sandbox)
  // invalid.
  absl::Status Resize(RPCChannel* rpc_channel, size_t nelems) {
    size_t absolute_size = sizeof(T) * nelems;
    // Resize local buffer.
    SAPI_RETURN_IF_ERROR(EnsureOwnedLocalBuffer(absolute_size));

    // Resize remote buffer and update local pointer.
    void* new_addr;

    SAPI_RETURN_IF_ERROR(
        rpc_channel->Reallocate(GetRemote(), absolute_size, &new_addr));
    if (!new_addr) {
      return absl::UnavailableError("Reallocate() returned nullptr");
    }
    SetRemote(new_addr);
    return absl::OkStatus();
  }

 private:
  friend class LenVal;

  // Resizes the internal storage.
  absl::Status EnsureOwnedLocalBuffer(size_t size) {
    if (size % sizeof(T)) {
      return absl::FailedPreconditionError(
          "Array size not a multiple of the item size");
    }
    // Do not (re-)allocate memory if the new size matches our size - except
    // when we don't own that buffer.
    if (size == total_size_ && buffer_owned_) {
      return absl::OkStatus();
    }
    void* new_addr = nullptr;
    if (buffer_owned_) {
      new_addr = realloc(arr_, size);
    } else {
      new_addr = malloc(size);
      if (new_addr) {
        memcpy(new_addr, arr_, std::min(size, total_size_));
        buffer_owned_ = true;
      }
    }
    if (!new_addr) {
      return absl::UnavailableError("(Re-)malloc failed");
    }

    arr_ = static_cast<T*>(new_addr);
    total_size_ = size;
    nelem_ = size / sizeof(T);
    SetLocal(new_addr);
    return absl::OkStatus();
  }

  // Pointer to the data, owned by the object if buffer_owned_ is 'true'.
  T* arr_;
  size_t nelem_;       // Number of elements
  size_t total_size_;  // Total size in bytes
  bool buffer_owned_;  // Whether we own the buffer
};

// Specialized Array class for representing NUL-terminated C-style strings. The
// buffer is owned by the class, and is mutable.
class CStr : public Array<char> {
 public:
  explicit CStr(absl::string_view cstr) : Array<char>(cstr.size() + 1) {
    std::copy(cstr.begin(), cstr.end(), GetData());
    GetData()[cstr.size()] = '\0';
  }

  std::string ToString() const final {
    return absl::StrCat("CStr: len(w/o NUL):", strlen(GetData()), ", ['",
                        GetData(), "']");
  }
};

// Specialized Array class for representing NUL-terminated C-style strings. The
// buffer is not owned by the class and is not mutable.
class ConstCStr : public Array<const char> {
 public:
  explicit ConstCStr(const char* cstr)
      : Array<const char>(cstr, strlen(cstr) + 1) {}

  std::string ToString() const final {
    return absl::StrCat("ConstCStr: len(w/o NUL):", strlen(GetData()), ", ['",
                        GetData(), "']");
  }
};

}  // namespace sapi::v

#endif  // SANDBOXED_API_VAR_ARRAY_H_
