/*
  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.
*/
//////////////////////////////////////////////////////////////////////////////////////////////
//
// Declarations for all conditionals / conditional values we support.
//
#pragma once

#include <string>
#include <cstring>

#include "ts/ts.h"
#include "tscore/ink_string.h"

#include "condition.h"
#include "matcher.h"
#include "value.h"
#include "lulu.h"
// #include <mdbm.h>

///////////////////////////////////////////////////////////////////////////////
// Condition declarations.
//

// Always true
class ConditionTrue : public Condition
{
public:
  ConditionTrue() { Dbg(dbg_ctl, "Calling CTOR for ConditionTrue"); }

  // noncopyable
  ConditionTrue(const ConditionTrue &)  = delete;
  void operator=(const ConditionTrue &) = delete;

  void
  append_value(std::string &s, const Resources & /* res ATS_UNUSED */) override
  {
    s += "TRUE";
  }

protected:
  bool
  eval(const Resources & /* res ATS_UNUSED */) override
  {
    Dbg(pi_dbg_ctl, "Evaluating TRUE()");
    return true;
  }
};

// Always false
class ConditionFalse : public Condition
{
public:
  ConditionFalse() { Dbg(dbg_ctl, "Calling CTOR for ConditionFalse"); }

  // noncopyable
  ConditionFalse(const ConditionFalse &) = delete;
  void operator=(const ConditionFalse &) = delete;

  void
  append_value(std::string &s, const Resources & /* res ATS_UNUSED */) override
  {
    s += "FALSE";
  }

protected:
  bool
  eval(const Resources & /* res ATS_UNUSED */) override
  {
    Dbg(pi_dbg_ctl, "Evaluating FALSE()");
    return false;
  }
};

// Check the HTTP return status
class ConditionStatus : public Condition
{
  using MatcherType = Matchers<TSHttpStatus>;

public:
  ConditionStatus() { Dbg(dbg_ctl, "Calling CTOR for ConditionStatus"); }

  // noncopyable
  ConditionStatus(const ConditionStatus &) = delete;
  void operator=(const ConditionStatus &)  = delete;

  void initialize(Parser &p) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;
  void initialize_hooks() override; // Return status only valid in certain hooks
};

// Check the HTTP method
class ConditionMethod : public Condition
{
  using MatcherType = Matchers<std::string>;

public:
  ConditionMethod() { Dbg(dbg_ctl, "Calling CTOR for ConditionMethod"); }

  // noncopyable
  ConditionMethod(const ConditionMethod &) = delete;
  void operator=(const ConditionMethod &)  = delete;

  void initialize(Parser &p) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;
};

// Random 0 to (N-1)
class ConditionRandom : public Condition
{
  using MatcherType = Matchers<unsigned int>;

public:
  ConditionRandom() { Dbg(dbg_ctl, "Calling CTOR for ConditionRandom"); }

  // noncopyable
  ConditionRandom(const ConditionRandom &) = delete;
  void operator=(const ConditionRandom &)  = delete;

  void initialize(Parser &p) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;

private:
  unsigned int _seed = 0;
  unsigned int _max  = 0;
};

// access(file)
class ConditionAccess : public Condition
{
public:
  ConditionAccess() { Dbg(dbg_ctl, "Calling CTOR for ConditionAccess"); }

  // noncopyable
  ConditionAccess(const ConditionAccess &) = delete;
  void operator=(const ConditionAccess &)  = delete;

  void initialize(Parser &p) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;

private:
  time_t _next = 0;
  bool   _last = false;
};

// cookie(name)
class ConditionCookie : public Condition
{
  using MatcherType = Matchers<std::string>;

public:
  ConditionCookie() { Dbg(dbg_ctl, "Calling CTOR for ConditionCookie"); }

  // noncopyable
  ConditionCookie(const ConditionCookie &) = delete;
  void operator=(const ConditionCookie &)  = delete;

  void initialize(Parser &p) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;

private:
  // Nginx-style cookie parsing:
  //   nginx/src/http/ngx_http_parse.c:ngx_http_parse_multi_header_lines()
  inline int
  get_cookie_value(const char *buf, int buf_len, const char *name, int name_len, const char **value, int *value_len)
  {
    const char *start, *last, *end;

    // Sanity
    if (buf == nullptr || name == nullptr || value == nullptr || value_len == nullptr) {
      return TS_ERROR;
    }

    start = buf;
    end   = buf + buf_len;

    while (start < end) {
      if (strncasecmp(start, name, name_len) != 0) {
        goto skip;
      }

      for (start += name_len; start < end && *start == ' '; start++) {}

      if (start == end || *start++ != '=') {
        goto skip;
      }

      while (start < end && *start == ' ') {
        start++;
      }

      for (last = start; last < end && *last != ';'; last++) {}

      *value_len = last - start;
      *value     = start;
      return TS_SUCCESS;
    skip:
      while (start < end) {
        char ch = *start++;
        if (ch == ';' || ch == ',') {
          break;
        }
      }
      while (start < end && *start == ' ') {
        start++;
      }
    }
    return TS_ERROR;
  }
};

// header
class ConditionHeader : public Condition
{
  using MatcherType = Matchers<std::string>;

public:
  explicit ConditionHeader(bool client = false) : _client(client)
  {
    Dbg(dbg_ctl, "Calling CTOR for ConditionHeader, client %d", client);
  }

  // noncopyable
  ConditionHeader(const ConditionHeader &) = delete;
  void operator=(const ConditionHeader &)  = delete;

  void initialize(Parser &p) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;

private:
  bool _client;
};

// url
class ConditionUrl : public Condition
{
  using MatcherType = Matchers<std::string>;

public:
  enum UrlType { CLIENT, URL, FROM, TO };

  explicit ConditionUrl(const UrlType type) : _type(type) { Dbg(dbg_ctl, "Calling CTOR for ConditionUrl"); }

  // noncopyable
  ConditionUrl(const ConditionUrl &)   = delete;
  void operator=(const ConditionUrl &) = delete;

  void initialize(Parser &p) override;
  void set_qualifier(const std::string &q) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;

private:
  UrlQualifiers _url_qual = URL_QUAL_NONE;
  UrlType       _type;
};

// DBM lookups
class ConditionDBM : public Condition
{
  using MatcherType = Matchers<std::string>;

public:
  ConditionDBM()
    : //_dbm(NULL),
      _file(""),
      _mutex(TSMutexCreate())
  {
    Dbg(dbg_ctl, "Calling CTOR for ConditionDBM");
  }

  ~ConditionDBM() override
  {
    // if (_dbm) {
    //   mdbm_close(_dbm);
    //   _dbm = NULL;
    // }
  }

  // noncopyable
  ConditionDBM(const ConditionDBM &)   = delete;
  void operator=(const ConditionDBM &) = delete;

  void initialize(Parser &p) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;

private:
  // MDBM* _dbm;
  std::string _file;
  Value       _key;
  TSMutex     _mutex;
};

class ConditionInternalTxn : public Condition
{
  using MatcherType = Matchers<std::string>;

public:
  void
  append_value(std::string & /* s ATS_UNUSED */, const Resources & /* res ATS_UNUSED */) override
  {
  }

protected:
  bool eval(const Resources &res) override;
};

class ConditionIp : public Condition
{
  using MatcherType   = Matchers<std::string>;
  using MatcherTypeIp = Matchers<const sockaddr *>;

public:
  explicit ConditionIp() { Dbg(dbg_ctl, "Calling CTOR for ConditionIp"); };

  // noncopyable
  ConditionIp(const ConditionIp &)    = delete;
  void operator=(const ConditionIp &) = delete;

  void initialize(Parser &p) override;
  void set_qualifier(const std::string &q) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool
  need_txn_private_slot() const override
  {
    return true;
  }

  bool eval(const Resources &res) override;

private:
  IpQualifiers _ip_qual = IP_QUAL_CLIENT;
};

// Transact Count
class ConditionTransactCount : public Condition
{
  using MatcherType = Matchers<int>;

public:
  ConditionTransactCount() { Dbg(dbg_ctl, "Calling CTOR for ConditionTransactCount"); }

  // noncopyable
  ConditionTransactCount(const ConditionTransactCount &) = delete;
  void operator=(const ConditionTransactCount &)         = delete;

  void initialize(Parser &p) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;
};

// now: Keeping track of current time / day / hour etc.
class ConditionNow : public Condition
{
  using MatcherType = Matchers<int64_t>;

public:
  explicit ConditionNow() { Dbg(dbg_ctl, "Calling CTOR for ConditionNow"); }

  // noncopyable
  ConditionNow(const ConditionNow &)   = delete;
  void operator=(const ConditionNow &) = delete;

  void initialize(Parser &p) override;
  void set_qualifier(const std::string &q) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;

  bool
  need_txn_private_slot() const override
  {
    return true;
  }

private:
  int64_t       get_now_qualified(NowQualifiers qual, const Resources &res) const;
  NowQualifiers _now_qual = NOW_QUAL_EPOCH;
};

// GeoIP class for the "integer" based Geo information pieces
class ConditionGeo : public Condition
{
public:
  explicit ConditionGeo() { Dbg(dbg_ctl, "Calling CTOR for ConditionGeo"); }

  // noncopyable
  ConditionGeo(const ConditionGeo &)   = delete;
  void operator=(const ConditionGeo &) = delete;

  void initialize(Parser &p) override;
  void set_qualifier(const std::string &q) override;
  void append_value(std::string &s, const Resources &res) override;

  // Make sure we know if the type is an int-type or a string.
  bool
  is_int_type() const
  {
    return _int_type;
  }

  void
  is_int_type(bool flag)
  {
    _int_type = flag;
  }

private:
  virtual int64_t     get_geo_int(const sockaddr *addr) const;
  virtual std::string get_geo_string(const sockaddr *addr) const;

protected:
  bool
  need_txn_private_slot() const override
  {
    return true;
  }

  bool          eval(const Resources &res) override;
  GeoQualifiers _geo_qual = GEO_QUAL_COUNTRY;
  bool          _int_type = false;
};

// id: Various identifiers for the requests, server process etc.
class ConditionId : public Condition
{
public:
  explicit ConditionId() { Dbg(dbg_ctl, "Calling CTOR for ConditionId"); };

  // noncopyable
  ConditionId(const ConditionId &)    = delete;
  void operator=(const ConditionId &) = delete;

  void initialize(Parser &p) override;
  void set_qualifier(const std::string &q) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;

private:
  IdQualifiers _id_qual = ID_QUAL_UNIQUE;
};

// cidr: A CIDR masked string representation of the Client's IP.
class ConditionCidr : public Condition
{
  using MatcherType = Matchers<std::string>;
  using self        = ConditionCidr;

public:
  explicit ConditionCidr()
  {
    _create_masks(); // This must be called here, because we might not have parameters specified
    Dbg(dbg_ctl, "Calling CTOR for ConditionCidr");
  };

  ConditionCidr(self &)   = delete;
  self &operator=(self &) = delete;

  void initialize(Parser &p) override;
  void set_qualifier(const std::string &q) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool
  need_txn_private_slot() const override
  {
    return true;
  }

  bool eval(const Resources &res) override;

private:
  void           _create_masks();
  int            _v4_cidr = 24;
  int            _v6_cidr = 48;
  struct in_addr _v4_mask;       // We do a 32-bit & using this mask, for efficiency
  unsigned char  _v6_mask;       // Only need one byte here, since we memset the rest (see next)
  int            _v6_zero_bytes; // How many initial bytes to memset to 0
};

/// Information about the inbound (client) session.
class ConditionInbound : public Condition
{
  using MatcherType   = Matchers<std::string>;
  using MatcherTypeIp = Matchers<const sockaddr *>;
  using self          = ConditionInbound;

public:
  explicit ConditionInbound() { Dbg(dbg_ctl, "Calling CTOR for ConditionInbound"); };
  ConditionInbound(self &) = delete;
  self &operator=(self &)  = delete;

  void initialize(Parser &p) override;
  void set_qualifier(const std::string &q) override;
  void append_value(std::string &s, const Resources &res) override;

  static constexpr const char *TAG = "INBOUND";

protected:
  bool
  need_txn_private_slot() const override
  {
    return true;
  }

  bool eval(const Resources &res) override;

private:
  NetworkSessionQualifiers _net_qual = NET_QUAL_STACK;
#if TS_HAS_CRIPTS
  bool _mtls_cert = false;
#endif
  void append_value(std::string &s, const Resources &res, NetworkSessionQualifiers qual);
};

class ConditionStringLiteral : public Condition
{
  using MatcherType = Matchers<std::string>;

public:
  explicit ConditionStringLiteral(const std::string &v);

  // noncopyable
  ConditionStringLiteral(const ConditionStringLiteral &) = delete;
  void operator=(const ConditionStringLiteral &)         = delete;

  void append_value(std::string &s, const Resources & /* res ATS_UNUSED */) override;

protected:
  bool eval(const Resources & /* res ATS_UNUSED */) override;

private:
  std::string _literal;
};

// Single Session Transaction Count
class ConditionSessionTransactCount : public Condition
{
  using MatcherType = Matchers<int>;

public:
  ConditionSessionTransactCount() { Dbg(dbg_ctl, "ConditionSessionTransactCount()"); }

  // noncopyable
  ConditionSessionTransactCount(const ConditionSessionTransactCount &) = delete;
  void operator=(const ConditionSessionTransactCount &)                = delete;

  void initialize(Parser &p) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;
};

// Tcp Info
class ConditionTcpInfo : public Condition
{
  using MatcherType = Matchers<int>;

public:
  ConditionTcpInfo() { Dbg(dbg_ctl, "Calling CTOR for ConditionTcpInfo"); }

  // noncopyable
  ConditionTcpInfo(const ConditionTcpInfo &) = delete;
  void operator=(const ConditionTcpInfo &)   = delete;

  void initialize(Parser &p) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;
  void initialize_hooks() override; // Return status only valid in certain hooks
};

// Cache Lookup Results
class ConditionCache : public Condition
{
  using MatcherType = Matchers<std::string>;

public:
  ConditionCache() { Dbg(dbg_ctl, "Calling CTOR for ConditionCache"); }

  // noncopyable
  ConditionCache(const ConditionCache &) = delete;
  void operator=(const ConditionCache &) = delete;

  void initialize(Parser &p) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;
};

// Next Hop
class ConditionNextHop : public Condition
{
  using MatcherType = Matchers<std::string>;

public:
  enum HostType { NAME, PORT };

  explicit ConditionNextHop() { Dbg(dbg_ctl, "Calling CTOR for ConditionNextHop"); }

  // noncopyable
  ConditionNextHop(const ConditionNextHop &) = delete;
  void operator=(const ConditionNextHop &)   = delete;

  void initialize(Parser &p) override;
  void set_qualifier(const std::string &q) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;

private:
  NextHopQualifiers _next_hop_qual = NEXT_HOP_NONE;
};

// HTTP CNTL
class ConditionHttpCntl : public Condition
{
public:
  explicit ConditionHttpCntl() { Dbg(dbg_ctl, "Calling CTOR for ConditionHttpCntl"); }

  // noncopyable
  ConditionHttpCntl(const ConditionHttpCntl &) = delete;
  void operator=(const ConditionHttpCntl &)    = delete;

  void set_qualifier(const std::string &q) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;

private:
  TSHttpCntlType _http_cntl_qual = TS_HTTP_CNTL_LOGGING_MODE;
};

class ConditionGroup : public Condition
{
public:
  ConditionGroup() { Dbg(dbg_ctl, "Calling CTOR for ConditionGroup"); }

  ~ConditionGroup() override
  {
    Dbg(dbg_ctl, "Calling DTOR for ConditionGroup");
    delete _cond;
  }

  void
  set_qualifier(const std::string &q) override
  {
    Condition::set_qualifier(q);

    if (!q.empty()) { // Anything goes here, but prefer END
      _end = true;
    }
  }

  // noncopyable
  ConditionGroup(const ConditionGroup &) = delete;
  void operator=(const ConditionGroup &) = delete;

  bool
  closes() const
  {
    return _end;
  }

  void
  append_value(std::string & /* s ATS_UNUSED */, const Resources & /* res ATS_UNUSED */) override
  {
    TSAssert(!"%{GROUP} should never be used as a condition value!");
    TSError("[%s] %%{GROUP} should never be used as a condition value!", PLUGIN_NAME);
  }

  void
  add_condition(Condition *cond)
  {
    if (_cond) {
      _cond->append(cond);
    } else {
      _cond = cond;
    }
  }

  // This can't be protected, because we actually evaluate this condition directly from the Ruleset
  bool
  eval(const Resources &res) override
  {
    Dbg(pi_dbg_ctl, "Evaluating GROUP()");

    if (_cond) {
      return _cond->do_eval(res);
    } else {
      return true;
    }
  }

private:
  Condition *_cond = nullptr; // First pre-condition (linked list)
  bool       _end  = false;
};

// State Flags
class ConditionStateFlag : public Condition
{
public:
  explicit ConditionStateFlag()
  {
    static_assert(sizeof(void *) == 8, "State Variables requires a 64-bit system.");
    Dbg(dbg_ctl, "Calling CTOR for ConditionStateFlag");
  }

  // noncopyable
  ConditionStateFlag(const ConditionStateFlag &) = delete;
  void operator=(const ConditionStateFlag &)     = delete;

  void set_qualifier(const std::string &q) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;

  bool
  need_txn_slot() const override
  {
    return true;
  }

private:
  int      _flag_ix = -1;
  uint64_t _mask    = 0;
};

// INT8 state variables
class ConditionStateInt8 : public Condition
{
  using MatcherType = Matchers<uint8_t>;

public:
  explicit ConditionStateInt8()
  {
    static_assert(sizeof(void *) == 8, "State Variables requires a 64-bit system.");
    Dbg(dbg_ctl, "Calling CTOR for ConditionStateInt8");
  }

  // noncopyable
  ConditionStateInt8(const ConditionStateInt8 &) = delete;
  void operator=(const ConditionStateInt8 &)     = delete;

  void initialize(Parser &p) override;
  void set_qualifier(const std::string &q) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;

  bool
  need_txn_slot() const override
  {
    return true;
  }

private:
  // Little helper function to extract out the data from the TXN user pointer
  uint8_t
  _get_data(const Resources &res) const
  {
    TSAssert(_byte_ix >= 0 && _byte_ix < NUM_STATE_INT8S);
    auto    ptr  = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, _txn_slot));
    uint8_t data = (ptr & STATE_INT8_MASKS[_byte_ix]) >> (NUM_STATE_FLAGS + _byte_ix * 8);

    return data;
  }

  int _byte_ix = -1;
};

// INT16 state variables
class ConditionStateInt16 : public Condition
{
  using MatcherType = Matchers<uint16_t>;

public:
  explicit ConditionStateInt16()
  {
    static_assert(sizeof(void *) == 8, "State Variables requires a 64-bit system.");
    Dbg(dbg_ctl, "Calling CTOR for ConditionStateInt16");
  }

  // noncopyable
  ConditionStateInt16(const ConditionStateInt8 &) = delete;
  void operator=(const ConditionStateInt8 &)      = delete;

  void initialize(Parser &p) override;
  void set_qualifier(const std::string &q) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;

  bool
  need_txn_slot() const override
  {
    return true;
  }

private:
  // Little helper function to extract out the data from the TXN user pointer
  uint16_t
  _get_data(const Resources &res) const
  {
    auto ptr = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, _txn_slot));

    return ((ptr & STATE_INT16_MASK) >> 48);
  }
};

// Last regex capture
class ConditionLastCapture : public Condition
{
  using MatcherType = Matchers<std::string>;

public:
  explicit ConditionLastCapture() { Dbg(dbg_ctl, "Calling CTOR for ConditionLastCapture"); }

  // noncopyable
  ConditionLastCapture(const ConditionLastCapture &) = delete;
  void operator=(const ConditionLastCapture &)       = delete;

  void set_qualifier(const std::string &q) override;
  void append_value(std::string &s, const Resources &res) override;

protected:
  bool eval(const Resources &res) override;

private:
  int _ix = -1;
};
