/*
 * Copyright (C) 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 "perfetto/protozero/field.h"

#include "perfetto/base/compiler.h"
#include "perfetto/base/logging.h"

#if !PERFETTO_IS_LITTLE_ENDIAN()
// The memcpy() for fixed32/64 below needs to be adjusted if we want to
// support big endian CPUs. There doesn't seem to be a compelling need today.
#error Unimplemented for big endian archs.
#endif

namespace protozero {

template <typename Container>
void Field::SerializeAndAppendToInternal(Container* dst) const {
  namespace pu = proto_utils;
  size_t initial_size = dst->size();
  dst->resize(initial_size + pu::kMaxSimpleFieldEncodedSize + size_);
  uint8_t* start = reinterpret_cast<uint8_t*>(&(*dst)[initial_size]);
  uint8_t* wptr = start;
  switch (type_) {
    case static_cast<int>(pu::ProtoWireType::kVarInt): {
      wptr = pu::WriteVarInt(pu::MakeTagVarInt(id_), wptr);
      wptr = pu::WriteVarInt(int_value_, wptr);
      break;
    }
    case static_cast<int>(pu::ProtoWireType::kFixed32): {
      wptr = pu::WriteVarInt(pu::MakeTagFixed<uint32_t>(id_), wptr);
      uint32_t value32 = static_cast<uint32_t>(int_value_);
      memcpy(wptr, &value32, sizeof(value32));
      wptr += sizeof(uint32_t);
      break;
    }
    case static_cast<int>(pu::ProtoWireType::kFixed64): {
      wptr = pu::WriteVarInt(pu::MakeTagFixed<uint64_t>(id_), wptr);
      memcpy(wptr, &int_value_, sizeof(int_value_));
      wptr += sizeof(uint64_t);
      break;
    }
    case static_cast<int>(pu::ProtoWireType::kLengthDelimited): {
      ConstBytes payload = as_bytes();
      wptr = pu::WriteVarInt(pu::MakeTagLengthDelimited(id_), wptr);
      wptr = pu::WriteVarInt(payload.size, wptr);
      memcpy(wptr, payload.data, payload.size);
      wptr += payload.size;
      break;
    }
    default:
      PERFETTO_FATAL("Unknown field type %u", type_);
  }
  size_t written_size = static_cast<size_t>(wptr - start);
  PERFETTO_DCHECK(written_size > 0 && written_size < pu::kMaxMessageLength);
  PERFETTO_DCHECK(initial_size + written_size <= dst->size());
  dst->resize(initial_size + written_size);
}

void Field::SerializeAndAppendTo(std::string* dst) const {
  SerializeAndAppendToInternal(dst);
}

void Field::SerializeAndAppendTo(std::vector<uint8_t>* dst) const {
  SerializeAndAppendToInternal(dst);
}

}  // namespace protozero
