/*
 * 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_VALUE_VISITOR_H
#define AAPT_VALUE_VISITOR_H

#include "ResourceTable.h"
#include "ResourceValues.h"

namespace aapt {

/**
 * Visits a value and invokes the appropriate method based on its type. Does not
 * traverse into compound types. Use ValueVisitor for that.
 */
struct RawValueVisitor {
  virtual ~RawValueVisitor() = default;

  virtual void VisitAny(Value* value) {}
  virtual void VisitItem(Item* value) { VisitAny(value); }
  virtual void Visit(Reference* value) { VisitItem(value); }
  virtual void Visit(RawString* value) { VisitItem(value); }
  virtual void Visit(String* value) { VisitItem(value); }
  virtual void Visit(StyledString* value) { VisitItem(value); }
  virtual void Visit(FileReference* value) { VisitItem(value); }
  virtual void Visit(Id* value) { VisitItem(value); }
  virtual void Visit(BinaryPrimitive* value) { VisitItem(value); }

  virtual void Visit(Attribute* value) { VisitAny(value); }
  virtual void Visit(Style* value) { VisitAny(value); }
  virtual void Visit(Array* value) { VisitAny(value); }
  virtual void Visit(Plural* value) { VisitAny(value); }
  virtual void Visit(Styleable* value) { VisitAny(value); }
};

// NOLINT, do not add parentheses around T.
#define DECL_VISIT_COMPOUND_VALUE(T)                   \
  virtual void Visit(T* value) override { /* NOLINT */ \
    VisitSubValues(value);                             \
  }

/**
 * Visits values, and if they are compound values, visits the components as
 * well.
 */
struct ValueVisitor : public RawValueVisitor {
  // The compiler will think we're hiding an overload, when we actually intend
  // to call into RawValueVisitor. This will expose the visit methods in the
  // super class so the compiler knows we are trying to call them.
  using RawValueVisitor::Visit;

  void VisitSubValues(Attribute* attribute) {
    for (Attribute::Symbol& symbol : attribute->symbols) {
      Visit(&symbol.symbol);
    }
  }

  void VisitSubValues(Style* style) {
    if (style->parent) {
      Visit(&style->parent.value());
    }

    for (Style::Entry& entry : style->entries) {
      Visit(&entry.key);
      entry.value->Accept(this);
    }
  }

  void VisitSubValues(Array* array) {
    for (std::unique_ptr<Item>& item : array->elements) {
      item->Accept(this);
    }
  }

  void VisitSubValues(Plural* plural) {
    for (std::unique_ptr<Item>& item : plural->values) {
      if (item) {
        item->Accept(this);
      }
    }
  }

  void VisitSubValues(Styleable* styleable) {
    for (Reference& reference : styleable->entries) {
      Visit(&reference);
    }
  }

  DECL_VISIT_COMPOUND_VALUE(Attribute);
  DECL_VISIT_COMPOUND_VALUE(Style);
  DECL_VISIT_COMPOUND_VALUE(Array);
  DECL_VISIT_COMPOUND_VALUE(Plural);
  DECL_VISIT_COMPOUND_VALUE(Styleable);
};

/**
 * Do not use directly. Helper struct for dyn_cast.
 */
template <typename T>
struct DynCastVisitor : public RawValueVisitor {
  T* value = nullptr;

  void Visit(T* v) override { value = v; }
};

/**
 * Specialization that checks if the value is an Item.
 */
template <>
struct DynCastVisitor<Item> : public RawValueVisitor {
  Item* value = nullptr;

  void VisitItem(Item* item) override { value = item; }
};

template <typename T>
const T* ValueCast(const Value* value) {
  return ValueCast<T>(const_cast<Value*>(value));
}

/**
 * Returns a valid pointer to T if the Value is of subtype T.
 * Otherwise, returns nullptr.
 */
template <typename T>
T* ValueCast(Value* value) {
  if (!value) {
    return nullptr;
  }
  DynCastVisitor<T> visitor;
  value->Accept(&visitor);
  return visitor.value;
}

inline void VisitAllValuesInPackage(ResourceTablePackage* pkg,
                                    RawValueVisitor* visitor) {
  for (auto& type : pkg->types) {
    for (auto& entry : type->entries) {
      for (auto& config_value : entry->values) {
        config_value->value->Accept(visitor);
      }
    }
  }
}

inline void VisitAllValuesInTable(ResourceTable* table,
                                  RawValueVisitor* visitor) {
  for (auto& pkg : table->packages) {
    VisitAllValuesInPackage(pkg.get(), visitor);
  }
}

}  // namespace aapt

#endif  // AAPT_VALUE_VISITOR_H
