//
// Copyright (C) 2021 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.

#pragma once

#include <algorithm>
#include <cstdint>
#include <optional>
#include <string>
#include <tuple>
#include <type_traits>
#include <vector>

#include <android-base/logging.h>
#include <android-base/strings.h>

#include "cuttlefish/common/libs/confui/packet_types.h"
#include "cuttlefish/common/libs/confui/utils.h"
#include "cuttlefish/common/libs/fs/shared_buf.h"
#include "cuttlefish/common/libs/fs/shared_fd.h"

/**
 * @file packet.h
 *
 * @brief lowest-level packet for communication between host & guest
 *
 * Each packet has three fields
 *  1. session_id_: the name of the currently active confirmation UI session
 *  2. type_: the type of command/response. E.g. start, stop, ack, abort, etc
 *  3. additional_info_: all the other additional information
 *
 * The binary representation of each packet is as follows:
 *  n:L[1]:L[2]:...:L[n]:data[1]data[2]data[3]...data[n]
 *
 * The additional_info_ is in general a variable number of items, each
 * is either a byte vector (e.g. std::vector<uint8_t>) or a string.
 *
 * n is the number of items. L[i] is the length of i th item. data[i]
 * is the binary representation of the i th item
 *
 */
namespace cuttlefish {
namespace confui {
namespace packet {

/*
 * methods in namespace impl is not intended for public use
 *
 * For exposed APIs, skip to "start of public APIs
 * or, skip the namespace impl
 */
namespace impl {
template <typename Buffer, typename... Args>
void AppendToBuffer(Buffer& buffer, Args&&... args) {
  (buffer.insert(buffer.end(), std::begin(std::forward<Args>(args)),
                 std::end(std::forward<Args>(args))),
   ...);
}

template <typename... Args>
std::vector<int> MakeSizeHeader(Args&&... args) {
  std::vector<int> lengths;
  (lengths.push_back(std::distance(std::begin(args), std::end(args))), ...);
  return lengths;
}

// Use only this function to make a packet to send over the confirmation
// ui packet layer
template <typename... Args>
Payload ToPayload(const std::string& cmd_str, const std::string& session_id,
                  Args&&... args) {
  using namespace cuttlefish::confui::packet::impl;
  constexpr auto n_args = sizeof...(Args);
  std::stringstream ss;
  ss << ArgsToString(session_id, ":", cmd_str, ":", n_args, ":");
  // create size header
  std::vector<int> size_info =
      impl::MakeSizeHeader(std::forward<Args>(args)...);
  for (const auto sz : size_info) {
    ss << sz << ":";
  }
  std::string header = ss.str();
  std::vector<std::uint8_t> payload_buffer{header.begin(), header.end()};
  impl::AppendToBuffer(payload_buffer, std::forward<Args>(args)...);

  PayloadHeader ph;
  ph.payload_length_ = payload_buffer.size();
  return {ph, payload_buffer};
}
}  // namespace impl

/*
 * start of public methods
 */
std::optional<ParsedPacket> ReadPayload(SharedFD s);

template <typename... Args>
bool WritePayload(SharedFD d, const std::string& cmd_str,
                  const std::string& session_id, Args&&... args) {
  // TODO(kwstephenkim): type check Args... so that they are either
  // kind of std::string or std::vector<1 byte>
  if (!d->IsOpen()) {
    ConfUiLog(ERROR) << "file, socket, etc, is not open to write";
    return false;
  }
  auto [payload_header, data_to_send] =
      impl::ToPayload(cmd_str, session_id, std::forward<Args>(args)...);
  const std::string data_in_str(data_to_send.cbegin(), data_to_send.cend());

  auto nwrite = WriteAll(d, reinterpret_cast<const char*>(&payload_header),
                         sizeof(payload_header));
  if (nwrite != sizeof(payload_header)) {
    return false;
  }
  nwrite = WriteAll(d, reinterpret_cast<const char*>(data_to_send.data()),
                    data_to_send.size());
  if (nwrite != data_to_send.size()) {
    return false;
  }
  return true;
}

}  // end of namespace packet
}  // end of namespace confui
}  // end of namespace cuttlefish
