// Copyright 2022 The Pigweed Authors
//
// 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.

#include "proto_bloat.h"
#include "pw_bloat/bloat_this_binary.h"
#include "pw_protobuf/decoder.h"
#include "pw_protobuf/encoder.h"
#include "pw_protobuf/stream_decoder.h"
#include "pw_protobuf_test_protos/size_report.pwpb.h"
#include "pw_result/result.h"
#include "pw_status/status.h"

#ifndef _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
#define _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN 0
#endif  // _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN

#ifndef _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
#define _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT 0
#endif  // _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT

#ifndef _PW_PROTOBUF_SIZE_REPORT_MESSAGE
#define _PW_PROTOBUF_SIZE_REPORT_MESSAGE 0
#endif  // _PW_PROTOBUF_SIZE_REPORT_MESSAGE

namespace pw::protobuf_size_report {
namespace {

namespace ItemInfo = pwpb::ItemInfo;
namespace ResponseInfo = pwpb::ResponseInfo;

template <typename T>
PW_NO_INLINE void ConsumeValue(T val) {
  [[maybe_unused]] volatile T no_optimize = val;
}

#if _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN

std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> encode_buffer;
pw::protobuf::MemoryEncoder encoder(encode_buffer);

PW_NO_INLINE void BasicEncode() {
  pw::Status status;
  volatile enum KeyType : uint32_t {
    NONE = 0,
    KEY_STRING = 1,
    KEY_TOKEN = 2,
  } which_key = KeyType::KEY_STRING;
  volatile bool has_timestamp = true;
  volatile bool has_has_value = false;
  if (which_key == KeyType::KEY_STRING) {
    encoder.WriteString(1, "test");
  } else if (which_key == KeyType::KEY_TOKEN) {
    encoder.WriteFixed32(2, 99999);
  }

  if (has_timestamp) {
    encoder.WriteInt64(3, 1663003467);
  }

  if (has_has_value) {
    encoder.WriteBool(4, true);
  }

  {
    pw::protobuf::StreamEncoder submessage_encoder =
        encoder.GetNestedEncoder(5);
    status.Update(submessage_encoder.WriteInt64(1, 0x5001DBADFEEDBEE5));
    status.Update(submessage_encoder.WriteInt32(2, 128));
    status.Update(submessage_encoder.WriteInt32(3, 2));
  }
  ConsumeValue(status);
}

std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> decode_buffer;
pw::protobuf::Decoder decoder(decode_buffer);

PW_NO_INLINE void DecodeItemInfo(pw::ConstByteSpan data) {
  pw::protobuf::Decoder submessage_decoder(data);
  while (submessage_decoder.Next().ok()) {
    switch (submessage_decoder.FieldNumber()) {
      case static_cast<uint32_t>(ItemInfo::Fields::kOffset): {
        uint64_t value;
        if (submessage_decoder.ReadUint64(&value).ok()) {
          ConsumeValue(value);
        }
        break;
      }
      case static_cast<uint32_t>(ItemInfo::Fields::kSize): {
        uint32_t value;
        if (submessage_decoder.ReadUint32(&value).ok()) {
          ConsumeValue(value);
        }
        break;
      }
      case static_cast<uint32_t>(ItemInfo::Fields::kAccessLevel): {
        uint32_t value;

        if (submessage_decoder.ReadUint32(&value).ok()) {
          ConsumeValue(value);
        }
        break;
      }
    }
  }
}

PW_NO_INLINE void BasicDecode() {
  volatile enum KeyType : uint32_t {
    NONE = 0,
    KEY_STRING = 1,
    KEY_TOKEN = 2,
  } which_key = KeyType::NONE;
  volatile bool has_timestamp = false;
  volatile bool has_has_value = false;

  while (decoder.Next().ok()) {
    switch (decoder.FieldNumber()) {
      case static_cast<uint32_t>(ResponseInfo::Fields::kKeyString): {
        which_key = KeyType::KEY_STRING;
        std::string_view value;
        if (decoder.ReadString(&value).ok()) {
          ConsumeValue(value);
        }
        break;
      }
      case static_cast<uint32_t>(ResponseInfo::Fields::kKeyToken): {
        which_key = KeyType::KEY_TOKEN;
        uint32_t value;
        if (decoder.ReadUint32(&value).ok()) {
          ConsumeValue(value);
        }
        break;
      }
      case static_cast<uint32_t>(ResponseInfo::Fields::kTimestamp): {
        uint64_t value;
        has_timestamp = true;
        if (decoder.ReadUint64(&value).ok()) {
          ConsumeValue(value);
        }
        break;
      }
      case static_cast<uint32_t>(ResponseInfo::Fields::kHasValue): {
        bool value;
        has_has_value = true;
        if (decoder.ReadBool(&value).ok()) {
          ConsumeValue(value);
        }
        break;
      }
      case static_cast<uint32_t>(ResponseInfo::Fields::kItemInfo): {
        pw::ConstByteSpan value;
        if (decoder.ReadBytes(&value).ok()) {
          DecodeItemInfo(value);
        }
        break;
      }
    }
  }
  ConsumeValue(which_key);
  ConsumeValue(has_timestamp);
  ConsumeValue(has_has_value);
}

#endif  // _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN

#if _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT

std::array<std::byte, ResponseInfo::kMaxEncodedSizeBytes> encode_buffer;
ResponseInfo::MemoryEncoder encoder(encode_buffer);

PW_NO_INLINE void BasicEncode() {
  pw::Status status;
  volatile enum KeyType : uint32_t {
    NONE = 0,
    KEY_STRING = 1,
    KEY_TOKEN = 2,
  } which_key = KeyType::KEY_STRING;
  volatile bool has_timestamp = true;
  volatile bool has_has_value = false;
  if (which_key == KeyType::KEY_STRING) {
    encoder.WriteKeyString("test");
  } else if (which_key == KeyType::KEY_TOKEN) {
    encoder.WriteKeyToken(99999);
  }

  if (has_timestamp) {
    encoder.WriteTimestamp(1663003467);
  }

  if (has_has_value) {
    encoder.WriteHasValue(true);
  }

  {
    ItemInfo::StreamEncoder submessage_encoder = encoder.GetItemInfoEncoder();
    status.Update(submessage_encoder.WriteOffset(0x5001DBADFEEDBEE5));
    status.Update(submessage_encoder.WriteSize(128));
    status.Update(submessage_encoder.WriteAccessLevel(ItemInfo::Access::WRITE));
  }
  ConsumeValue(status);
}

std::array<std::byte, ResponseInfo::kMaxEncodedSizeBytes> decode_buffer;
pw::stream::MemoryReader reader(decode_buffer);
ResponseInfo::StreamDecoder decoder(reader);

PW_NO_INLINE void DecodeItemInfo(ItemInfo::StreamDecoder& submessage_decoder) {
  while (submessage_decoder.Next().ok()) {
    pw::Result<ItemInfo::Fields> field = submessage_decoder.Field();
    if (!field.ok()) {
      ConsumeValue(field.status());
      return;
    }

    switch (field.value()) {
      case ItemInfo::Fields::kOffset: {
        pw::Result<uint64_t> value = submessage_decoder.ReadOffset();
        if (value.ok()) {
          ConsumeValue(value);
        }
        break;
      }
      case ItemInfo::Fields::kSize: {
        pw::Result<uint32_t> value = submessage_decoder.ReadSize();
        if (value.ok()) {
          ConsumeValue(value);
        }
        break;
      }
      case ItemInfo::Fields::kAccessLevel: {
        pw::Result<ItemInfo::Access> value =
            submessage_decoder.ReadAccessLevel();
        if (value.ok()) {
          ConsumeValue(value);
        }
        break;
      }
    }
  }
}

PW_NO_INLINE void BasicDecode() {
  volatile enum KeyType : uint32_t {
    NONE = 0,
    KEY_STRING = 1,
    KEY_TOKEN = 2,
  } which_key = KeyType::NONE;
  volatile bool has_timestamp = false;
  volatile bool has_has_value = false;

  while (decoder.Next().ok()) {
    while (decoder.Next().ok()) {
      pw::Result<ResponseInfo::Fields> field = decoder.Field();
      if (!field.ok()) {
        ConsumeValue(field.status());
        return;
      }

      switch (field.value()) {
        case ResponseInfo::Fields::kKeyString: {
          which_key = KeyType::KEY_STRING;
          std::array<char, 8> value;
          pw::StatusWithSize status = decoder.ReadKeyString(value);
          if (status.ok()) {
            ConsumeValue(pw::span(value));
          }
          break;
        }
        case ResponseInfo::Fields::kKeyToken: {
          which_key = KeyType::KEY_TOKEN;
          pw::Result<uint32_t> value = decoder.ReadKeyToken();
          if (value.ok()) {
            ConsumeValue(value);
          }
          break;
        }
        case ResponseInfo::Fields::kTimestamp: {
          has_timestamp = true;
          pw::Result<int64_t> value = decoder.ReadTimestamp();
          if (value.ok()) {
            ConsumeValue(value);
          }
          break;
        }
        case ResponseInfo::Fields::kHasValue: {
          has_has_value = true;
          pw::Result<bool> value = decoder.ReadHasValue();
          if (value.ok()) {
            ConsumeValue(value);
          }
          break;
        }
        case ResponseInfo::Fields::kItemInfo: {
          ItemInfo::StreamDecoder submessage_decoder =
              decoder.GetItemInfoDecoder();
          DecodeItemInfo(submessage_decoder);
          break;
        }
      }
    }
  }
  ConsumeValue(which_key);
  ConsumeValue(has_timestamp);
  ConsumeValue(has_has_value);
}
#endif  // _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT

#if _PW_PROTOBUF_SIZE_REPORT_MESSAGE

ResponseInfo::Message message;

std::array<std::byte, ResponseInfo::kMaxEncodedSizeBytes> encode_buffer;
ResponseInfo::MemoryEncoder encoder(encode_buffer);

PW_NO_INLINE void BasicEncode() {
  volatile enum KeyType : uint32_t {
    NONE = 0,
    KEY_STRING = 1,
    KEY_TOKEN = 2,
  } which_key = KeyType::KEY_STRING;
  volatile bool has_timestamp = true;
  volatile bool has_has_value = false;
  if (which_key == KeyType::KEY_STRING) {
    message.key_string.SetEncoder(
        [](ResponseInfo::StreamEncoder& key_string_encoder) -> pw::Status {
          key_string_encoder.WriteKeyString("test");
          return pw::OkStatus();
        });
  } else if (which_key == KeyType::KEY_TOKEN) {
    message.key_token = 99999;
  }
  message.timestamp =
      has_timestamp ? std::optional<uint32_t>(1663003467) : std::nullopt;
  message.has_value = has_has_value ? std::optional<bool>(false) : std::nullopt;

  message.item_info.offset = 0x5001DBADFEEDBEE5;
  message.item_info.size = 128;
  message.item_info.access_level = ItemInfo::Access::WRITE;
  ConsumeValue(encoder.Write(message));
}

std::array<std::byte, ResponseInfo::kMaxEncodedSizeBytes> decode_buffer;
pw::stream::MemoryReader reader(decode_buffer);
ResponseInfo::StreamDecoder decoder(reader);

PW_NO_INLINE void BasicDecode() {
  volatile enum KeyType : uint32_t {
    NONE = 0,
    KEY_STRING = 1,
    KEY_TOKEN = 2,
  } which_key = KeyType::NONE;
  volatile bool has_timestamp = false;
  volatile bool has_has_value = false;
  if (pw::Status status = decoder.Read(message); status.ok()) {
    ConsumeValue(status);
    has_timestamp = message.timestamp.has_value();
    has_has_value = message.has_value.has_value();
  }
  ConsumeValue(which_key);
  ConsumeValue(has_timestamp);
  ConsumeValue(has_has_value);
}
#endif  // _PW_PROTOBUF_SIZE_REPORT_MESSAGE

}  // namespace
}  // namespace pw::protobuf_size_report

int main() {
  pw::bloat::BloatThisBinary();
  pw::protobuf_size_report::BloatWithBase();
  pw::protobuf_size_report::BloatWithEncoder();
  pw::protobuf_size_report::BloatWithStreamDecoder();
  pw::protobuf_size_report::BloatWithDecoder();
  pw::protobuf_size_report::BloatWithTableEncoder();
  pw::protobuf_size_report::BloatWithTableDecoder();
  pw::protobuf_size_report::BasicEncode();
  pw::protobuf_size_report::BasicDecode();
  return 0;
}
