// Copyright 2021 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 "pw_software_update/manifest_accessor.h"

#include "pw_software_update/config.h"
#include "pw_software_update/update_bundle.pwpb.h"
#include "pw_software_update/update_bundle_accessor.h"

namespace pw::software_update {

ManifestAccessor ManifestAccessor::FromBundle(protobuf::Message bundle) {
  protobuf::Message targets_metadata =
      bundle
          .AsStringToMessageMap(static_cast<uint32_t>(
              UpdateBundle::Fields::kTargetsMetadata))[kTopLevelTargetsName]
          .AsMessage(static_cast<uint32_t>(
              SignedTargetsMetadata::Fields::kSerializedTargetsMetadata));

  protobuf::Bytes user_manifest =
      bundle.AsStringToBytesMap(static_cast<uint32_t>(
          UpdateBundle::Fields::kTargetPayloads))[kUserManifestTargetFileName];

  return ManifestAccessor(targets_metadata, user_manifest);
}

ManifestAccessor ManifestAccessor::FromManifest(protobuf::Message manifest) {
  protobuf::Message targets_metadata =
      manifest.AsStringToMessageMap(static_cast<uint32_t>(
          Manifest::Fields::kTargetsMetadata))[kTopLevelTargetsName];

  protobuf::Bytes user_manifest =
      manifest.AsBytes(static_cast<uint32_t>(Manifest::Fields::kUserManifest));

  return ManifestAccessor(targets_metadata, user_manifest);
}

protobuf::RepeatedMessages ManifestAccessor::GetTargetFiles() {
  PW_TRY(status());
  return targets_metadata_.AsRepeatedMessages(
      static_cast<uint32_t>(TargetsMetadata::Fields::kTargetFiles));
}

protobuf::Uint32 ManifestAccessor::GetVersion() {
  PW_TRY(status());
  return targets_metadata_
      .AsMessage(
          static_cast<uint32_t>(TargetsMetadata::Fields::kCommonMetadata))
      .AsUint32(static_cast<uint32_t>(CommonMetadata::Fields::kVersion));
}

Status ManifestAccessor::Export(stream::Writer& writer) {
  PW_TRY(status());

  // Write out the targets metadata map.
  stream::MemoryReader name_reader(as_bytes(span(kTopLevelTargetsName)));
  stream::IntervalReader metadata_reader =
      targets_metadata_.ToBytes().GetBytesReader();
  std::byte stream_pipe_buffer[WRITE_MANIFEST_STREAM_PIPE_BUFFER_SIZE];
  PW_TRY(protobuf::WriteProtoStringToBytesMapEntry(
      static_cast<uint32_t>(Manifest::Fields::kTargetsMetadata),
      name_reader,
      kTopLevelTargetsName.size(),
      metadata_reader,
      metadata_reader.interval_size(),
      stream_pipe_buffer,
      writer));

  // The user manifest is optional, write it out if available().
  stream::IntervalReader user_manifest_reader = user_manifest_.GetBytesReader();
  if (user_manifest_reader.ok()) {
    protobuf::StreamEncoder encoder(writer, {});
    PW_TRY(encoder.WriteBytesFromStream(
        static_cast<uint32_t>(Manifest::Fields::kUserManifest),
        user_manifest_reader,
        user_manifest_reader.interval_size(),
        stream_pipe_buffer));
  }

  return OkStatus();
}

protobuf::Message ManifestAccessor::GetTargetFile(protobuf::String name) {
  PW_TRY(status());

  std::array<std::byte, MAX_TARGET_NAME_LENGTH> name_buf = {};

  stream::IntervalReader name_reader = name.GetBytesReader();
  PW_TRY(name_reader.status());

  if (name_reader.interval_size() > name_buf.size()) {
    return Status::OutOfRange();
  }

  Result<ByteSpan> read_result = name_reader.Read(name_buf);
  PW_TRY(read_result.status());

  const ConstByteSpan name_span = read_result.value();
  const std::string_view name_view(
      reinterpret_cast<const char*>(name_span.data()), name_span.size_bytes());

  return GetTargetFile(name_view);
}

protobuf::Message ManifestAccessor::GetTargetFile(std::string_view name) {
  PW_TRY(status());

  for (protobuf::Message target_file : GetTargetFiles()) {
    protobuf::String target_name = target_file.AsString(
        static_cast<uint32_t>(TargetFile::Fields::kFileName));
    Result<bool> compare_result = target_name.Equal(name);
    PW_TRY(compare_result.status());
    if (compare_result.value()) {
      return target_file;
    }
  }

  return Status::NotFound();
}

}  // namespace pw::software_update
