// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#ifndef RUNTIME_BIN_OPTIONS_H_
#define RUNTIME_BIN_OPTIONS_H_

#include "bin/dartutils.h"
#include "platform/globals.h"
#include "platform/hashmap.h"
#include "platform/syslog.h"

namespace dart {
namespace bin {

typedef bool (*OptionProcessorCallback)(const char* arg,
                                        CommandLineOptions* vm_options);

class OptionProcessor {
 public:
  OptionProcessor() : next_(first_) { first_ = this; }

  virtual ~OptionProcessor() {}

  // Returns true if name starts with "--".
  static bool IsValidFlag(const char* name);

  // Returns true if name starts with "-".
  static bool IsValidShortFlag(const char* name);

  virtual bool Process(const char* option, CommandLineOptions* options) = 0;

  static bool TryProcess(const char* option, CommandLineOptions* options);

  static const char* ProcessOption(const char* option, const char* name);

  static bool ProcessEnvironmentOption(const char* arg,
                                       CommandLineOptions* vm_options,
                                       dart::SimpleHashMap** environment);

 private:
  static OptionProcessor* first_;
  OptionProcessor* next_;

  DISALLOW_ALLOCATION();
  DISALLOW_COPY_AND_ASSIGN(OptionProcessor);
};

class CallbackOptionProcessor : public OptionProcessor {
 public:
  explicit CallbackOptionProcessor(OptionProcessorCallback cb) : cb_(cb) {}
  virtual bool Process(const char* option, CommandLineOptions* vm_options) {
    return cb_(option, vm_options);
  }

 private:
  OptionProcessorCallback cb_;
};

#define DEFINE_CB_OPTION(callback)                                             \
  static CallbackOptionProcessor option_##callback(&callback);

#define DEFINE_STRING_OPTION_CB(name, callback)                                \
  class OptionProcessor_##name : public OptionProcessor {                      \
   public:                                                                     \
    virtual bool Process(const char* option, CommandLineOptions* vm_options) { \
      const char* value =                                                      \
          OptionProcessor::ProcessOption(option, "--" #name "=");              \
      if (value == nullptr) {                                                  \
        return false;                                                          \
      }                                                                        \
      if (*value == '\0') {                                                    \
        Syslog::PrintErr("Empty value for option " #name "\n");                \
        return false;                                                          \
      }                                                                        \
      callback;                                                                \
      return true;                                                             \
    }                                                                          \
  };                                                                           \
  static OptionProcessor_##name option_##name;

#define DEFINE_STRING_OPTION(name, variable)                                   \
  DEFINE_STRING_OPTION_CB(name, { variable = value; })

#define DEFINE_ENUM_OPTION(name, enum_name, variable)                          \
  DEFINE_STRING_OPTION_CB(name, {                                              \
    const char* const* kNames = k##enum_name##Names;                           \
    for (intptr_t i = 0; kNames[i] != nullptr; i++) {                          \
      if (strcmp(value, kNames[i]) == 0) {                                     \
        variable = static_cast<enum_name>(i);                                  \
        return true;                                                           \
      }                                                                        \
    }                                                                          \
    Syslog::PrintErr(                                                          \
        "Unrecognized value for " #name ": '%s'\nValid values are: ", value);  \
    for (intptr_t i = 0; kNames[i] != nullptr; i++) {                          \
      Syslog::PrintErr("%s%s", i > 0 ? ", " : "", kNames[i]);                  \
    }                                                                          \
    Syslog::PrintErr("\n");                                                    \
  })

#define DEFINE_BOOL_OPTION_CB(name, callback)                                  \
  class OptionProcessor_##name : public OptionProcessor {                      \
   public:                                                                     \
    virtual bool Process(const char* option, CommandLineOptions* vm_options) { \
      const char* value = OptionProcessor::ProcessOption(option, "--" #name);  \
      if (value == nullptr) {                                                  \
        return false;                                                          \
      }                                                                        \
      if (*value == '=') {                                                     \
        Syslog::PrintErr("Non-empty value for option " #name "\n");            \
        return false;                                                          \
      }                                                                        \
      if (*value != '\0') {                                                    \
        return false;                                                          \
      }                                                                        \
      callback(vm_options);                                                    \
      return true;                                                             \
    }                                                                          \
  };                                                                           \
  static OptionProcessor_##name option_##name;

#define DEFINE_BOOL_OPTION(name, variable)                                     \
  class OptionProcessor_##name : public OptionProcessor {                      \
   public:                                                                     \
    virtual bool Process(const char* option, CommandLineOptions* vm_options) { \
      const char* value = OptionProcessor::ProcessOption(option, "--" #name);  \
      if (value == nullptr) {                                                  \
        return false;                                                          \
      }                                                                        \
      if (*value == '=') {                                                     \
        Syslog::PrintErr("Non-empty value for option " #name "\n");            \
        return false;                                                          \
      }                                                                        \
      if (*value != '\0') {                                                    \
        return false;                                                          \
      }                                                                        \
      variable = true;                                                         \
      return true;                                                             \
    }                                                                          \
  };                                                                           \
  static OptionProcessor_##name option_##name;

#define DEFINE_BOOL_OPTION_SHORT(short_name, long_name, variable)              \
  class OptionProcessor_##long_name : public OptionProcessor {                 \
   public:                                                                     \
    virtual bool Process(const char* option, CommandLineOptions* vm_options) { \
      const char* value =                                                      \
          OptionProcessor::ProcessOption(option, "-" #short_name);             \
      if (value == nullptr) {                                                  \
        value = OptionProcessor::ProcessOption(option, "--" #long_name);       \
      }                                                                        \
      if (value == nullptr) {                                                  \
        return false;                                                          \
      }                                                                        \
      if (*value == '=') {                                                     \
        Syslog::PrintErr("Non-empty value for option " #long_name "\n");       \
        return false;                                                          \
      }                                                                        \
      if (*value != '\0') {                                                    \
        return false;                                                          \
      }                                                                        \
      variable = true;                                                         \
      return true;                                                             \
    }                                                                          \
  };                                                                           \
  static OptionProcessor_##long_name option_##long_name;

}  // namespace bin
}  // namespace dart

#endif  // RUNTIME_BIN_OPTIONS_H_
