/*
  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

// Setup for PCRE2
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
#include <vector>
#include <tuple>

#include "cripts/Headers.hpp"
#include "cripts/Lulu.hpp"
#include "swoc/IPRange.h"

namespace cripts::Matcher
{
namespace Range
{
  class IP : public swoc::IPRangeSet
  {
    using super_type = swoc::IPRangeSet;
    using self_type  = Range::IP;

  public:
    IP()                              = delete;
    void operator=(const self_type &) = delete;

    explicit IP(cripts::string_view ip) { Add(ip); }

    IP(self_type const &ip)
    {
      for (auto &it : ip) {
        mark(it);
      }
    }

    IP(const std::initializer_list<self_type> &list)
    {
      for (auto &it : list) {
        for (auto &it2 : it) {
          mark(it2);
        }
      }
    }

    IP(std::initializer_list<cripts::string_view> list)
    {
      for (auto &it : list) {
        Add(it);
      }
    }

    bool
    Match(sockaddr const *target) const
    {
      return contains(swoc::IPAddr(target));
    }

    [[nodiscard]] bool
    Match(in_addr_t target) const
    {
      return contains(swoc::IPAddr(target));
    }

    [[nodiscard]] bool
    Match(swoc::IPAddr const &target) const
    {
      return contains(target);
    }

    [[nodiscard]] bool
    Contains(swoc::IPAddr const &target) const
    {
      return contains(target);
    }

    bool
    Contains(sockaddr const *target) const
    {
      return contains(swoc::IPAddr(target));
    }

    [[nodiscard]] bool
    Contains(in_addr_t target) const
    {
      return contains(swoc::IPAddr(target));
    }

    void
    Add(cripts::string_view str)
    {
      if (swoc::IPRange r; r.load(str)) {
        mark(r);
      } else {
        CFatal("[Matcher::Range::IP] Invalid IP range: %.*s", static_cast<int>(str.size()), str.data());
      }
    }

  }; // End class IP
} // namespace Range

namespace List
{
  class Method : public std::vector<cripts::Header::Method>
  {
#undef DELETE // ToDo: macOS shenanigans here, defining DELETE as a macro

    using super_type = std::vector<cripts::Header::Method>;
    using self_type  = Method;

  public:
    Method() = delete;
    explicit Method(cripts::Header::Method method) : std::vector<cripts::Header::Method>() { push_back(method); }
    void operator=(const self_type &) = delete;

    Method(self_type const &method) : std::vector<cripts::Header::Method>() { insert(end(), std::begin(method), std::end(method)); }

    Method(const std::initializer_list<self_type> &list)
    {
      for (auto &it : list) {
        insert(end(), std::begin(it), std::end(it));
      }
    }

    Method(std::initializer_list<cripts::Header::Method> list)
    {
      for (auto &it : list) {
        push_back(it);
      }
    }

    // Make sure we only allow the cripts::Method::* constants

    [[nodiscard]] bool
    Contains(cripts::Header::Method method) const
    {
      auto data = method.Data();

      return end() != std::find_if(begin(), end(), [&](const cripts::Header::Method &header) { return header.Data() == data; });
    }

    [[nodiscard]] bool
    Match(cripts::Header::Method method) const
    {
      return Contains(method);
    }

  }; // End class Method

} // namespace List

class PCRE
{
public:
  static constexpr size_t MAX_CAPTURES = 32;

  using Regex        = std::tuple<cripts::string, pcre2_code *>;
  using RegexEntries = std::vector<Regex>;

  using self_type = PCRE;

  class Result
  {
  public:
    friend class PCRE;

    Result() = delete;
    Result(cripts::string_view subject) : _subject(subject) {}

    ~Result() { pcre2_match_data_free(_data); }

    explicit
    operator bool() const
    {
      return Matched();
    }

    cripts::string_view
    operator[](size_t ix) const
    {
      cripts::string_view ret;

      if ((Count() > ix) && _ovector) {
        ret = {_subject.substr(_ovector[ix * 2], _ovector[ix * 2 + 1] - _ovector[ix * 2])};
      }

      return ret;
    }

    [[nodiscard]] bool
    Matched() const
    {
      return _match != 0;
    }

    [[nodiscard]] RegexEntries::size_type
    MatchIX() const
    {
      return _match;
    }

    [[nodiscard]] uint32_t
    Count() const
    {
      return _data ? pcre2_get_ovector_count(_data) : 0;
    }

    // The allocator for the PCRE2 contexts, which puts the match data etc. on the stack when used appropriately
    static void *malloc(PCRE2_SIZE size, void *context);

  private:
    RegexEntries::size_type _match   = 0; // The index into the regex vector that match, starting at 1 for the first regex
    pcre2_match_data       *_data    = nullptr;
    PCRE2_SIZE             *_ovector = nullptr;
    PCRE2_SIZE              _ctx_ix  = 0;
    std::byte               _ctx_data[24 * 2 + 96 + 16 * MAX_CAPTURES];
    cripts::string_view     _subject;
  };

  PCRE()                            = default;
  PCRE(const self_type &)           = delete;
  void operator=(const self_type &) = delete;

  PCRE(cripts::string_view regex, uint32_t options = 0) { Add(regex, options); }

  PCRE(std::initializer_list<cripts::string_view> list, uint32_t options = 0)
  {
    for (auto &it : list) {
      Add(it, options);
    }
  }

  ~PCRE();

  void   Add(cripts::string_view regex, uint32_t options = 0, bool jit = true);
  Result Contains(cripts::string_view subject, PCRE2_SIZE offset = 0, uint32_t options = 0);

  Result
  Match(cripts::string_view subject, PCRE2_SIZE offset = 0, uint32_t options = 0)
  {
    return Contains(subject, offset, options);
  }

private:
  RegexEntries _regexes;
};

} // namespace cripts::Matcher
