/** @file

  traffic_ctl

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you 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 "ts/ink_platform.h"
#include "ts/Diags.h"
#include "ts/ink_memory.h"
#include "mgmtapi.h"
#include "ts/ink_args.h"
#include "ts/I_Version.h"
#include "ts/BaseLogFile.h"

#include <vector>
#include <string>

struct subcommand {
  int (*handler)(unsigned, const char **);
  const char *name;
  const char *help;
};

extern AppVersionInfo CtrlVersionInfo;

#define CtrlDebug(...) Debug("traffic_ctl", __VA_ARGS__)

void CtrlMgmtError(TSMgmtError err, const char *fmt, ...) TS_PRINTFLIKE(2, 3);
int CtrlSubcommandUsage(const char *name, const subcommand *cmds, unsigned ncmds, const ArgumentDescription *desc, unsigned ndesc);
int CtrlCommandUsage(const char *msg, const ArgumentDescription *desc = nullptr, unsigned ndesc = 0);

bool CtrlProcessArguments(int argc, const char **argv, const ArgumentDescription *desc, unsigned ndesc);
int CtrlUnimplementedCommand(unsigned argc, const char **argv);

int CtrlGenericSubcommand(const char *, const subcommand *cmds, unsigned ncmds, unsigned argc, const char **argv);

#define CTRL_MGMT_CHECK(expr)                          \
  do {                                                 \
    TSMgmtError e = (expr);                            \
    if (e != TS_ERR_OKAY) {                            \
      CtrlDebug("%s failed with status %d", #expr, e); \
      CtrlMgmtError(e, nullptr);                       \
      return CTRL_EX_ERROR;                            \
    }                                                  \
  } while (0)

struct CtrlMgmtRecord {
  explicit CtrlMgmtRecord(TSRecordEle *e) : ele(e) {}
  CtrlMgmtRecord() : ele(TSRecordEleCreate()) {}
  ~CtrlMgmtRecord()
  {
    if (this->ele) {
      TSRecordEleDestroy(this->ele);
    }
  }

  TSMgmtError fetch(const char *);

  const char *name() const;
  TSRecordT type() const;
  int rclass() const;
  int64_t as_int() const;

private:
  CtrlMgmtRecord(const CtrlMgmtRecord &);            // disabled
  CtrlMgmtRecord &operator=(const CtrlMgmtRecord &); // disabled

  TSRecordEle *ele;

  friend struct CtrlMgmtRecordValue;
};

struct CtrlMgmtRecordValue {
  explicit CtrlMgmtRecordValue(const TSRecordEle *);
  explicit CtrlMgmtRecordValue(const CtrlMgmtRecord &);

  CtrlMgmtRecordValue(TSRecordT, TSRecordValueT);
  const char *c_str() const;

private:
  CtrlMgmtRecordValue(const CtrlMgmtRecordValue &);            // disabled
  CtrlMgmtRecordValue &operator=(const CtrlMgmtRecordValue &); // disabled
  void init(TSRecordT, TSRecordValueT);

  TSRecordT rec_type;
  union {
    const char *str;
    char nbuf[32];
  } fmt;
};

struct RecordListPolicy {
  typedef TSRecordEle *entry_type;

  static void
  free(entry_type e)
  {
    TSRecordEleDestroy(e);
  }

  static entry_type
  cast(void *ptr)
  {
    return (entry_type)ptr;
  }
};

template <typename T> struct CtrlMgmtList {
  CtrlMgmtList() : list(TSListCreate()) {}
  ~CtrlMgmtList()
  {
    this->clear();
    TSListDestroy(this->list);
  }

  bool
  empty() const
  {
    return TSListIsEmpty(this->list);
  }

  void
  clear() const
  {
    while (!this->empty()) {
      T::free(T::cast(TSListDequeue(this->list)));
    }
  }

  // Return (ownership of) the next list entry.
  typename T::entry_type
  next()
  {
    return T::cast(TSListDequeue(this->list));
  }

  TSList list;

private:
  CtrlMgmtList(const CtrlMgmtList &);            // disabled
  CtrlMgmtList &operator=(const CtrlMgmtList &); // disabled
};

struct CtrlMgmtRecordList : CtrlMgmtList<RecordListPolicy> {
  TSMgmtError match(const char *);
};

struct CtrlCommandLine {
  CtrlCommandLine() { this->args.push_back(nullptr); }
  void
  init(unsigned argc, const char **argv)
  {
    this->args.clear();
    for (unsigned i = 0; i < argc; ++i) {
      this->args.push_back(argv[i]);
    }

    // Always nullptr-terminate to keep ink_args happy. Note that we adjust arg() accordingly.
    this->args.push_back(nullptr);
  }

  unsigned
  argc()
  {
    return args.size() - 1;
  }

  const char **
  argv()
  {
    return &args[0];
  }

private:
  std::vector<const char *> args;
};

int subcommand_alarm(unsigned argc, const char **argv);
int subcommand_cluster(unsigned argc, const char **argv);
int subcommand_config(unsigned argc, const char **argv);
int subcommand_metric(unsigned argc, const char **argv);
int subcommand_server(unsigned argc, const char **argv);
int subcommand_storage(unsigned argc, const char **argv);
int subcommand_plugin(unsigned argc, const char **argv);

// Exit status codes, following BSD's sysexits(3)
#define CTRL_EX_OK 0
#define CTRL_EX_ERROR 2
#define CTRL_EX_UNIMPLEMENTED 3
#define CTRL_EX_USAGE EX_USAGE
#define CTRL_EX_UNAVAILABLE 69
