// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <lib/devmgr-launcher/launch.h>
#include <lib/driver-integration-test/fixture.h>
#include <lib/zx/vmo.h>
#include <memory.h>
#include <string.h>
#include <zircon/assert.h>
#include <zircon/boot/image.h>
#include <zircon/status.h>

#include <ddk/platform-defs.h>
#include <fbl/algorithm.h>

namespace driver_integration_test {

namespace {

// This board driver knows how to interpret the metadata for which devices to
// spawn.
const zbi_platform_id_t kPlatformId = []() {
  zbi_platform_id_t plat_id = {};
  plat_id.vid = PDEV_VID_TEST;
  plat_id.pid = PDEV_PID_INTEGRATION_TEST;
  strcpy(plat_id.board_name, "driver-integration-test");
  return plat_id;
}();

#define BOARD_REVISION_TEST 42

const zbi_board_info_t kBoardInfo = []() {
  zbi_board_info_t board_info = {};
  board_info.revision = BOARD_REVISION_TEST;
  return board_info;
}();

// This function is responsible for serializing driver data. It must be kept
// updated with the function that deserialized the data. This function
// is TestBoard::FetchAndDeserialize.
zx_status_t GetBootItem(const fbl::Vector<board_test::DeviceEntry>& entries, fbl::String board_name,
                        uint32_t type, uint32_t extra, zx::vmo* out, uint32_t* length) {
  zx::vmo vmo;
  switch (type) {
    case ZBI_TYPE_PLATFORM_ID: {
      zbi_platform_id_t platform_id = kPlatformId;
      if (!board_name.empty()) {
        strncpy(platform_id.board_name, board_name.c_str(), ZBI_BOARD_NAME_LEN - 1);
      }
      zx_status_t status = zx::vmo::create(sizeof(kPlatformId), 0, &vmo);
      if (status != ZX_OK) {
        return status;
      }
      status = vmo.write(&platform_id, 0, sizeof(platform_id));
      if (status != ZX_OK) {
        return status;
      }
      *length = sizeof(platform_id);
      break;
    }
    case ZBI_TYPE_DRV_BOARD_INFO: {
      zbi_board_info_t board_info = kBoardInfo;
      zx_status_t status = zx::vmo::create(sizeof(kBoardInfo), 0, &vmo);
      if (status != ZX_OK) {
        return status;
      }
      status = vmo.write(&board_info, 0, sizeof(board_info));
      if (status != ZX_OK) {
        return status;
      }
      *length = sizeof(board_info);
      break;
    }
    case ZBI_TYPE_DRV_BOARD_PRIVATE: {
      size_t list_size = sizeof(board_test::DeviceList);
      size_t entry_size = entries.size() * sizeof(board_test::DeviceEntry);

      size_t metadata_size = 0;
      for (board_test::DeviceEntry& entry : entries) {
        metadata_size += entry.metadata_size;
      }

      zx_status_t status = zx::vmo::create(list_size + entry_size + metadata_size, 0, &vmo);
      if (status != ZX_OK) {
        return status;
      }

      // Write DeviceList to vmo.
      board_test::DeviceList list{.count = entries.size()};
      status = vmo.write(&list, 0, sizeof(list));
      if (status != ZX_OK) {
        return status;
      }

      // Write DeviceEntries to vmo.
      status = vmo.write(entries.data(), list_size, entry_size);
      if (status != ZX_OK) {
        return status;
      }

      // Write Metadata to vmo.
      size_t write_offset = list_size + entry_size;
      for (board_test::DeviceEntry& entry : entries) {
        status = vmo.write(entry.metadata, write_offset, entry.metadata_size);
        if (status != ZX_OK) {
          return status;
        }
        write_offset += entry.metadata_size;
      }

      *length = static_cast<uint32_t>(list_size + entry_size + metadata_size);
      break;
    }
    default:
      break;
  }
  *out = std::move(vmo);
  return ZX_OK;
}

}  // namespace

__EXPORT
zx_status_t IsolatedDevmgr::Create(IsolatedDevmgr::Args* args, IsolatedDevmgr* out) {
  IsolatedDevmgr devmgr;

  struct Args {
    Args(fbl::Vector<board_test::DeviceEntry> device_list, fbl::String board_name)
        : device_list_(std::move(device_list)), board_name_(board_name) {}
    fbl::Vector<board_test::DeviceEntry> device_list_;
    fbl::String board_name_;
  };
  auto cb_args = std::make_unique<Args>(std::move(args->device_list), args->board_name);

  devmgr_launcher::Args devmgr_args;
  devmgr_args.sys_device_driver = "/boot/driver/platform-bus.so";
  devmgr_args.driver_search_paths.swap(args->driver_search_paths);
  devmgr_args.load_drivers.swap(args->load_drivers);
  devmgr_args.flat_namespace = std::move(args->flat_namespace);
  devmgr_args.boot_args = std::move(args->boot_args);
  devmgr_args.disable_block_watcher = args->disable_block_watcher;
  devmgr_args.no_exit_after_suspend = args->no_exit_after_suspend;
  devmgr_args.get_boot_item = [args = std::move(cb_args)](uint32_t type, uint32_t extra,
                                                          zx::vmo* out, uint32_t* length) {
    return GetBootItem(args->device_list_, args->board_name_, type, extra, out, length);
  };

  zx_status_t status =
      devmgr_integration_test::IsolatedDevmgr::Create(std::move(devmgr_args), &devmgr.devmgr_);
  if (status != ZX_OK) {
    return status;
  }

  *out = std::move(devmgr);
  return ZX_OK;
}

}  // namespace driver_integration_test
