/*
 * Copyright 2019 The Android Open Source Project
 *
 * 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
 *
 *      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 "fields/array_field.h"

#include "fields/custom_field.h"
#include "fields/scalar_field.h"
#include "util.h"

const std::string ArrayField::kFieldType = "ArrayField";

ArrayField::ArrayField(std::string name, int element_size, int array_size, ParseLocation loc)
    : PacketField(name, loc),
      element_field_(new ScalarField("val", element_size, loc)),
      element_size_(element_size),
      array_size_(array_size) {
  if (element_size > 64 || element_size < 0) {
    ERROR(this) << __func__ << ": Not implemented for element size = " << element_size;
  }
  if (element_size % 8 != 0) {
    ERROR(this) << "Can only have arrays with elements that are byte aligned (" << element_size
                << ")";
  }
}

ArrayField::ArrayField(std::string name, TypeDef* type_def, int array_size, ParseLocation loc)
    : PacketField(name, loc),
      element_field_(type_def->GetNewField("val", loc)),
      element_size_(element_field_->GetSize()),
      array_size_(array_size) {
  if (!element_size_.empty() && element_size_.bits() % 8 != 0) {
    ERROR(this) << "Can only have arrays with elements that are byte aligned (" << element_size_
                << ")";
  }
}

const std::string& ArrayField::GetFieldType() const { return ArrayField::kFieldType; }

Size ArrayField::GetSize() const {
  if (!element_size_.empty() && !element_size_.has_dynamic()) {
    return Size(array_size_ * element_size_.bits());
  }
  return Size();
}

Size ArrayField::GetBuilderSize() const {
  if (!element_size_.empty() && !element_size_.has_dynamic()) {
    return GetSize();
  } else if (element_field_->BuilderParameterMustBeMoved()) {
    std::string ret = "[this](){ size_t length = 0; for (const auto& elem : " + GetName() +
                      "_) { length += elem->size() * 8; } return length; }()";
    return ret;
  } else {
    std::string ret = "[this](){ size_t length = 0; for (const auto& elem : " + GetName() +
                      "_) { length += elem.size() * 8; } return length; }()";
    return ret;
  }
}

Size ArrayField::GetStructSize() const {
  if (!element_size_.empty() && !element_size_.has_dynamic()) {
    return GetSize();
  } else if (element_field_->BuilderParameterMustBeMoved()) {
    std::string ret = "[this](){ size_t length = 0; for (const auto& elem : to_fill->" + GetName() +
                      "_) { length += elem->size() * 8; } return length; }()";
    return ret;
  } else {
    std::string ret = "[this](){ size_t length = 0; for (const auto& elem : to_fill->" + GetName() +
                      "_) { length += elem.size() * 8; } return length; }()";
    return ret;
  }
}

std::string ArrayField::GetDataType() const {
  return "std::array<" + element_field_->GetDataType() + "," + std::to_string(array_size_) + ">";
}

void ArrayField::GenExtractor(std::ostream& s, int num_leading_bits, bool for_struct) const {
  s << GetDataType() << "::iterator ret_it = " << GetName() << "_ptr->begin();";
  s << "auto " << element_field_->GetName() << "_it = " << GetName() << "_it;";
  if (!element_size_.empty()) {
    s << "while (" << element_field_->GetName()
      << "_it.NumBytesRemaining() >= " << element_size_.bytes();
    s << " && ret_it < " << GetName() << "_ptr->end()) {";
  } else {
    s << "while (" << element_field_->GetName() << "_it.NumBytesRemaining() > 0 ";
    s << " && ret_it < " << GetName() << "_ptr->end()) {";
  }
  if (element_field_->BuilderParameterMustBeMoved()) {
    s << element_field_->GetDataType() << " " << element_field_->GetName() << "_ptr;";
  } else {
    s << "auto " << element_field_->GetName() << "_ptr = ret_it;";
  }
  element_field_->GenExtractor(s, num_leading_bits, for_struct);
  if (element_field_->BuilderParameterMustBeMoved()) {
    s << "*ret_it = std::move(" << element_field_->GetName() << "_ptr);";
  }
  s << "ret_it++;";
  s << "}";
}

std::string ArrayField::GetGetterFunctionName() const {
  std::stringstream ss;
  ss << "Get" << util::UnderscoreToCamelCase(GetName());
  return ss.str();
}

void ArrayField::GenGetter(std::ostream& s, Size start_offset, Size end_offset) const {
  s << GetDataType() << " " << GetGetterFunctionName() << "() const {";
  s << "ASSERT(was_validated_);";
  s << "size_t end_index = size();";
  s << "auto to_bound = begin();";

  int num_leading_bits = GenBounds(s, start_offset, end_offset, GetSize());
  s << GetDataType() << " " << GetName() << "_value{};";
  s << GetDataType() << "* " << GetName() << "_ptr = &" << GetName() << "_value;";
  GenExtractor(s, num_leading_bits, false);

  s << "return " << GetName() << "_value;";
  s << "}\n";
}

std::string ArrayField::GetBuilderParameterType() const {
  std::stringstream ss;
  if (element_field_->BuilderParameterMustBeMoved()) {
    ss << "std::array<" << element_field_->GetDataType() << "," << array_size_ << ">";
  } else {
    ss << "const std::array<" << element_field_->GetDataType() << "," << array_size_ << ">&";
  }
  return ss.str();
}

bool ArrayField::BuilderParameterMustBeMoved() const {
  return element_field_->BuilderParameterMustBeMoved();
}

bool ArrayField::GenBuilderMember(std::ostream& s) const {
  s << "std::array<" << element_field_->GetDataType() << "," << array_size_ << "> " << GetName();
  return true;
}

bool ArrayField::HasParameterValidator() const { return false; }

void ArrayField::GenParameterValidator(std::ostream&) const {
  // Array length is validated by the compiler
}

void ArrayField::GenInserter(std::ostream& s) const {
  s << "for (const auto& val_ : " << GetName() << "_) {";
  element_field_->GenInserter(s);
  s << "}\n";
}

void ArrayField::GenValidator(std::ostream&) const {
  // NOTE: We could check if the element size divides cleanly into the array size, but we decided to
  // forgo that in favor of just returning as many elements as possible in a best effort style.
  //
  // Other than that there is nothing that arrays need to be validated on other than length so
  // nothing needs to be done here.
}

bool ArrayField::IsContainerField() const { return true; }

const PacketField* ArrayField::GetElementField() const { return element_field_; }

void ArrayField::GenStringRepresentation(std::ostream& s, std::string accessor) const {
  s << "\"ARRAY[\";";
  s << "/* " << element_field_->GetDataType() << "   " << element_field_->GetFieldType() << " */";

  std::string arr_idx = "arridx_" + accessor;
  std::string arr_size = std::to_string(array_size_);
  s << "for (size_t index = 0; index < " << arr_size << "; index++) {";
  std::string element_accessor = "(" + accessor + "[index])";
  s << "ss << ((index == 0) ? \"\" : \", \") << ";

  if (element_field_->GetFieldType() == CustomField::kFieldType) {
    s << element_accessor << ".ToString()";
  } else {
    element_field_->GenStringRepresentation(s, element_accessor);
  }

  s << ";}";
  s << "ss << \"]\"";
}
