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

#ifndef AAPT_RESOURCE_TABLE_H
#define AAPT_RESOURCE_TABLE_H

#include <functional>
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>

#include "Resource.h"
#include "ResourceValues.h"
#include "android-base/macros.h"
#include "androidfw/ConfigDescription.h"
#include "androidfw/IDiagnostics.h"
#include "androidfw/Source.h"
#include "androidfw/StringPiece.h"
#include "androidfw/StringPool.h"
#include "io/File.h"

using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;

namespace aapt {

// The Public status of a resource.
struct Visibility {
  enum class Level {
    kUndefined,
    kPrivate,
    kPublic,
  };

  Level level = Level::kUndefined;
  android::Source source;
  std::string comment;

  // Indicates that the resource id may change across builds and that the public R.java identifier
  // for this resource should not be final. This is set to `true` for resources in `staging-group`
  // tags.
  bool staged_api = false;
};

// Represents <add-resource> in an overlay.
struct AllowNew {
  android::Source source;
  std::string comment;
};

// Represents the staged resource id of a finalized resource.
struct StagedId {
  ResourceId id;
  android::Source source;
};

struct Overlayable {
  Overlayable() = default;
  Overlayable(android::StringPiece name, android::StringPiece actor) : name(name), actor(actor) {
  }
  Overlayable(android::StringPiece name, android::StringPiece actor, const android::Source& source)
      : name(name), actor(actor), source(source) {
  }

  static const char* kActorScheme;
  std::string name;
  std::string actor;
  android::Source source;
};

// Represents a declaration that a resource is overlayable at runtime.
struct OverlayableItem {
  explicit OverlayableItem(const std::shared_ptr<Overlayable>& overlayable)
      : overlayable(overlayable) {}
  std::shared_ptr<Overlayable> overlayable;
  PolicyFlags policies = PolicyFlags::NONE;
  std::string comment;
  android::Source source;
};

class ResourceConfigValue {
 public:
  // The configuration for which this value is defined.
  const android::ConfigDescription config;

  // The product for which this value is defined.
  const std::string product;

  // The actual Value.
  std::unique_ptr<Value> value;

  ResourceConfigValue(const android::ConfigDescription& config, android::StringPiece product)
      : config(config), product(product) {
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue);
};

// Represents a resource entry, which may have varying values for each defined configuration.
class ResourceEntry {
 public:
  // The name of the resource. Immutable, as this determines the order of this resource
  // when doing lookups.
  const std::string name;

  // The entry ID for this resource (the EEEE in 0xPPTTEEEE).
  std::optional<ResourceId> id;

  // Whether this resource is public (and must maintain the same entry ID across builds).
  Visibility visibility;

  std::optional<AllowNew> allow_new;

  // The declarations of this resource as overlayable for RROs
  std::optional<OverlayableItem> overlayable_item;

  // The staged resource id for a finalized resource.
  std::optional<StagedId> staged_id;

  // The resource's values for each configuration.
  std::vector<std::unique_ptr<ResourceConfigValue>> values;

  explicit ResourceEntry(android::StringPiece name) : name(name) {
  }

  ResourceConfigValue* FindValue(const android::ConfigDescription& config,
                                 android::StringPiece product = {});
  const ResourceConfigValue* FindValue(const android::ConfigDescription& config,
                                       android::StringPiece product = {}) const;

  ResourceConfigValue* FindOrCreateValue(const android::ConfigDescription& config,
                                         android::StringPiece product);
  std::vector<ResourceConfigValue*> FindAllValues(const android::ConfigDescription& config);

  template <typename Func>
  std::vector<ResourceConfigValue*> FindValuesIf(Func f) {
    std::vector<ResourceConfigValue*> results;
    for (auto& config_value : values) {
      if (f(config_value.get())) {
        results.push_back(config_value.get());
      }
    }
    return results;
  }

  bool HasDefaultValue() const;

 private:
  DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
};

// Represents a resource type (eg. string, drawable, layout, etc.) containing resource entries.
class ResourceTableType {
 public:
  // The logical type of resource (string, drawable, layout, etc.).
  const ResourceNamedType named_type;

  // Whether this type is public (and must maintain the same type ID across builds).
  Visibility::Level visibility_level = Visibility::Level::kUndefined;

  // List of resources for this type.
  std::vector<std::unique_ptr<ResourceEntry>> entries;

  explicit ResourceTableType(const ResourceNamedTypeRef& type)
      : named_type(type.ToResourceNamedType()) {
  }

  ResourceEntry* CreateEntry(android::StringPiece name);
  ResourceEntry* FindEntry(android::StringPiece name) const;
  ResourceEntry* FindOrCreateEntry(android::StringPiece name);

 private:
  DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
};

class ResourceTablePackage {
 public:
  std::string name;

  std::vector<std::unique_ptr<ResourceTableType>> types;

  explicit ResourceTablePackage(android::StringPiece name) : name(name) {
  }

  ResourceTablePackage() = default;
  ResourceTableType* FindTypeWithDefaultName(const ResourceType type) const;
  ResourceTableType* FindType(const ResourceNamedTypeRef& type) const;
  ResourceTableType* FindOrCreateType(const ResourceNamedTypeRef& type);

 private:
  DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
};

struct ResourceTableEntryView {
  std::string name;
  std::optional<uint16_t> id;
  Visibility visibility;
  std::optional<AllowNew> allow_new;
  std::optional<OverlayableItem> overlayable_item;
  std::optional<StagedId> staged_id;
  std::vector<const ResourceConfigValue*> values;

  const ResourceConfigValue* FindValue(const android::ConfigDescription& config,
                                       android::StringPiece product = {}) const;
};

struct ResourceTableTypeView {
  ResourceNamedType named_type;
  std::optional<uint8_t> id;
  Visibility::Level visibility_level = Visibility::Level::kUndefined;

  // Entries sorted in ascending entry id order. If ids have not been assigned, the entries are
  // sorted lexicographically.
  std::vector<ResourceTableEntryView> entries;
};

struct ResourceTablePackageView {
  std::string name;
  std::optional<uint8_t> id;
  // Types sorted in ascending type id order. If ids have not been assigned, the types are sorted by
  // their declaration order in the ResourceType enum.
  std::vector<ResourceTableTypeView> types;
};

struct ResourceTableViewOptions {
  bool create_alias_entries = false;
};

struct ResourceTableView {
  // Packages sorted in ascending package id order. If ids have not been assigned, the packages are
  // sorted lexicographically.
  std::vector<ResourceTablePackageView> packages;
};

enum class OnIdConflict {
  // If the resource entry already exists but has a different resource id, the resource value will
  // not be added to the table.
  ERROR,

  // If the resource entry already exists but has a different resource id, create a new resource
  // with this resource name and id combination.
  CREATE_ENTRY,
};

struct NewResource {
  ResourceName name;
  std::unique_ptr<Value> value;
  android::ConfigDescription config;
  std::string product;
  std::optional<std::pair<ResourceId, OnIdConflict>> id;
  std::optional<Visibility> visibility;
  std::optional<OverlayableItem> overlayable;
  std::optional<AllowNew> allow_new;
  std::optional<StagedId> staged_id;
  bool allow_mangled = false;
};

struct NewResourceBuilder {
  explicit NewResourceBuilder(const ResourceNameRef& name);
  explicit NewResourceBuilder(const std::string& name);
  NewResourceBuilder& SetValue(std::unique_ptr<Value> value, android::ConfigDescription config = {},
                               std::string product = {});
  NewResourceBuilder& SetId(ResourceId id, OnIdConflict on_conflict = OnIdConflict::ERROR);
  NewResourceBuilder& SetVisibility(Visibility id);
  NewResourceBuilder& SetOverlayable(OverlayableItem overlayable);
  NewResourceBuilder& SetAllowNew(AllowNew allow_new);
  NewResourceBuilder& SetStagedId(StagedId id);
  NewResourceBuilder& SetAllowMangled(bool allow_mangled);
  NewResource Build();

 private:
  NewResource res_;
};

// The container and index for all resources defined for an app.
class ResourceTable {
 public:
  enum class Validation {
    kEnabled,
    kDisabled,
  };

  enum class CollisionResult { kKeepBoth, kKeepOriginal, kConflict, kTakeNew };

  ResourceTable() = default;
  explicit ResourceTable(Validation validation);

  bool AddResource(NewResource&& res, android::IDiagnostics* diag);

  // Retrieves a sorted a view of the packages, types, and entries sorted in ascending resource id
  // order.
  ResourceTableView GetPartitionedView(const ResourceTableViewOptions& options = {}) const;

  using ReferencedPackages = std::map<uint8_t, std::string>;
  const ReferencedPackages& GetReferencedPackages() const {
    return included_packages_;
  }

  struct SearchResult {
    ResourceTablePackage* package;
    ResourceTableType* type;
    ResourceEntry* entry;
  };

  std::optional<SearchResult> FindResource(const ResourceNameRef& name) const;
  std::optional<SearchResult> FindResource(const ResourceNameRef& name, ResourceId id) const;
  bool RemoveResource(const ResourceNameRef& name, ResourceId id) const;

  // Returns the package struct with the given name, or nullptr if such a package does not
  // exist. The empty string is a valid package and typically is used to represent the
  // 'current' package before it is known to the ResourceTable.
  ResourceTablePackage* FindPackage(android::StringPiece name) const;
  ResourceTablePackage* FindOrCreatePackage(android::StringPiece name);

  std::unique_ptr<ResourceTable> Clone() const;

  // When a collision of resources occurs, this method decides which value to keep.
  static CollisionResult ResolveValueCollision(Value* existing, Value* incoming);

  // The string pool used by this resource table. Values that reference strings must use
  // this pool to create their strings.
  // NOTE: `string_pool` must come before `packages` so that it is destroyed after.
  // When `string_pool` references are destroyed (as they will be when `packages` is destroyed),
  // they decrement a refCount, which would cause invalid memory access if the pool was already
  // destroyed.
  android::StringPool string_pool;

  // The list of packages in this table, sorted alphabetically by package name and increasing
  // package ID (missing ID being the lowest).
  std::vector<std::unique_ptr<ResourceTablePackage>> packages;

  // Set of dynamic packages that this table may reference. Their package names get encoded
  // into the resources.arsc along with their compile-time assigned IDs.
  ReferencedPackages included_packages_;

 private:
  DISALLOW_COPY_AND_ASSIGN(ResourceTable);

  Validation validation_ = Validation::kEnabled;
};

}  // namespace aapt

#endif  // AAPT_RESOURCE_TABLE_H
