/*
 * 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 <android-base/file.h>
#include <gtest/gtest.h>
#include <idmap2/FabricatedOverlay.h>
#include "TestHelpers.h"

#include <fstream>
#include <utility>

namespace android::idmap2 {

TEST(FabricatedOverlayTests, OverlayInfo) {
  auto overlay =
      FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
          .SetOverlayable("TestResources")
          .Build();

  ASSERT_TRUE(overlay);
  auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
  auto info = container->FindOverlayInfo("SandTheme");
  ASSERT_TRUE(info);
  EXPECT_EQ("SandTheme", (*info).name);
  EXPECT_EQ("TestResources", (*info).target_name);

  info = container->FindOverlayInfo("OceanTheme");
  ASSERT_FALSE(info);
}

TEST(FabricatedOverlayTests, SetResourceValue) {
  auto path = GetTestDataPath() + "/overlay/res/drawable/android.png";
  auto fd = android::base::unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
  ASSERT_TRUE(fd > 0) << "errno " << errno << " for path " << path;

  auto overlay =
      FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
          .SetResourceValue(
              "com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U, "port")
          .SetResourceValue(
              "com.example.target.split:integer/int2", Res_value::TYPE_INT_DEC, 2U, "land")
          .SetResourceValue(
              "string/int3", Res_value::TYPE_REFERENCE, 0x7f010000, "xxhdpi-v7")
          .SetResourceValue(
              "com.example.target:string/string1",
              Res_value::TYPE_STRING,
              "foobar",
              "en-rUS-normal-xxhdpi-v21")
          .SetResourceValue("com.example.target:drawable/dr1", fd, "port-xxhdpi-v7")
          .setFrroPath("/foo/bar/biz.frro")
          .Build();
  ASSERT_TRUE(overlay);
  auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
  auto info = container->FindOverlayInfo("SandTheme");
  ASSERT_TRUE(info);
  ASSERT_TRUE((*info).target_name.empty());

  auto crc = (*container).GetCrc();
  ASSERT_TRUE(crc) << crc.GetErrorMessage();
  EXPECT_NE(0U, *crc);

  auto pairs = container->GetOverlayData(*info);
  ASSERT_TRUE(pairs);
  ASSERT_EQ(5U, pairs->pairs.size());
  auto string_pool = ResStringPool(pairs->string_pool_data->data.get(),
                                        pairs->string_pool_data->data_length, false);

  auto& it = pairs->pairs[0];
  ASSERT_EQ("com.example.target:drawable/dr1", it.resource_name);
  auto entry = std::get_if<TargetValueWithConfig>(&it.value);
  ASSERT_NE(nullptr, entry);
  ASSERT_EQ(std::string("frro://foo/bar/biz.frro?offset=16&size=8341"),
      string_pool.string8At(entry->value.data_value).value_or(""));
  ASSERT_EQ(Res_value::TYPE_STRING, entry->value.data_type);
  ASSERT_EQ("port-xxhdpi-v7", entry->config);

  it = pairs->pairs[1];
  ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
  entry = std::get_if<TargetValueWithConfig>(&it.value);
  ASSERT_NE(nullptr, entry);
  ASSERT_EQ(1U, entry->value.data_value);
  ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->value.data_type);
  ASSERT_EQ("port", entry->config);

  it = pairs->pairs[2];
  ASSERT_EQ("com.example.target:string/int3", it.resource_name);
  entry = std::get_if<TargetValueWithConfig>(&it.value);
  ASSERT_NE(nullptr, entry);
  ASSERT_EQ(0x7f010000, entry->value.data_value);
  ASSERT_EQ(Res_value::TYPE_REFERENCE, entry->value.data_type);
  ASSERT_EQ("xxhdpi-v7", entry->config);

  it = pairs->pairs[3];
  ASSERT_EQ("com.example.target:string/string1", it.resource_name);
  entry = std::get_if<TargetValueWithConfig>(&it.value);
  ASSERT_NE(nullptr, entry);
  ASSERT_EQ(Res_value::TYPE_STRING, entry->value.data_type);
  ASSERT_EQ(std::string("foobar"), string_pool.string8At(entry->value.data_value).value_or(""));
  ASSERT_EQ("en-rUS-normal-xxhdpi-v21", entry->config);

  it = pairs->pairs[4];
  ASSERT_EQ("com.example.target.split:integer/int2", it.resource_name);
  entry = std::get_if<TargetValueWithConfig>(&it.value);
  ASSERT_NE(nullptr, entry);
  ASSERT_EQ(2U, entry->value.data_value);
  ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->value.data_type);
  ASSERT_EQ("land", entry->config);
}

TEST(FabricatedOverlayTests, SetResourceValueBadArgs) {
  {
    auto builder =
        FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
            .SetResourceValue("int1", Res_value::TYPE_INT_DEC, 1U, "");
    ASSERT_FALSE(builder.Build());
  }
  {
    auto builder =
        FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
            .SetResourceValue("com.example.target:int2", Res_value::TYPE_INT_DEC, 1U, "");
    ASSERT_FALSE(builder.Build());
  }
}

TEST(FabricatedOverlayTests, SerializeAndDeserialize) {
  auto overlay =
      FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
          .SetOverlayable("TestResources")
          .SetResourceValue("com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U, "")
          .SetResourceValue(
              "com.example.target:string/string1", Res_value::TYPE_STRING, "foobar", "")
          .Build();
  ASSERT_TRUE(overlay);
  TemporaryFile tf;
  std::ofstream out(tf.path);
  ASSERT_TRUE((*overlay).ToBinaryStream(out));
  out.close();

  auto container = OverlayResourceContainer::FromPath(tf.path);
  ASSERT_TRUE(container) << container.GetErrorMessage();
  EXPECT_EQ(tf.path, (*container)->GetPath());

  auto crc = (*container)->GetCrc();
  ASSERT_TRUE(crc) << crc.GetErrorMessage();
  EXPECT_NE(0U, *crc);

  auto info = (*container)->FindOverlayInfo("SandTheme");
  ASSERT_TRUE(info) << info.GetErrorMessage();
  EXPECT_EQ("SandTheme", (*info).name);
  EXPECT_EQ("TestResources", (*info).target_name);

  auto pairs = (*container)->GetOverlayData(*info);
  ASSERT_TRUE(pairs) << pairs.GetErrorMessage();
  EXPECT_EQ(2U, pairs->pairs.size());
  auto string_pool = ResStringPool(pairs->string_pool_data->data.get(),
                                        pairs->string_pool_data->data_length, false);

  auto& it = pairs->pairs[0];
  ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
  auto entry = std::get_if<TargetValueWithConfig>(&it.value);
  ASSERT_NE(nullptr, entry);
  EXPECT_EQ(1U, entry->value.data_value);
  EXPECT_EQ(Res_value::TYPE_INT_DEC, entry->value.data_type);

  it = pairs->pairs[1];
  ASSERT_EQ("com.example.target:string/string1", it.resource_name);
  entry = std::get_if<TargetValueWithConfig>(&it.value);
  ASSERT_NE(nullptr, entry);
  ASSERT_EQ(Res_value::TYPE_STRING, entry->value.data_type);
  ASSERT_EQ(std::string("foobar"), string_pool.string8At(entry->value.data_value).value_or(""));
}

}  // namespace android::idmap2