//
// 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.

#include "cuttlefish/common/libs/confui/protocol.h"

#include <sstream>
#include <vector>

#include <android-base/strings.h>

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

namespace cuttlefish {
namespace confui {
namespace {
// default implementation of ToConfUiMessage
template <ConfUiCmd C>
std::unique_ptr<ConfUiMessage> ToConfUiMessage(
    const packet::ParsedPacket& message) {
  return std::make_unique<ConfUiGenericMessage<C>>(message.session_id_);
}

// these are specialized, and defined below
template <>
std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kCliAck>(
    const packet::ParsedPacket& message);
template <>
std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kStart>(
    const packet::ParsedPacket& message);
template <>
std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kUserInputEvent>(
    const packet::ParsedPacket& message);
template <>
std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kUserTouchEvent>(
    const packet::ParsedPacket& message);
template <>
std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kCliRespond>(
    const packet::ParsedPacket& message);

std::unique_ptr<ConfUiMessage> ToConfUiMessage(
    const packet::ParsedPacket& confui_packet) {
  const auto confui_cmd = ToCmd(confui_packet.type_);
  switch (confui_cmd) {
    // customized ConfUiMessage
    case ConfUiCmd::kStart:
      return ToConfUiMessage<ConfUiCmd::kStart>(confui_packet);
    case ConfUiCmd::kCliAck:
      return ToConfUiMessage<ConfUiCmd::kCliAck>(confui_packet);
    case ConfUiCmd::kCliRespond:
      return ToConfUiMessage<ConfUiCmd::kCliRespond>(confui_packet);
    case ConfUiCmd::kUserInputEvent:
      return ToConfUiMessage<ConfUiCmd::kUserInputEvent>(confui_packet);
    case ConfUiCmd::kUserTouchEvent:
      return ToConfUiMessage<ConfUiCmd::kUserTouchEvent>(confui_packet);
      // default ConfUiMessage with session & type only
    case ConfUiCmd::kAbort:
      return ToConfUiMessage<ConfUiCmd::kAbort>(confui_packet);
    case ConfUiCmd::kStop:
      return ToConfUiMessage<ConfUiCmd::kStop>(confui_packet);
      // these are errors
    case ConfUiCmd::kUnknown:
    default:
      ConfUiLog(ERROR) << "ConfUiCmd value is not good for ToConfUiMessage: "
                       << ToString(confui_cmd);
      break;
  }
  return {nullptr};
}
}  // end of unnamed namespace

std::string ToString(const ConfUiMessage& msg) { return msg.ToString(); }

std::unique_ptr<ConfUiMessage> RecvConfUiMsg(SharedFD fd) {
  if (!fd->IsOpen()) {
    ConfUiLog(ERROR) << "file, socket, etc, is not open to read";
    return {nullptr};
  }
  auto confui_packet_opt = packet::ReadPayload(fd);
  if (!confui_packet_opt) {
    ConfUiLog(ERROR) << "ReadPayload returns but with std::nullptr";
    return {nullptr};
  }

  auto confui_packet = confui_packet_opt.value();
  return ToConfUiMessage(confui_packet);
}

std::unique_ptr<ConfUiMessage> RecvConfUiMsg(const std::string& session_id,
                                             SharedFD fd) {
  auto conf_ui_msg = RecvConfUiMsg(fd);
  if (!conf_ui_msg) {
    return {nullptr};
  }
  auto recv_session_id = conf_ui_msg->GetSessionId();
  if (session_id != recv_session_id) {
    ConfUiLog(ERROR) << "Received Session ID (" << recv_session_id
                     << ") is not the expected one (" << session_id << ")";
    return {nullptr};
  }
  return conf_ui_msg;
}

bool SendAbortCmd(SharedFD fd, const std::string& session_id) {
  ConfUiGenericMessage<ConfUiCmd::kAbort> confui_msg{session_id};
  return confui_msg.SendOver(fd);
}

bool SendStopCmd(SharedFD fd, const std::string& session_id) {
  ConfUiGenericMessage<ConfUiCmd::kStop> confui_msg{session_id};
  return confui_msg.SendOver(fd);
}

bool SendAck(SharedFD fd, const std::string& session_id, const bool is_success,
             const std::string& status_message) {
  ConfUiAckMessage confui_msg{session_id, is_success, status_message};
  return confui_msg.SendOver(fd);
}

bool SendResponse(SharedFD fd, const std::string& session_id,
                  const UserResponse::type& plain_selection,
                  const std::vector<std::uint8_t>& signed_response,
                  const std::vector<std::uint8_t>& message) {
  ConfUiCliResponseMessage confui_msg{session_id, plain_selection,
                                      signed_response, message};
  return confui_msg.SendOver(fd);
}

bool SendStartCmd(SharedFD fd, const std::string& session_id,
                  const std::string& prompt_text,
                  const std::vector<std::uint8_t>& extra_data,
                  const std::string& locale,
                  const std::vector<teeui::UIOption>& ui_opts) {
  ConfUiStartMessage confui_msg{session_id, prompt_text, extra_data, locale,
                                ui_opts};
  return confui_msg.SendOver(fd);
}

// this is only for deliverSecureInputEvent
bool SendUserSelection(SharedFD fd, const std::string& session_id,
                       const UserResponse::type& confirm_cancel) {
  ConfUiUserSelectionMessage confui_msg{session_id, confirm_cancel};
  return confui_msg.SendOver(fd);
}

// specialized ToConfUiMessage()
namespace {
template <>
std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kCliAck>(
    const packet::ParsedPacket& message) {
  auto type = ToCmd(message.type_);
  auto& contents = message.additional_info_;
  if (type != ConfUiCmd::kCliAck) {
    ConfUiLog(ERROR) << "Received cmd is not ack but " << ToString(type);
    return {nullptr};
  }

  if (contents.size() != 2) {
    ConfUiLog(ERROR)
        << "Ack message should only have pass/fail and a status message";
    return {nullptr};
  }

  const std::string success_str(contents[0].begin(), contents[0].end());
  const bool is_success = (success_str == "success");
  const std::string status_message(contents[1].begin(), contents[1].end());
  return std::make_unique<ConfUiAckMessage>(message.session_id_, is_success,
                                            status_message);
}

template <>
std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kStart>(
    const packet::ParsedPacket& message) {
  /*
   * additional_info_[0]: prompt text
   * additional_info_[1]: extra data
   * additional_info_[2]: locale
   * additional_info_[3]: UIOptions
   *
   */
  if (message.additional_info_.size() < 3) {
    ConfUiLog(ERROR) << "ConfUiMessage for kStart is ill-formatted: "
                     << packet::ToString(message);
    return {nullptr};
  }
  std::vector<teeui::UIOption> ui_opts;
  bool has_ui_option = (message.additional_info_.size() == 4) &&
                       !(message.additional_info_[3].empty());
  if (has_ui_option) {
    std::string ui_opts_string{message.additional_info_[3].begin(),
                               message.additional_info_[3].end()};
    auto tokens = android::base::Split(ui_opts_string, ",");
    for (auto token : tokens) {
      auto ui_opt_optional = ToUiOption(token);
      if (!ui_opt_optional) {
        ConfUiLog(ERROR) << "Wrong UiOption String : " << token;
        return {nullptr};
      }
      ui_opts.emplace_back(ui_opt_optional.value());
    }
  }
  auto sm = std::make_unique<ConfUiStartMessage>(
      message.session_id_,
      std::string(message.additional_info_[0].begin(),
                  message.additional_info_[0].end()),
      message.additional_info_[1],
      std::string(message.additional_info_[2].begin(),
                  message.additional_info_[2].end()),
      ui_opts);
  return sm;
}

template <>
std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kUserInputEvent>(
    const packet::ParsedPacket& message) {
  if (message.additional_info_.empty()) {
    ConfUiLog(ERROR)
        << "kUserInputEvent message should have at least one additional_info_";
    return {nullptr};
  }
  auto response = std::string{message.additional_info_[0].begin(),
                              message.additional_info_[0].end()};
  return std::make_unique<ConfUiUserSelectionMessage>(message.session_id_,
                                                      response);
}

template <>
std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kUserTouchEvent>(
    const packet::ParsedPacket& message) {
  if (message.additional_info_.size() < 2) {
    ConfUiLog(ERROR)
        << "kUserTouchEvent message should have at least two additional_info_";
    return {nullptr};
  }
  auto x = std::string(message.additional_info_[0].begin(),
                       message.additional_info_[0].end());
  auto y = std::string(message.additional_info_[1].begin(),
                       message.additional_info_[1].end());
  return std::make_unique<ConfUiUserTouchMessage>(message.session_id_,
                                                  std::stoi(x), std::stoi(y));
}

template <>
std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kCliRespond>(
    const packet::ParsedPacket& message) {
  if (message.additional_info_.size() < 3) {
    ConfUiLog(ERROR)
        << "kCliRespond message should have at least two additional info";
    return {nullptr};
  }
  auto response = std::string{message.additional_info_[0].begin(),
                              message.additional_info_[0].end()};
  auto sign = message.additional_info_[1];
  auto msg = message.additional_info_[2];
  return std::make_unique<ConfUiCliResponseMessage>(message.session_id_,
                                                    response, sign, msg);
}
}  // end of unnamed namespace
}  // end of namespace confui
}  // end of namespace cuttlefish
