/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * 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.
 */

#pragma once

#include <type_traits>

#include <boost/function_types/parameter_types.hpp>
#include <boost/mpl/equal.hpp>
#include <boost/mpl/pop_front.hpp>
#include <boost/mpl/transform.hpp>
#include <boost/mpl/vector.hpp>

#include <folly/Conv.h>
#include <folly/Traits.h>

namespace folly {

// Auto-conversion of key/value based on callback signature, documented in
// DynamicParser.h.
namespace detail {
class IdentifyCallable {
 public:
  enum class Kind { Function, MemberFunction };
  template <typename Fn>
  constexpr static Kind getKind() {
    return test<Fn>(nullptr);
  }

 private:
  template <typename Fn>
  using IsMemFn = std::is_member_pointer<decltype(&Fn::operator())>;
  template <typename Fn>
  constexpr static typename std::enable_if<IsMemFn<Fn>::value, Kind>::type test(
      IsMemFn<Fn>*) {
    return IdentifyCallable::Kind::MemberFunction;
  }
  template <typename>
  constexpr static Kind test(...) {
    return IdentifyCallable::Kind::Function;
  }
};

template <IdentifyCallable::Kind, typename Fn>
struct ArgumentTypesByKind {};
template <typename Fn>
struct ArgumentTypesByKind<IdentifyCallable::Kind::MemberFunction, Fn> {
  using type = typename boost::mpl::template pop_front<
      typename boost::function_types::template parameter_types<
          decltype(&Fn::operator())>::type>::type;
};
template <typename Fn>
struct ArgumentTypesByKind<IdentifyCallable::Kind::Function, Fn> {
  using type = typename boost::function_types::template parameter_types<Fn>;
};

template <typename Fn>
using ArgumentTypes =
    typename ArgumentTypesByKind<IdentifyCallable::getKind<Fn>(), Fn>::type;

// At present, works for lambdas or plain old functions, but can be
// extended.  The comparison deliberately strips cv-qualifiers and
// reference, leaving that choice up to the caller.
template <typename Fn, typename... Args>
struct HasArgumentTypes
    : boost::mpl::template equal<
          typename boost::mpl::
              transform<ArgumentTypes<Fn>, remove_cvref<boost::mpl::_1>>::type,
          boost::mpl::vector<Args...>>::type {};
template <typename... Args>
using EnableForArgTypes =
    typename std::enable_if<HasArgumentTypes<Args...>::value, void>::type;

// No arguments
template <typename Fn>
EnableForArgTypes<Fn> invokeForKeyValue(
    Fn f, const folly::dynamic&, const folly::dynamic&) {
  f();
}

// 1 argument -- pass only the value
//
// folly::dynamic (no conversion)
template <typename Fn>
EnableForArgTypes<Fn, folly::dynamic> invokeForKeyValue(
    Fn fn, const folly::dynamic&, const folly::dynamic& v) {
  fn(v);
}
// int64_t
template <typename Fn>
EnableForArgTypes<Fn, int64_t> invokeForKeyValue(
    Fn fn, const folly::dynamic&, const folly::dynamic& v) {
  fn(v.asInt());
}
// bool
template <typename Fn>
EnableForArgTypes<Fn, bool> invokeForKeyValue(
    Fn fn, const folly::dynamic&, const folly::dynamic& v) {
  fn(v.asBool());
}
// double
template <typename Fn>
EnableForArgTypes<Fn, double> invokeForKeyValue(
    Fn fn, const folly::dynamic&, const folly::dynamic& v) {
  fn(v.asDouble());
}
// std::string
template <typename Fn>
EnableForArgTypes<Fn, std::string> invokeForKeyValue(
    Fn fn, const folly::dynamic&, const folly::dynamic& v) {
  fn(v.asString());
}

//
// 2 arguments -- pass both the key and the value.
//

// Pass the key as folly::dynamic, without conversion
//
// folly::dynamic, folly::dynamic (no conversion of value, either)
template <typename Fn>
EnableForArgTypes<Fn, folly::dynamic, folly::dynamic> invokeForKeyValue(
    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
  fn(k, v);
}
// folly::dynamic, int64_t
template <typename Fn>
EnableForArgTypes<Fn, folly::dynamic, int64_t> invokeForKeyValue(
    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
  fn(k, v.asInt());
}
// folly::dynamic, bool
template <typename Fn>
EnableForArgTypes<Fn, folly::dynamic, bool> invokeForKeyValue(
    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
  fn(k, v.asBool());
}
// folly::dynamic, double
template <typename Fn>
EnableForArgTypes<Fn, folly::dynamic, double> invokeForKeyValue(
    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
  fn(k, v.asDouble());
}
// folly::dynamic, std::string
template <typename Fn>
EnableForArgTypes<Fn, folly::dynamic, std::string> invokeForKeyValue(
    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
  fn(k, v.asString());
}

// Convert the key to std::string.
//
// std::string, folly::dynamic (no conversion of value)
template <typename Fn>
EnableForArgTypes<Fn, std::string, folly::dynamic> invokeForKeyValue(
    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
  fn(k.asString(), v);
}
// std::string, int64_t
template <typename Fn>
EnableForArgTypes<Fn, std::string, int64_t> invokeForKeyValue(
    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
  fn(k.asString(), v.asInt());
}
// std::string, bool
template <typename Fn>
EnableForArgTypes<Fn, std::string, bool> invokeForKeyValue(
    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
  fn(k.asString(), v.asBool());
}
// std::string, double
template <typename Fn>
EnableForArgTypes<Fn, std::string, double> invokeForKeyValue(
    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
  fn(k.asString(), v.asDouble());
}
// std::string, std::string
template <typename Fn>
EnableForArgTypes<Fn, std::string, std::string> invokeForKeyValue(
    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
  fn(k.asString(), v.asString());
}

// Convert the key to int64_t (good for arrays).
//
// int64_t, folly::dynamic (no conversion of value)
template <typename Fn>
EnableForArgTypes<Fn, int64_t, folly::dynamic> invokeForKeyValue(
    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
  fn(k.asInt(), v);
}
// int64_t, int64_t
template <typename Fn>
EnableForArgTypes<Fn, int64_t, int64_t> invokeForKeyValue(
    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
  fn(k.asInt(), v.asInt());
}
// int64_t, bool
template <typename Fn>
EnableForArgTypes<Fn, int64_t, bool> invokeForKeyValue(
    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
  fn(k.asInt(), v.asBool());
}
// int64_t, double
template <typename Fn>
EnableForArgTypes<Fn, int64_t, double> invokeForKeyValue(
    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
  fn(k.asInt(), v.asDouble());
}
// int64_t, std::string
template <typename Fn>
EnableForArgTypes<Fn, int64_t, std::string> invokeForKeyValue(
    Fn fn, const folly::dynamic& k, const folly::dynamic& v) {
  fn(k.asInt(), v.asString());
}
} // namespace detail

template <typename Fn>
void DynamicParser::optional(const folly::dynamic& key, Fn fn) {
  wrapError(&key, [&]() {
    if (auto vp = value().get_ptr(key)) {
      parse(key, *vp, fn);
    }
  });
}

//
// Implementation of DynamicParser template & inline methods.
//

template <typename Fn>
void DynamicParser::required(const folly::dynamic& key, Fn fn) {
  wrapError(&key, [&]() {
    auto vp = value().get_ptr(key);
    if (!vp) {
      throw std::runtime_error(folly::to<std::string>(
          "Couldn't find key ",
          detail::toPseudoJson(key),
          " in dynamic object"));
    }
    parse(key, *vp, fn);
  });
}

template <typename Fn>
void DynamicParser::objectItems(Fn fn) {
  wrapError(nullptr, [&]() {
    for (const auto& kv : value().items()) { // .items() can throw
      parse(kv.first, kv.second, fn);
    }
  });
}

template <typename Fn>
void DynamicParser::arrayItems(Fn fn) {
  wrapError(nullptr, [&]() {
    size_t i = 0;
    for (const auto& v : value()) { // Iteration can throw
      parse(i, v, fn); // i => dynamic cannot throw
      ++i;
    }
  });
}

template <typename Fn>
void DynamicParser::wrapError(const folly::dynamic* lookup_k, Fn fn) {
  try {
    fn();
  } catch (DynamicParserLogicError&) {
    // When the parser is misused, we throw all the way up to the user,
    // instead of reporting it as if the input is invalid.
    throw;
  } catch (DynamicParserParseError&) {
    // We are just bubbling up a parse error for OnError::THROW.
    throw;
  } catch (const std::exception& ex) {
    reportError(lookup_k, ex);
  }
}

template <typename Fn>
void DynamicParser::parse(
    const folly::dynamic& k, const folly::dynamic& v, Fn fn) {
  auto guard = stack_.push(k, v); // User code can nest parser calls.
  wrapError(nullptr, [&]() { detail::invokeForKeyValue(fn, k, v); });
}

inline const folly::dynamic& DynamicParser::ParserStack::key() const {
  if (!key_) {
    throw DynamicParserLogicError("Only call key() inside parsing callbacks.");
  }
  return *key_;
}

inline const folly::dynamic& DynamicParser::ParserStack::value() const {
  if (!value_) {
    throw DynamicParserLogicError(
        "Parsing nullptr, or parsing after releaseErrors()");
  }
  return *value_;
}

} // namespace folly
