/*
  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 "tscore/ink_defs.h"

#include <openssl/sha.h>
#ifndef HAVE_SHA1
#include <openssl/evp.h>
#endif
#include <cstring>
#include <unordered_map>
#include <list>
#include <tuple>

#include "policy.h"

#define MINIMUM_BUCKET_SIZE 10

//////////////////////////////////////////////////////////////////////////////////////////////
// The LRU based policy keeps track of <bucket> number of URLs, with a counter for each slot.
// Objects are not promoted unless the counter reaches <hits> before it gets evicted. An
// optional <chance> parameter can be used to sample hits, this can reduce contention and
// churning in the LRU as well.
//
class LRUHash
{
  friend struct LRUHashHasher;

public:
  LRUHash() { DBG("LRUHash() CTOR"); }
  ~LRUHash() { DBG("~LRUHash() DTOR"); }

  LRUHash(const LRUHash &h)
  {
    DBG("Copy CTOR an LRUHash object");
    memcpy(_hash, h._hash, sizeof(_hash));
  }

  LRUHash &
  operator=(const LRUHash &h)
  {
    DBG("copying an LRUHash object");
    if (this != &h) {
      memcpy(_hash, h._hash, sizeof(_hash));
    }
    return *this;
  }

  // Initialize the hash key from the TXN's URL
  bool initFromUrl(TSHttpTxn txnp);

private:
  u_char _hash[SHA_DIGEST_LENGTH];
};

struct LRUHashHasher {
  bool
  operator()(const LRUHash *s1, const LRUHash *s2) const
  {
    return 0 == memcmp(s1->_hash, s2->_hash, sizeof(s2->_hash));
  }

  size_t
  operator()(const LRUHash *s) const
  {
    return *(reinterpret_cast<const size_t *>(s->_hash)) ^ *(reinterpret_cast<const size_t *>(s->_hash + 9));
  }
};

using LRUEntry = std::tuple<LRUHash, unsigned, int64_t>;
using LRUList  = std::list<LRUEntry>;
using LRUMap   = std::unordered_map<const LRUHash *, LRUList::iterator, LRUHashHasher, LRUHashHasher>;

class LRUPolicy : public PromotionPolicy
{
public:
  LRUPolicy() : PromotionPolicy(), _lock(TSMutexCreate()) {}
  ~LRUPolicy() override;

  bool parseOption(int opt, char *optarg) override;
  bool doPromote(TSHttpTxn txnp) override;
  bool stats_add(const char *remap_id) override;
  void addBytes(TSHttpTxn txnp) override;

  bool
  countBytes() const override
  {
    return _bytes > 0;
  }

  void
  usage() const override
  {
    TSError("[%s] Usage: @plugin=%s.so @pparam=--policy=lru @pparam=--buckets=<m> --hits=<n> --bytes=<o> --sample=<p>", PLUGIN_NAME,
            PLUGIN_NAME);
  }

  const char *
  policyName() const override
  {
    return "LRU";
  }

  const std::string
  id() const override
  {
    if (_label.empty()) {
      return ""; // This will prevent the policy factory from coalescing this policy
    }

    return _label + ";LRU=b:" + std::to_string(_buckets) + ",h:" + std::to_string(_hits) + ",B:" + std::to_string(_bytes) +
           ",i:" + std::to_string(_internal_enabled) + ",e:" + _stats_id;
  }

  void
  cleanup(TSHttpTxn txnp) override
  {
    LRUHash *hash = static_cast<LRUHash *>(TSUserArgGet(txnp, TXN_ARG_IDX));

    // Delete the hash, and remove the pointer from the TXN user arg slot (to be safe)
    if (hash) {
      delete hash;
      TSUserArgSet(txnp, TXN_ARG_IDX, nullptr);
    }
  }

private:
  unsigned    _buckets = 1000;
  unsigned    _hits    = 10;
  int64_t     _bytes   = 0;
  std::string _label   = "";

  // For the LRU. Note that we keep track of the List sizes, because some versions fo STL have broken
  // implementations of size(), making them obsessively slow on calling ::size().
  TSMutex _lock;
  LRUMap  _map;
  LRUList _list, _freelist;
  size_t  _list_size = 0, _freelist_size = 0;

  // internal stats ids
  int _freelist_size_id = -1;
  int _lru_size_id      = -1;
  int _lru_hit_id       = -1;
  int _lru_miss_id      = -1;
  int _lru_vacated_id   = -1;
  int _promoted_id      = -1;
};
