/* Copyright (C) 2006 MySQL AB
   Copyright (c) 2011, 2020, MariaDB Corporation Ab

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */

/* Page cache variable structures */

#ifndef _ma_pagecache_h
#define _ma_pagecache_h
C_MODE_START

#include "ma_loghandler_lsn.h"
#include <m_string.h>
#include <hash.h>

/* Type of the page */
enum pagecache_page_type
{
  /*
    Used only for control page type changing during debugging. This define
    should only be using when using DBUG.
  */
  PAGECACHE_EMPTY_PAGE,
  /* the page does not contain LSN */
  PAGECACHE_PLAIN_PAGE,
  /* the page contain LSN (maria tablespace page) */
  PAGECACHE_LSN_PAGE,
  /* Page type used when scanning file and we don't care about the type */
  PAGECACHE_READ_UNKNOWN_PAGE
};

/*
  This enum describe lock status changing. every type of page cache will
  interpret WRITE/READ lock as it need.
*/
enum pagecache_page_lock
{
  PAGECACHE_LOCK_LEFT_UNLOCKED,       /* free  -> free  */
  PAGECACHE_LOCK_LEFT_READLOCKED,     /* read  -> read  */
  PAGECACHE_LOCK_LEFT_WRITELOCKED,    /* write -> write */
  PAGECACHE_LOCK_READ,                /* free  -> read  */
  PAGECACHE_LOCK_WRITE,               /* free  -> write */
  PAGECACHE_LOCK_READ_UNLOCK,         /* read  -> free  */
  PAGECACHE_LOCK_WRITE_UNLOCK,        /* write -> free  */
  PAGECACHE_LOCK_WRITE_TO_READ        /* write -> read  */
};
/*
  This enum describe pin status changing
*/
enum pagecache_page_pin
{
  PAGECACHE_PIN_LEFT_PINNED,   /* pinned   -> pinned   */
  PAGECACHE_PIN_LEFT_UNPINNED, /* unpinned -> unpinned */
  PAGECACHE_PIN,               /* unpinned -> pinned   */
  PAGECACHE_UNPIN              /* pinned   -> unpinned */
};
/* How to write the page */
enum pagecache_write_mode
{
  /* do not write immediately, i.e. it will be dirty page */
  PAGECACHE_WRITE_DELAY,
  /* page already is in the file. (key cache insert analogue) */
  PAGECACHE_WRITE_DONE
};

/* page number for maria */
typedef ulonglong pgcache_page_no_t;

/* args for read/write hooks */
typedef struct st_pagecache_io_hook_args
{
  uchar * page;
  pgcache_page_no_t pageno;
  uchar * data;

  uchar *crypt_buf; /* when using encryption */
} PAGECACHE_IO_HOOK_ARGS;

struct st_pagecache;

/* Structure to store things from get_object */

typedef struct st_S3_BLOCK
{
  uchar *str, *alloc_ptr;
  size_t length;
} S3_BLOCK;


/* file descriptor for Maria */
typedef struct st_pagecache_file
{
  /* Number of pages in the header which are not read with big blocks */
  size_t head_blocks;
  /* size of a big block for S3 or 0 */
  size_t big_block_size;
  /* File number */
  File file;

  /** Cannot be NULL */
  my_bool (*pre_read_hook)(PAGECACHE_IO_HOOK_ARGS *args);
  my_bool (*post_read_hook)(int error, PAGECACHE_IO_HOOK_ARGS *args);

  /** Cannot be NULL */
  my_bool (*pre_write_hook)(PAGECACHE_IO_HOOK_ARGS *args);
  void (*post_write_hook)(int error, PAGECACHE_IO_HOOK_ARGS *args);

  my_bool (*flush_log_callback)(PAGECACHE_IO_HOOK_ARGS *args);

  /** Cannot be NULL */
  uchar *callback_data;
} PAGECACHE_FILE;

/* declare structures that is used by  st_pagecache */

struct st_pagecache_block_link;
typedef struct st_pagecache_block_link PAGECACHE_BLOCK_LINK;
struct st_pagecache_page;
typedef struct st_pagecache_page PAGECACHE_PAGE;
struct st_pagecache_hash_link;
typedef struct st_pagecache_hash_link PAGECACHE_HASH_LINK;

#include <wqueue.h>

/* Default size of hash for changed files */
#define MIN_PAGECACHE_CHANGED_BLOCKS_HASH_SIZE 512

#define PAGECACHE_PRIORITY_LOW 0
#define PAGECACHE_PRIORITY_DEFAULT 3
#define PAGECACHE_PRIORITY_HIGH 6

/*
  The page cache structure
  It also contains read-only statistics parameters.
*/

typedef struct st_pagecache
{
  size_t mem_size;               /* specified size of the cache memory       */
  size_t min_warm_blocks;        /* min number of warm blocks;               */
  size_t age_threshold;          /* age threshold for hot blocks             */
  ulonglong time;                /* total number of block link operations    */
  size_t hash_entries;           /* max number of entries in the hash table  */
  size_t changed_blocks_hash_size;/* Number of hash buckets for file blocks   */
  ssize_t hash_links;            /* max number of hash links                 */
  ssize_t hash_links_used;       /* number of hash links taken from free links pool */
  ssize_t disk_blocks;           /* max number of blocks in the cache        */
  size_t blocks_used;            /* maximum number of concurrently used blocks */
  size_t blocks_unused;          /* number of currently unused blocks        */
  size_t blocks_changed;         /* number of currently dirty blocks         */
  size_t warm_blocks;            /* number of blocks in warm sub-chain       */
  size_t cnt_for_resize_op;      /* counter to block resize operation        */
  size_t blocks_available;       /* number of blocks available in the LRU chain */
  ssize_t blocks;                /* max number of blocks in the cache        */
  uint32 block_size;             /* size of the page buffer of a cache block */
  PAGECACHE_HASH_LINK **hash_root;/* arr. of entries into hash table buckets */
  PAGECACHE_HASH_LINK *hash_link_root;/* memory for hash table links         */
  PAGECACHE_HASH_LINK *free_hash_list;/* list of free hash links             */
  PAGECACHE_BLOCK_LINK *free_block_list;/* list of free blocks               */
  PAGECACHE_BLOCK_LINK *block_root;/* memory for block links                 */
  uchar *block_mem;              /* memory for block buffers                 */
  PAGECACHE_BLOCK_LINK *used_last;/* ptr to the last block of the LRU chain  */
  PAGECACHE_BLOCK_LINK *used_ins;/* ptr to the insertion block in LRU chain  */
  mysql_mutex_t cache_lock;      /* to lock access to the cache structure    */
  WQUEUE resize_queue; /* threads waiting during resize operation  */
  WQUEUE waiting_for_hash_link;/* waiting for a free hash link     */
  WQUEUE waiting_for_block;   /* requests waiting for a free block */
  /* hash for dirty file bl.*/
  PAGECACHE_BLOCK_LINK **changed_blocks;
  /* hash for other file bl.*/
  PAGECACHE_BLOCK_LINK **file_blocks;

  /**
    Function for reading file in big hunks from S3
    Data will be filled with pointer and length to data read
    start_page will be contain first page read.
  */
  my_bool (*big_block_read)(struct st_pagecache *pagecache,
                            PAGECACHE_IO_HOOK_ARGS *args,
                            struct st_pagecache_file *file, S3_BLOCK *data);
  void (*big_block_free)(S3_BLOCK *data);


  /*
    The following variables are and variables used to hold parameters for
    initializing the key cache.
  */

  ulonglong param_buff_size;    /* size the memory allocated for the cache  */
  size_t param_block_size;       /* size of the blocks in the key cache      */
  size_t param_division_limit;   /* min. percentage of warm blocks           */
  size_t param_age_threshold;    /* determines when hot block is downgraded  */

  /* Statistics variables. These are reset in reset_pagecache_counters().    */
  size_t global_blocks_changed;	/* number of currently dirty blocks          */
  ulonglong global_cache_w_requests;/* number of write requests (write hits) */
  ulonglong global_cache_write;     /* number of writes from cache to files  */
  ulonglong global_cache_r_requests;/* number of read requests (read hits)   */
  ulonglong global_cache_read;      /* number of reads from files to cache   */

  uint shift;                       /* block size = 2 ^ shift                */
  myf  readwrite_flags;             /* Flags to pread/pwrite() */
  myf  org_readwrite_flags;         /* Flags to pread/pwrite() at init */
  my_bool inited;
  my_bool resize_in_flush;       /* true during flush of resize operation    */
  my_bool can_be_used;           /* usage of cache for read/write is allowed */
  my_bool in_init;		/* Set to 1 in MySQL during init/resize     */
  my_bool extra_debug;	        /* set to 1 if one wants extra logging */
  HASH    files_in_flush;       /**< files in flush_pagecache_blocks_int() */
} PAGECACHE;

/** @brief Return values for PAGECACHE_FLUSH_FILTER */
enum pagecache_flush_filter_result
{
  FLUSH_FILTER_SKIP_TRY_NEXT= 0,/**< skip page and move on to next one */
  FLUSH_FILTER_OK,              /**< flush page and move on to next one */
  FLUSH_FILTER_SKIP_ALL         /**< skip page and all next ones */
};
/** @brief a filter function type for flush_pagecache_blocks_with_filter() */
typedef enum pagecache_flush_filter_result
(*PAGECACHE_FLUSH_FILTER)(enum pagecache_page_type type,
                          pgcache_page_no_t page,
                          LSN rec_lsn, void *arg);

/* The default key cache */
extern PAGECACHE dflt_pagecache_var, *dflt_pagecache;

extern size_t init_pagecache(PAGECACHE *pagecache, size_t use_mem,
                            uint division_limit, uint age_threshold,
                            uint block_size, uint changed_blocks_hash_size,
                            myf my_read_flags)__attribute__((visibility("default"))) ;
extern size_t resize_pagecache(PAGECACHE *pagecache,
                              size_t use_mem, uint division_limit,
                              uint age_threshold, uint changed_blocks_hash_size);
extern void change_pagecache_param(PAGECACHE *pagecache, uint division_limit,
                                   uint age_threshold);

extern uchar *pagecache_read(PAGECACHE *pagecache,
                             PAGECACHE_FILE *file,
                             pgcache_page_no_t pageno,
                             uint level,
                             uchar *buff,
                             enum pagecache_page_type type,
                             enum pagecache_page_lock lock,
                             PAGECACHE_BLOCK_LINK **link);

#define  pagecache_write(P,F,N,L,B,T,O,I,M,K,R) \
   pagecache_write_part(P,F,N,L,B,T,O,I,M,K,R,0,(P)->block_size)

#define  pagecache_inject(P,F,N,L,B,T,O,I,K,R) \
   pagecache_write_part(P,F,N,L,B,T,O,I,PAGECACHE_WRITE_DONE, \
                        K,R,0,(P)->block_size)

extern my_bool pagecache_write_part(PAGECACHE *pagecache,
                                    PAGECACHE_FILE *file,
                                    pgcache_page_no_t pageno,
                                    uint level,
                                    uchar *buff,
                                    enum pagecache_page_type type,
                                    enum pagecache_page_lock lock,
                                    enum pagecache_page_pin pin,
                                    enum pagecache_write_mode write_mode,
                                    PAGECACHE_BLOCK_LINK **link,
                                    LSN first_REDO_LSN_for_page,
                                    uint offset,
                                    uint size);
extern void pagecache_unlock(PAGECACHE *pagecache,
                             PAGECACHE_FILE *file,
                             pgcache_page_no_t pageno,
                             enum pagecache_page_lock lock,
                             enum pagecache_page_pin pin,
                             LSN first_REDO_LSN_for_page,
                             LSN lsn, my_bool was_changed);
extern void pagecache_unlock_by_link(PAGECACHE *pagecache,
                                     PAGECACHE_BLOCK_LINK *block,
                                     enum pagecache_page_lock lock,
                                     enum pagecache_page_pin pin,
                                     LSN first_REDO_LSN_for_page,
                                     LSN lsn, my_bool was_changed,
                                     my_bool any);
extern void pagecache_unpin(PAGECACHE *pagecache,
                            PAGECACHE_FILE *file,
                            pgcache_page_no_t pageno,
                            LSN lsn);
extern void pagecache_unpin_by_link(PAGECACHE *pagecache,
                                    PAGECACHE_BLOCK_LINK *link,
                                    LSN lsn);
extern void pagecache_set_write_on_delete_by_link(PAGECACHE_BLOCK_LINK *block);


/* Results of flush operation (bit field in fact) */

/* The flush is done. */
#define PCFLUSH_OK 0
/* There was errors during the flush process. */
#define PCFLUSH_ERROR 1
/* Pinned blocks was met and skipped. */
#define PCFLUSH_PINNED 2
/* PCFLUSH_ERROR and PCFLUSH_PINNED. */
#define PCFLUSH_PINNED_AND_ERROR (PCFLUSH_ERROR|PCFLUSH_PINNED)

// initialize file with empty hooks
void pagecache_file_set_null_hooks(PAGECACHE_FILE*);

#define flush_pagecache_blocks(A,B,C)                   \
  flush_pagecache_blocks_with_filter(A,B,C,NULL,NULL)
extern int flush_pagecache_blocks_with_filter(PAGECACHE *keycache,
                                              PAGECACHE_FILE *file,
                                              enum flush_type type,
                                              PAGECACHE_FLUSH_FILTER filter,
                                              void *filter_arg)__attribute__((visibility("default"))) ;
extern my_bool pagecache_delete(PAGECACHE *pagecache,
                                PAGECACHE_FILE *file,
                                pgcache_page_no_t pageno,
                                enum pagecache_page_lock lock,
                                my_bool flush);
extern my_bool pagecache_delete_by_link(PAGECACHE *pagecache,
					PAGECACHE_BLOCK_LINK *link,
					enum pagecache_page_lock lock,
					my_bool flush);
extern my_bool pagecache_delete_pages(PAGECACHE *pagecache,
                                      PAGECACHE_FILE *file,
                                      pgcache_page_no_t pageno,
                                      uint page_count,
                                      enum pagecache_page_lock lock,
                                      my_bool flush);
extern void end_pagecache(PAGECACHE *keycache, my_bool cleanup)__attribute__((visibility("default"))) ;
extern my_bool pagecache_collect_changed_blocks_with_lsn(PAGECACHE *pagecache,
                                                         LEX_STRING *str,
                                                         LSN *min_lsn);
extern int reset_pagecache_counters(const char *name, PAGECACHE *pagecache);
extern uchar *pagecache_block_link_to_buffer(PAGECACHE_BLOCK_LINK *block);

extern uint pagecache_pagelevel(PAGECACHE_BLOCK_LINK *block);
extern void pagecache_add_level_by_link(PAGECACHE_BLOCK_LINK *block,
					uint level);

/* Functions to handle multiple key caches */
extern my_bool multi_pagecache_init(void);
extern void multi_pagecache_free(void);
extern PAGECACHE *multi_pagecache_search(uchar *key, uint length,
                                         PAGECACHE *def);
extern my_bool multi_pagecache_set(const uchar *key, uint length,
				   PAGECACHE *pagecache);
extern void multi_pagecache_change(PAGECACHE *old_data,
				   PAGECACHE *new_data);
#ifndef DBUG_OFF
void pagecache_file_no_dirty_page(PAGECACHE *pagecache, PAGECACHE_FILE *file);
#else
#define pagecache_file_no_dirty_page(A,B) {}
#endif

C_MODE_END
#endif /* _keycache_h */
