/*	$Id$ */
/*
 * Copyright (c) 2015 Kristaps Dzonsons <kristaps@kcons.eu>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <assert.h>
#include <math.h>
#include <stdint.h>
#include <stdlib.h>

#include <gtk/gtk.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_multifit.h>
#include <gsl/gsl_histogram.h>
#include <kplot.h>

#include "extern.h"

struct simbuf *
simbuf_alloc_warm(struct kdata *hot, size_t bufsz)
{
	struct simbuf	*buf;

	g_assert(NULL != hot);
	buf = g_malloc0(sizeof(struct simbuf));
	buf->cold = kdata_buffer_alloc(bufsz);
	g_assert(NULL != buf->cold);
	return(buf);
}

struct simbuf *
simbuf_alloc(struct kdata *hot, size_t bufsz)
{
	struct simbuf	*buf;

	g_assert(NULL != hot);
	buf = g_malloc0(sizeof(struct simbuf));
	buf->hot = hot;
	buf->hotlsb = kdata_buffer_alloc(bufsz);
	g_assert(NULL != buf->hotlsb);
	buf->warm = kdata_buffer_alloc(bufsz);
	g_assert(NULL != buf->warm);
	buf->cold = kdata_buffer_alloc(bufsz);
	g_assert(NULL != buf->cold);
	return(buf);
}

void
simbuf_free(struct simbuf *buf)
{

	kdata_destroy(buf->hot);
	kdata_destroy(buf->hotlsb);
	kdata_destroy(buf->warm);
	kdata_destroy(buf->cold);
	free(buf);
}

void
simbuf_copy_hotlsb(struct simbuf *buf)
{
	int	rc;

	rc = kdata_buffer_copy(buf->hotlsb, buf->hot);
	g_assert(0 != rc);
}

void
simbuf_copy_warm(struct simbuf *buf)
{
	int	rc;

	rc = kdata_buffer_copy(buf->warm, buf->hotlsb);
	g_assert(0 != rc);
}

void
simbuf_copy_cold(struct simbuf *buf)
{
	int	rc;

	rc = kdata_buffer_copy(buf->cold, buf->warm);
	g_assert(0 != rc);
}


#if !defined(RANDOM_H_INCLUDED)
#define RANDOM_H_INCLUDED

#include <gsl/gsl>
#include <iosfwd>

class Random
{
public:
	static int usage(::std::ostream& strm, const ::std::string& progName,
		const char* pMsg);

	Random(::gsl::span<const char*const> args);
	int run() const;

	Random(const Random&) = delete;
	Random& operator=(const Random&) = delete;
	Random(Random&&) = delete;
	Random& operator=(Random&&) = delete;

private:
	static long getRandomInteger(long low, long high);

	long	m_lowerBound;
	long	m_upperBound;
	long	m_count;
};

#endif // RANDOM_H_INCLUDED


#pragma once

#include <gsl\gsl>
#include <winrt\base.h>
#include <d3d11.h>
#include "DrawableGameComponent.h"
#include "PointSpriteMaterial.h"

namespace Rendering
{
	class PointSpriteDemo final : public Library::DrawableGameComponent
	{
	public:
		PointSpriteDemo(Library::Game& game, const std::shared_ptr<Library::Camera>& camera);
		PointSpriteDemo(const PointSpriteDemo&) = delete;
		PointSpriteDemo(PointSpriteDemo&&) = default;
		PointSpriteDemo& operator=(const PointSpriteDemo&) = default;
		PointSpriteDemo& operator=(PointSpriteDemo&&) = default;
		~PointSpriteDemo();

		virtual void Initialize() override;
		virtual void Draw(const Library::GameTime& gameTime) override;

	private:
		void InitializeRandomPoints();

		PointSpriteMaterial mMaterial;
		winrt::com_ptr<ID3D11Buffer> mVertexBuffer;
		std::size_t mVertexCount{ 0 };
		bool mUpdateMaterial{ true };
	};
}


#include <pygsl/error_helpers.h>
#include <pygsl/utils.h>
#include <gsl/gsl_errno.h>
#include <compile.h>
#include <frameobject.h>

enum handleflag {
     HANDLE_ERROR = 0,
     HANDLE_WARNING
};

static int
PyGSL_internal_error_handler(const char *reason, /* name of function*/
			     const char *file, /*from CPP*/
			     int line,   /*from CPP*/
			     int gsl_error,
			     enum handleflag flag);

static int
PyGSL_error_flag(long flag)
{
     FUNC_MESS_BEGIN();
     if(PyGSL_DEBUG_LEVEL() > 2){
	  fprintf(stderr,"I got an Error %ld\n", flag);
     }
     if(PyErr_Occurred()){
	     DEBUG_MESS(3, "Already a python error registered for flag %ld", flag);
	     return GSL_FAILURE;
     }
     if(flag>0){
	  /*
	   * How can I end here without an Python error?
	   *
	   * 25. October 2008
	   * Well, very simply when the GSL_ERROR_HANDLER is set to off.
	   *
	   * All GSL modules are
	   * supposed to call GSL_ERROR which should call the default error
	   * handler.
	   *
	   * 25. October 2008
	   * No not when the handler is set to off, which is necessary in
	   * a threaded python. That's why it was a bad idea to call the
	   * gsl_error_handler here!
	   */
	  /*
	   * gsl_error("Unknown Reason. It was not set by GSL.",  __FILE__,
	   *	    __LINE__, flag);
	  */
	  PyGSL_internal_error_handler("Unknown Reason. It was not set by GSL",  __FILE__,
				       __LINE__, flag, HANDLE_ERROR);
	  /*
	   * So lets keep the flag to return ... who knows what it will be used for...
	   * return GSL_FAILURE;
	   */
	  return flag;
     }
     FUNC_MESS_END();
     return GSL_SUCCESS;
}

static PyObject *
PyGSL_error_flag_to_pyint(long flag)
{
     PyObject * result = NULL;
     FUNC_MESS_BEGIN();
     if(GSL_FAILURE == PyGSL_error_flag(flag)){
	  return NULL;
     }
     result = PyInt_FromLong((long) flag);
     FUNC_MESS_END();

     return result;
}

static void
PyGSL_add_traceback(PyObject *module, const char *filename, const char *funcname, int lineno)
{
     PyObject *py_srcfile = NULL, *py_funcname = NULL, *py_globals = NULL,
	  *empty_tuple = NULL,  *empty_string = NULL;
     PyCodeObject *py_code = NULL;
     PyFrameObject *py_frame = NULL;

     FUNC_MESS_BEGIN();

     if(filename == NULL)
	  filename = "file ???";
     py_srcfile = PyString_FromString(filename);
     if (py_srcfile == NULL)
	  goto fail;

     if(funcname == NULL)
	  funcname = "function ???";

     py_funcname = PyString_FromString(funcname);
     if (py_funcname == NULL)
	  goto fail;

     /* Use the module if provided */
     if(module == NULL){
	  py_globals = PyDict_New();
     } else {
	  py_globals = PyModule_GetDict(module);
     }
     if (py_globals == NULL)
	  goto fail;

     empty_tuple = PyTuple_New(0);
     if (empty_tuple == NULL)
	  goto fail;

     empty_string = PyString_FromString("");
     if (empty_string == NULL)
	  goto fail;

     py_code = PyCode_New(
	  0,            /*int argcount,*/
	  0,            /*int nlocals,*/
	  0,            /*int stacksize,*/
	  0,            /*int flags,*/
	  empty_string, /*PyObject *code,*/
	  empty_tuple,  /*PyObject *consts,*/
	  empty_tuple,  /*PyObject *names,*/
	  empty_tuple,  /*PyObject *varnames,*/
	  empty_tuple,  /*PyObject *freevars,*/
	  empty_tuple,  /*PyObject *cellvars,*/
	  py_srcfile,   /*PyObject *filename,*/
	  py_funcname,  /*PyObject *name,*/
	  lineno,       /*int firstlineno,*/
	  empty_string  /*PyObject *lnotab*/
	  );
     if (py_code == NULL)
	  goto fail;

     py_frame = PyFrame_New(
	  PyThreadState_Get(), /*PyThreadState *tstate,*/
	  py_code,             /*PyCodeObject *code,*/
	  py_globals,          /*PyObject *globals,*/
	  0                    /*PyObject *locals*/
	  );

     if (py_frame == NULL)
	  goto fail;
     py_frame->f_lineno = lineno;
     PyTraceBack_Here(py_frame);

     FUNC_MESS_END();
     return;

 fail:
     FUNC_MESS("Handling failure");
     Py_XDECREF(py_srcfile);
     Py_XDECREF(py_funcname);
     Py_XDECREF(empty_tuple);
     Py_XDECREF(empty_string);
     Py_XDECREF(py_code);
     Py_XDECREF(py_frame);
}

#define PyGSL_ERRNO_MAX 32
static PyObject * errno_accel[PyGSL_ERRNO_MAX];
static PyObject * error_dict = NULL;
static PyObject * warning_dict = NULL;
static PyObject * unknown_error = NULL;

static void
PyGSL_print_accel_object(void)
{
     int i;
     FUNC_MESS_BEGIN();
     for(i = 0; i<PyGSL_ERRNO_MAX; ++i){
	  DEBUG_MESS(4, "errno_accel[%d] = %p", i, (void*)(errno_accel[i]));
     }
     FUNC_MESS_END();
}

static int
PyGSL_register_accel_err_object(PyObject * err_ob, long test_errno)
{
     PyObject *tmp;

     FUNC_MESS_BEGIN();
     assert(err_ob);
     tmp = errno_accel[test_errno];
     if(tmp != NULL){
	  PyErr_Format(PyExc_ValueError,
		       "In errno_accel: errno %ld already occupied with object %p!\n",
		       test_errno, (void *) tmp);
	  return -2;
     }
     Py_INCREF(err_ob);
     errno_accel[test_errno] = err_ob;
     FUNC_MESS_END();
     return 0;
}

/* register an error object to the Python dictionary */
static int
_PyGSL_register_err_object(PyObject *dict, PyObject * err_ob, PyObject *the_errno)
{
     PyObject *test;

     FUNC_MESS_BEGIN();
     assert(error_dict);
     test = PyDict_GetItem(dict, the_errno);
     if(test != NULL){
	  PyErr_Format(PyExc_ValueError,
		       "In dict %p: key %p already occupied with object %p!\n",
		       dict, the_errno, (void *) test);
	  return -2;
     }
     Py_INCREF(err_ob);
     PyDict_SetItem(dict, the_errno, err_ob);
     FUNC_MESS_END();
     return 0;
}

/* register an error object */
static int
_PyGSL_register_error(PyObject *dict, int errno_max, PyObject * err_ob)
{
     PyObject *tmp, *name;
     long test_errno;
     int flag;
     char * c_name;

     FUNC_MESS_BEGIN();
     assert(err_ob);
     tmp = PyObject_GetAttrString(err_ob, "errno");
     if(tmp == NULL){
	  name = PyObject_GetAttrString(err_ob, "__name__");

	  if(name == NULL)
	       c_name = "unknown name";
	  else if (!PyString_Check(name))
	       c_name = "name not str object!";
	  else
	       c_name = PyString_AsString (name);

	  fprintf(stderr, "failed to get errno from err_ob '%s' @ %p\n",
		  c_name, (void *) err_ob);
	  PyErr_Format(PyExc_AttributeError,
		       "err_ob '%s' @ %p missed attribue 'errno!'\n", c_name,
		       err_ob);
	  return -1;
     }

     if(!PyInt_CheckExact(tmp)){
	  fprintf(stderr, "errno %p from err_ob %p was not an exact int!\n",
		  (void *) tmp, (void *) err_ob);
	  PyErr_Format(PyExc_TypeError, "errno %p from err_ob %p was not an exact int!\n",
		       (void *) tmp, (void *) err_ob);
	  return -1;
     }

     test_errno = PyInt_AsLong(tmp);
     if((dict == error_dict) && (test_errno < PyGSL_ERRNO_MAX)){
	  flag = PyGSL_register_accel_err_object(err_ob, test_errno);
     }else{
	  flag = _PyGSL_register_err_object(dict, err_ob, tmp);
     }
     if(flag != 0)
	  fprintf(stderr, "Failed to register err_ob %p with errno %ld.\n"
		  "\tAlready registered?\n", err_ob, test_errno);
     FUNC_MESS_END();
     return flag;
}

static PyObject*
PyGSL_register_error_objs(PyObject *self, PyObject *args, PyObject *dict, int errno_max)
{
     int flag, i, len;
     PyObject *tmp;

     FUNC_MESS_BEGIN();
     if(!PySequence_Check(args))
	  return NULL;

     len = PySequence_Size(args);
     DEBUG_MESS(5, "Recieved %d error objects", len);
     for(i = 0; i < len; ++i){
	  tmp = PySequence_GetItem(args, i);
	  flag = _PyGSL_register_error(dict, errno_max, tmp);
	  if(flag != 0){
	       fprintf(stderr, "Failed to register error object %d\n", i);
	       return NULL;
	  }
     }
     PyGSL_print_accel_object();

     Py_INCREF(Py_None);
     FUNC_MESS_END();
     return Py_None;
}

static PyObject*
PyGSL_register_warnings(PyObject *self, PyObject *args)
{
     PyObject *tmp;
     FUNC_MESS_BEGIN();
     tmp = PyGSL_register_error_objs(self, args, warning_dict, 0);
     FUNC_MESS_END();
     return tmp;
}

static PyObject*
PyGSL_register_exceptions(PyObject *self, PyObject *args)
{
     PyObject *tmp;
     FUNC_MESS_BEGIN();
     tmp = PyGSL_register_error_objs(self, args, error_dict, PyGSL_ERRNO_MAX);
     FUNC_MESS_END();
     return tmp;
}

static PyObject *
PyGSL_get_error_object(int the_errno, PyObject ** accel, int accel_max, PyObject *dict)
{
     PyObject *tmp;

     FUNC_MESS_BEGIN();
     assert(the_errno >= 0);
     if (the_errno < accel_max){
	  DEBUG_MESS(4, "Trying to get an error object from accel array at %p",
		     (void*) accel);
	  tmp = accel[the_errno];
     }else{
	  DEBUG_MESS(4, "Trying to get an error object from dictonary at %p",
		     (void*) dict);
	  tmp =  PyDict_GetItem(dict, PyInt_FromLong(the_errno));
     }
     if(tmp == NULL){
	  DEBUG_MESS(3, "Could not find an error object for errno %d", the_errno);
	  PyGSL_print_accel_object();
	  return unknown_error;
     }
     FUNC_MESS_END();
     return tmp;
}

static int
PyGSL_init_errno(void)
{
     int i;
     FUNC_MESS_BEGIN();

     for(i = 0; i< PyGSL_ERRNO_MAX; ++i){
	  DEBUG_MESS(3, "setting errno_accel[%d] to NULL; was %p",
		     i, (void*) (errno_accel[i]));
	  errno_accel[i] = NULL;
     }
     error_dict = PyDict_New();
     if (error_dict == NULL)
	  return -1;

     warning_dict = PyDict_New();
     if (warning_dict == NULL)
	  return -1;

     unknown_error = PyExc_ValueError;
     FUNC_MESS_END();
     return 0;
}

/*
 * Warnings return a flag, so one can see if the warning raises an exception
 *  or not.
 */
static int
PyGSL_internal_error_handler(const char *reason, /* name of function*/
			     const char *file, /*from CPP*/
			     int line,   /*from CPP*/
			     int gsl_error,
			     enum handleflag flag)
{
  const char* error_explanation;
  char error_text[255];
  PyObject* gsl_error_object;

  FUNC_MESS_BEGIN();
  /*
   * GSL_ENOMEM is special. I am out of memory. No fancy tricks here.
   */
  if (GSL_ENOMEM == gsl_error){
       PyErr_NoMemory();
       return -1;
  }

  /*
   * some functions call error handler more than once before returning
   *  report only the first (most specific) error
   */
  if (line < 0) line = 0;
  /* test, if exception is already set */
  DEBUG_MESS(5, "Checking if python error occured, gsl error %d, line %d", gsl_error, line);
  if (PyErr_Occurred()) {
       if(PyGSL_DEBUG_LEVEL() > 0)
	    fprintf(stderr, "Another error occured: %s\n",error_text);
       FUNC_MESS("Already set python error found");
       return -1;
  }

  /*
   * Find the approbriate error
   */
  error_explanation = gsl_strerror(gsl_error);
  if (reason==NULL){
       reason = "no reason given!";
  }

  if (error_explanation==NULL){
      snprintf(error_text,sizeof(error_text),
	       "unknown error %d: %s",
	       gsl_error, reason);
  }else{
      snprintf(error_text,sizeof(error_text),
	       "%s: %s",
	       error_explanation,reason);
  }

  switch(flag){
  case HANDLE_ERROR:
       assert(gsl_error > 0);
       gsl_error_object = PyGSL_get_error_object(gsl_error, errno_accel, PyGSL_ERRNO_MAX, error_dict);
       Py_INCREF(gsl_error_object);
       PyErr_SetObject(gsl_error_object, PyString_FromString(error_text));
       FUNC_MESS("Set Python error object");
       return -1;
       break;
  case HANDLE_WARNING:
       assert(gsl_error > 0);
       gsl_error_object = PyGSL_get_error_object(gsl_error, NULL, 0, warning_dict);
       Py_INCREF(gsl_error_object);
       FUNC_MESS("Returning python warning");
       return PyErr_Warn(gsl_error_object, error_text);
       break;
  default:
       fprintf(stderr, "Unknown handle %d\n", flag);
  }
  FUNC_MESS("Should not end here!");
  return -1;
}
/*
 * sets the right exception, but does not return to python!
 */
static void
PyGSL_module_error_handler(const char *reason, /* name of function*/
			   const char *file, /*from CPP*/
			   int line,   /*from CPP*/
			   int gsl_error) /* real "reason" */
{
     FUNC_MESS_BEGIN();
     PyGSL_internal_error_handler(reason, file, line,  gsl_error, HANDLE_ERROR);
     FUNC_MESS_END();
}

static int
PyGSL_warning(const char *reason, /* name of function*/
	      const char *file, /*from CPP*/
	      int line,   /*from CPP*/
	      int gsl_error) /* real "reason" */
{
     int tmp;
     FUNC_MESS_BEGIN();
     tmp =  PyGSL_internal_error_handler(reason, file, line,  gsl_error, HANDLE_WARNING);
     FUNC_MESS_END();
     return tmp;
}


#include <petsc.h>
#if PETSC_VERSION_LE(3,3,0)

PETSC_EXTERN PetscErrorCode PetscOptionsGetViewer(MPI_Comm,const char[],const char[],PetscViewer*,PetscViewerFormat*,PetscBool*);

#undef __FUNCT__
#define __FUNCT__ "PetscEListFind"
static PetscErrorCode PetscEListFind(PetscInt n,const char *const *list,const char *str,PetscInt *value,PetscBool *found)
{
  PetscInt  i;
  PetscBool matched;
  PetscErrorCode ierr;
  PetscFunctionBegin;
  if (found) *found = PETSC_FALSE;
  for (i=0; i<n; i++) {
    ierr = PetscStrcasecmp(str,list[i],&matched);CHKERRQ(ierr);
    if (matched || !str[0]) {
      if (found) *found = PETSC_TRUE;
      *value = i;
      break;
    }
  }
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "PetscEnumFind"
static PetscErrorCode PetscEnumFind(const char *const *enumlist,const char *str,PetscEnum *value,PetscBool *found)
{
  PetscInt       n,evalue;
  PetscBool      efound;
  PetscErrorCode ierr;
  PetscFunctionBegin;
  for (n = 0; enumlist[n]; n++) {
    if (n > 50) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_WRONG,"List argument appears to be wrong or have more than 50 entries");
  }
  if (n < 3) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_WRONG,"List argument must have at least two entries: typename and type prefix");
  n -= 3;                       /* drop enum name, prefix, and null termination */
  ierr = PetscEListFind(n,enumlist,str,&evalue,&efound);CHKERRQ(ierr);
  if (efound) *value = (PetscEnum)evalue;
  if (found)  *found = efound;
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "PetscOptionsGetViewer"
PetscErrorCode PetscOptionsGetViewer(MPI_Comm comm,const char pre[],const char name[],PetscViewer *viewer,PetscViewerFormat *format,PetscBool *set)
{
  char           value[2*PETSC_MAX_PATH_LEN];
  PetscBool      flag;
  PetscErrorCode ierr;
  PetscFunctionBegin;
  PetscValidCharPointer(name,3);
  if (format) *format = PETSC_VIEWER_DEFAULT;
  if (set) *set = PETSC_FALSE;
  ierr = PetscOptionsGetString(pre,name,value,sizeof(value),&flag);CHKERRQ(ierr);
  if (flag) {
    if (set) *set = PETSC_TRUE;
    if (!value[0]) {
      ierr = PetscViewerASCIIGetStdout(comm,viewer);CHKERRQ(ierr);
      ierr = PetscObjectReference((PetscObject)*viewer);CHKERRQ(ierr);
    } else {
      char       *loc0_vtype,*loc1_fname,*loc2_fmt = NULL,*loc3_fmode = NULL;
      PetscInt   cnt;
      const char *const PetscFileModes[] = {"READ","WRITE","APPEND","UPDATE","APPEND_UPDATE","PetscFileMode","PETSC_FILE_",0};
      const char *const viewers[] = {PETSCVIEWERASCII,PETSCVIEWERBINARY,PETSCVIEWERDRAW,PETSCVIEWERSOCKET,PETSCVIEWERMATLAB,PETSCVIEWERAMS,PETSCVIEWERVTK,0};
      ierr = PetscStrallocpy(value,&loc0_vtype);CHKERRQ(ierr);
      ierr = PetscStrchr(loc0_vtype,':',&loc1_fname);CHKERRQ(ierr);
      if (loc1_fname) {
        *loc1_fname++ = 0;
        ierr = PetscStrchr(loc1_fname,':',&loc2_fmt);CHKERRQ(ierr);
      }
      if (loc2_fmt) {
        *loc2_fmt++ = 0;
        ierr = PetscStrchr(loc2_fmt,':',&loc3_fmode);CHKERRQ(ierr);
      }
      if (loc3_fmode) *loc3_fmode++ = 0;
      ierr = PetscStrendswithwhich(*loc0_vtype ? loc0_vtype : "ascii",viewers,&cnt);CHKERRQ(ierr);
      if (cnt > (PetscInt)sizeof(viewers)-1) SETERRQ1(comm,PETSC_ERR_ARG_OUTOFRANGE,"Unknown viewer type: %s",loc0_vtype);
      if (!loc1_fname) {
        switch (cnt) {
        case 0:
          ierr = PetscViewerASCIIGetStdout(comm,viewer);CHKERRQ(ierr);
          break;
        case 1:
          if (!(*viewer = PETSC_VIEWER_BINARY_(comm))) CHKERRQ(PETSC_ERR_PLIB);
          break;
        case 2:
          if (!(*viewer = PETSC_VIEWER_DRAW_(comm))) CHKERRQ(PETSC_ERR_PLIB);
          break;
#if defined(PETSC_USE_SOCKET_VIEWER)
        case 3:
          if (!(*viewer = PETSC_VIEWER_SOCKET_(comm))) CHKERRQ(PETSC_ERR_PLIB);
          break;
#endif
#if defined(PETSC_HAVE_MATLAB_ENGINE)
        case 4:
          if (!(*viewer = PETSC_VIEWER_MATLAB_(comm))) CHKERRQ(PETSC_ERR_PLIB);
          break;
#endif
#if defined(PETSC_HAVE_AMS)
        case 5:
          if (!(*viewer = PETSC_VIEWER_AMS_(comm))) CHKERRQ(PETSC_ERR_PLIB);
          break;
#endif
        default: SETERRQ1(PETSC_COMM_SELF,PETSC_ERR_SUP,"Unsupported viewer %s",loc0_vtype);
        }
        ierr = PetscObjectReference((PetscObject)*viewer);CHKERRQ(ierr);
      } else {
        if (loc2_fmt && !*loc1_fname && (cnt == 0)) { /* ASCII format without file name */
          ierr = PetscViewerASCIIGetStdout(comm,viewer);CHKERRQ(ierr);
          ierr = PetscObjectReference((PetscObject)*viewer);CHKERRQ(ierr);
        } else {
          PetscFileMode fmode;
          ierr = PetscViewerCreate(comm,viewer);CHKERRQ(ierr);
          ierr = PetscViewerSetType(*viewer,*loc0_vtype ? loc0_vtype : "ascii");CHKERRQ(ierr);
#if defined(PETSC_HAVE_AMS)
          ierr = PetscViewerAMSSetCommName(*viewer,loc1_fname);CHKERRQ(ierr);
#endif
          fmode = FILE_MODE_WRITE;
          if (loc3_fmode && *loc3_fmode) { /* Has non-empty file mode ("write" or "append") */
            ierr = PetscEnumFind(PetscFileModes,loc3_fmode,(PetscEnum*)&fmode,&flag);CHKERRQ(ierr);
            if (!flag) SETERRQ1(comm,PETSC_ERR_ARG_UNKNOWN_TYPE,"Unknown file mode: %s",loc3_fmode);
          }
          ierr = PetscViewerFileSetMode(*viewer,flag?fmode:FILE_MODE_WRITE);CHKERRQ(ierr);
          ierr = PetscViewerFileSetName(*viewer,loc1_fname);CHKERRQ(ierr);
        }
      }
      if (loc2_fmt && *loc2_fmt) {
        ierr = PetscEnumFind(PetscViewerFormats,loc2_fmt,(PetscEnum*)format,&flag);CHKERRQ(ierr);
        if (!flag) SETERRQ1(PETSC_COMM_SELF,PETSC_ERR_SUP,"Unknown viewer format %s",loc2_fmt);CHKERRQ(ierr);
        if (((int)*format) >= 5) *format = (PetscViewerFormat)(((int)*format)-2); /**/
      }
      ierr = PetscViewerSetUp(*viewer);CHKERRQ(ierr);
      ierr = PetscFree(loc0_vtype);CHKERRQ(ierr);
    }
  }
  PetscFunctionReturn(0);
}

#endif


/* -----------------------------------------------------------------------------
 * Copyright 2021 Jonathan Haigh
 * SPDX-License-Identifier: MIT
 * ---------------------------------------------------------------------------*/

#ifndef SQ_INCLUDE_GUARD_core_typeutil_h_
#define SQ_INCLUDE_GUARD_core_typeutil_h_

#include <concepts>
#include <cstddef>
#include <gsl/gsl>
#include <range/v3/range/concepts.hpp>
#include <stdexcept>
#include <string>
#include <system_error>
#include <type_traits>
#include <variant>

// The following macros can't be replaced with constexpr functions or constants
// so tell clang-tidy not to complain

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define SQ_FWD(x) static_cast<decltype(x) &&>(x)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define SQ_ND [[nodiscard]]

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define SQ_MU [[maybe_unused]]

namespace sq {

/**
 * Concept for types that can be dumped to a std::ostream.
 */
template <typename T> concept Printable = requires(T x, std::ostream &os) {
  os << x;
};

///@{
/**
 * Get whether the type T is a specialization of std::variant.
 */
template <typename T> struct IsVariant : std::false_type {};

template <typename... Types>
struct IsVariant<std::variant<Types...>> : std::true_type {};

template <typename T> inline constexpr bool is_variant_v = IsVariant<T>::value;
///@}

///@{
/**
 * Get whether the type T is one of the alternative types of the variant V.
 */
template <typename T, typename V>
requires is_variant_v<V> struct IsAlternative : std::false_type {};

template <typename T, typename... Types>
struct IsAlternative<T, std::variant<Types...>>
    : std::disjunction<std::is_same<T, Types>...> {};

template <typename T, typename V>
inline constexpr bool is_alternative_v = IsAlternative<T, V>::value;
///@}

/**
 * Concept for types that are alternatives of the variant V.
 */
template <typename T, typename V> concept Alternative = is_alternative_v<T, V>;

///@{
/**
 * Get whether T can be converted to one of the variant V's alternatives.
 */
template <typename T, typename V>
requires is_variant_v<V> struct IsConvertibleToAlternative : std::false_type {};

template <typename T, typename... Types>
struct IsConvertibleToAlternative<T, std::variant<Types...>>
    : std::disjunction<std::is_convertible<T, Types>...> {};

template <typename T, typename V>
inline constexpr bool is_convertible_to_alternative_v =
    IsConvertibleToAlternative<T, V>::value;
///@}

/**
 * Concept for types that are convertible to one of the variant V's
 * alternatives.
 */
template <typename T, typename V>
concept ConvertibleToAlternative = is_convertible_to_alternative_v<T, V>;

///@{
/**
 * A range whose distance can be obtained without invalidating the range.
 *
 * This is different to std::sized_range because it doesn't require:
 * - that size(rng) is available;
 * - that distance(rng) is a constant time operation.
 *
 * Essentially, it just rules out std::input_ranges without a known size.
 */
template <typename T>
concept SlowSizedRange =
    ranges::cpp20::forward_range<T> || ranges::cpp20::sized_range<T>;

/**
 * Get the name of the "base type" of the given expression.
 *
 * The "base type" is the type with no references or cv qualification.
 */
SQ_ND std::string base_type_name(const auto &thing);

/**
 * Create a std::error code from a platform dependent error code.
 */
SQ_ND inline std::error_code make_error_code(int code) {
  return std::make_error_code(static_cast<std::errc>(code));
}

} // namespace sq

namespace ranges {

// Tell the ranges library that gsl::spans with dynamic extent are views. Note
// that the view concept requires a default constructor so sized gsl::spans are
// *not* views...
template <typename T>
inline constexpr bool enable_view<gsl::span<T, gsl::dynamic_extent>> = true;

// ... unless the size is zero.
template <typename T> inline constexpr bool enable_view<gsl::span<T, 0>> = true;

} // namespace ranges

#include "core/typeutil.inl.h"

#endif // SQ_INCLUDE_GUARD_core_typeutil_h_


/* -----------------------------------------------------------------------------
 * Copyright 2021 Jonathan Haigh
 * SPDX-License-Identifier: MIT
 * ---------------------------------------------------------------------------*/

#ifndef SQ_INCLUDE_GUARD_core_errors_h_
#define SQ_INCLUDE_GUARD_core_errors_h_

#include "core/Primitive.fwd.h"
#include "core/Token.fwd.h"
#include "core/typeutil.h"

#include <cstddef>
#include <filesystem>
#include <fmt/format.h>
#include <fmt/ostream.h>
#include <gsl/gsl>
#include <stdexcept>
#include <string_view>
#include <system_error>

namespace sq {

/**
 * Base class for errors thrown by the SQ code.
 */
class Exception : public std::runtime_error {
public:
  using std::runtime_error::runtime_error;
};

/**
 * Indicates that a required parameter of a field is missing.
 */
class ArgumentMissingError : public Exception {
public:
  /**
   * @param arg_name the name of the missing argument.
   * @param arg_type the type of the missing argument.
   */
  ArgumentMissingError(std::string_view arg_name, std::string_view arg_type);
};

/**
 * Indicates that a given parameter is of incorrect type.
 */
class ArgumentTypeError : public Exception {
public:
  /**
   * @param received the value of the given parameter.
   * @param type_expected the name of the type expected for the parameter.
   */
  ArgumentTypeError(const Primitive &received, std::string_view type_expected);
};

/**
 * Indicates a programming error in SQ.
 *
 * InternalError should never actually be thrown - they are used in places that
 * the programmer believes are dead code, but where the C++ language still
 * requires e.g. a return statement.
 */
class InternalError : public Exception {
public:
  using Exception::Exception;
};

class InvalidConversionError : public Exception {
public:
  using Exception::Exception;
  InvalidConversionError(std::string_view from, std::string_view to);
};

/**
 * Indicates attempted access of a non-existent field.
 */
class InvalidFieldError : public Exception {
public:
  /**
   * @param sq_type the SQ type of the parent of the missing field.
   * @param field the name of the field that was requested.
   *
   * E.g. if the query is "a.b" and "b" is not a field of "a" then sq_type
   * should be the type of "a" and field should be "b".
   */
  InvalidFieldError(std::string_view sq_type, std::string_view field);
};

/**
 * Indicates incorrect grammar in a query.
 */
class ParseError : public Exception {
public:
  using Exception::Exception;

  /**
   * Create a ParseError for when an unexpected token is found.
   *
   * @param token the unexpected token.
   * @param expecting the set of tokens that would have been valid in place
   * of the unexpected token.
   */
  ParseError(const Token &token, const TokenKindSet &expecting);
};

/**
 * Indicates a failure to interpret part of the input query as a token.
 */
class LexError : public ParseError {
public:
  /**
   * @param pos position in the input query (in characters) at which the lex
   *      error occured.
   * @param query the full input query.
   */
  LexError(gsl::index pos, std::string_view query);
};

/**
 * Indicates that an array operation has been requested on a non-array type.
 */
class NotAScalarError : public Exception {
public:
  using Exception::Exception;
};

/**
 * Indicates that an array operation has been requested on a non-array type.
 */
class NotAnArrayError : public Exception {
public:
  using Exception::Exception;
};
/**
 * Indicates that a requested feature has not been implemented.
 */
class NotImplementedError : public Exception {
public:
  using Exception::Exception;
};

/**
 * Indicates that a request to access an element outside of an allowed range.
 */
class OutOfRangeError : public Exception {
public:
  using Exception::Exception;
  /**
   * @param token the token in the query where the access was requested.
   * @param message details about the requested access.
   */
  OutOfRangeError(const Token &token, std::string_view message);
};

/**
 * Indicates that a pullup type field access has been requested for a field
 * access with siblings.
 */
class PullupWithSiblingsError : public Exception {
  using Exception::Exception;
};

/**
 * Indicates an error was received from a system library.
 */
class SystemError : public Exception {
public:
  using Exception::Exception;

  /**
   * Create a SystemError object.
   *
   * @param operation the operation that failed.
   * @param code the system error code associated with the error.
   */
  SystemError(std::string_view operation, std::error_code code);

  /**
   * Get the system error code associated with this error.
   */
  SQ_ND std::error_code code() const;

private:
  std::error_code code_;
};

/**
 * Indicates that the udev library returned an error.
 */
class UdevError : public SystemError {
  using SystemError::SystemError;
};

/**
 * Indicates that a filesystem error occurred.
 */
class FilesystemError : public SystemError {
public:
  using SystemError::SystemError;

  /**
   * Create a FilesystemError object.
   *
   * @param operation the operation that failed.
   * @param path the path for which the operation failed.
   * @param code the system error code associated with the error.
   */
  FilesystemError(std::string_view operation, const std::filesystem::path &path,
                  std::error_code code);
};

class NarrowingError : public OutOfRangeError {
public:
  using OutOfRangeError::OutOfRangeError;

  NarrowingError(auto &&target, auto &&value, auto &&kind_of_value_format,
                 auto &&...format_args);

  NarrowingError(auto &&target, auto &&value);
};

} // namespace sq

#include "core/errors.inl.h"

#endif // SQ_INCLUDE_GUARD_core_errors_h_


/**
 * @file coroutine/net.h
 * @brief Async I/O operation support with system socket functions
 * @author github.com/luncliff (luncliff@gmail.com)
 * @copyright CC BY 4.0
 */
#pragma once
#ifndef COROUTINE_NET_IO_H
#define COROUTINE_NET_IO_H
#include <gsl/gsl>

#include <coroutine/return.h>

/**
 * @defgroup Network
 * Helper types to apply `co_await` for socket operations + Name resolution utilities
 */

#if __has_include(<WinSock2.h>) // use winsock
#include <WS2tcpip.h>
#include <WinSock2.h>
#include <ws2def.h>

/** @brief indicates current import: using Windows Socket API? */
/** @brief indicates current import: using <netinet/*.h>? */
static constexpr bool is_winsock = true;
static constexpr bool is_netinet = false;

using io_control_block = OVERLAPPED;

#elif __has_include(<netinet/in.h>) // use netinet
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <unistd.h>

static constexpr bool is_winsock = false;
static constexpr bool is_netinet = true;

/**
 * @brief Follow the definition of Windows `OVERLAPPED`
 * @see https://docs.microsoft.com/en-us/windows/win32/sync/synchronization-and-overlapped-input-and-output
 * @see https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-overlapped
 * @ingroup Network
 */
struct io_control_block {
    uint64_t internal;      // uint32_t errc, int32_t flag
    uint64_t internal_high; // int64_t len, socklen_t addrlen
    union {
        struct {
            int32_t offset;
            int32_t offset_high;
        };
        void* ptr; // sockaddr* addr;
    };
    int64_t handle; // int64_t sd;
};

#endif // winsock || netinet

namespace coro {

/**
 * @brief This is simply a view to storage. Be aware that it doesn't have ownership
 * @ingroup Network
 */
using io_buffer_t = gsl::span<std::byte>;
static_assert(sizeof(io_buffer_t) <= sizeof(void*) * 2);

/**
 * @brief A struct to describe "1 I/O request" to system API.
 * @ingroup Network
 * When I/O request is submitted, an I/O task becomes 1 coroutine handle
 */
class io_work_t : public io_control_block {
  public:
    coroutine_handle<void> task{};
    io_buffer_t buffer{};

  protected:
    /**
     * @see await_ready
     * @return true  The given socket can be use for non-blocking operations
     * @return false For Windows, the return is always `false`
     */
    bool ready() const noexcept;

  public:
    /**
     * @brief Multiple retrieving won't be a matter
     * @return uint32_t error code from the system
     */
    uint32_t error() const noexcept;
};
static_assert(sizeof(io_work_t) <= 56);

/**
 * @brief Awaitable type to perform `sendto` I/O request
 * @see sendto
 * @see WSASendTo
 * @ingroup Network
 */
class io_send_to final : public io_work_t {
  private:
    /**
     * @brief makes an I/O request with given context(`coroutine_handle<void>`)
     * @throw std::system_error
     */
    void suspend(coroutine_handle<void> t) noexcept(false);
    /**
     * @brief Fetch I/O result/error
     * @return int64_t return of `sendto`
     *
     * This function must be used through `co_await`.
     * Multiple invoke of this will lead to malfunction.
     */
    int64_t resume() noexcept;

  public:
    bool await_ready() const noexcept {
        return this->ready();
    }
    /**
     * @throw std::system_error
     */
    void await_suspend(coroutine_handle<void> t) noexcept(false) {
        return this->suspend(t);
    }
    int64_t await_resume() noexcept {
        return this->resume();
    }
};
static_assert(sizeof(io_send_to) == sizeof(io_work_t));

/**
 * @brief Awaitable type to perform `recvfrom` I/O request
 * @see recvfrom
 * @see WSARecvFrom
 * @ingroup Network
 */
class io_recv_from final : public io_work_t {
  private:
    /**
     * @brief makes an I/O request with given context(`coroutine_handle<void>`)
     * @throw std::system_error
     */
    void suspend(coroutine_handle<void> t) noexcept(false);
    /**
     * @brief Fetch I/O result/error
     * @return int64_t return of `recvfrom`
     *
     * This function must be used through `co_await`.
     * Multiple invoke of this will lead to malfunction.
     */
    int64_t resume() noexcept;

  public:
    bool await_ready() const noexcept {
        return this->ready();
    }
    /**
     * @throw std::system_error
     */
    void await_suspend(coroutine_handle<void> t) noexcept(false) {
        return this->suspend(t);
    }
    int64_t await_resume() noexcept {
        return this->resume();
    }
};
static_assert(sizeof(io_recv_from) == sizeof(io_work_t));

/**
 * @brief Awaitable type to perform `send` I/O request
 * @see send
 * @see WSASend
 * @ingroup Network
 */
class io_send final : public io_work_t {
  private:
    /**
     * @brief makes an I/O request with given context(`coroutine_handle<void>`)
     * @throw std::system_error
     */
    void suspend(coroutine_handle<void> t) noexcept(false);
    /**
     * @brief Fetch I/O result/error
     * @return int64_t return of `send`
     *
     * This function must be used through `co_await`.
     * Multiple invoke of this will lead to malfunction.
     */
    int64_t resume() noexcept;

  public:
    bool await_ready() const noexcept {
        return this->ready();
    }
    void await_suspend(coroutine_handle<void> t) noexcept(false) {
        return this->suspend(t);
    }
    int64_t await_resume() noexcept {
        return this->resume();
    }
};
static_assert(sizeof(io_send) == sizeof(io_work_t));

/**
 * @brief Awaitable type to perform `recv` I/O request
 * @see recv
 * @see WSARecv
 * @ingroup Network
 */
class io_recv final : public io_work_t {
  private:
    /**
     * @brief makes an I/O request with given context(`coroutine_handle<void>`)
     * @throw std::system_error
     */
    void suspend(coroutine_handle<void> t) noexcept(false);

    /**
     * @brief Fetch I/O result/error
     * @return int64_t return of `recv`
     *
     * This function must be used through `co_await`.
     * Multiple invoke of this will lead to malfunction.
     */
    int64_t resume() noexcept;

  public:
    bool await_ready() const noexcept {
        return this->ready();
    }
    void await_suspend(coroutine_handle<void> t) noexcept(false) {
        return this->suspend(t);
    }
    int64_t await_resume() noexcept {
        return this->resume();
    }
};
static_assert(sizeof(io_recv) == sizeof(io_work_t));

/**
 * @brief Constructs `io_send_to` awaitable with the given parameters
 * @param sd
 * @param remote
 * @param buf
 * @param work
 * @return io_send_to&
 *
 * @ingroup Network
 */
auto send_to(uint64_t sd, const sockaddr_in& remote, io_buffer_t buf,
             io_work_t& work) noexcept(false) -> io_send_to&;

/**
 * @brief Constructs `io_send_to` awaitable with the given parameters
 * @param sd
 * @param remote
 * @param buf
 * @param work
 * @return io_send_to&
 *
 * @ingroup Network
 */
auto send_to(uint64_t sd, const sockaddr_in6& remote, io_buffer_t buf,
             io_work_t& work) noexcept(false) -> io_send_to&;

/**
 * @brief Constructs `io_recv_from` awaitable with the given parameters
 * @param sd
 * @param remote
 * @param buf
 * @param work
 * @return io_recv_from&
 *
 * @ingroup Network
 */
auto recv_from(uint64_t sd, sockaddr_in& remote, io_buffer_t buf,
               io_work_t& work) noexcept(false) -> io_recv_from&;

/**
 * @brief Constructs `io_recv_from` awaitable with the given parameters
 * @param sd
 * @param remote
 * @param buf
 * @param work
 * @return io_recv_from&
 *
 * @ingroup Network
 */
auto recv_from(uint64_t sd, sockaddr_in6& remote, io_buffer_t buf,
               io_work_t& work) noexcept(false) -> io_recv_from&;

/**
 * @brief Constructs `io_send` awaitable with the given parameters
 * @param sd
 * @param buf
 * @param flag
 * @param work
 * @return io_send&
 *
 * @ingroup Network
 */
auto send_stream(uint64_t sd, io_buffer_t buf, uint32_t flag,
                 io_work_t& work) noexcept(false) -> io_send&;

/**
 * @brief Constructs `io_recv` awaitable with the given parameters
 * @param sd
 * @param buf
 * @param flag
 * @param work
 * @return io_recv&
 *
 * @ingroup Network
 */
auto recv_stream(uint64_t sd, io_buffer_t buf, uint32_t flag,
                 io_work_t& work) noexcept(false) -> io_recv&;

/**
 * @brief Poll internal I/O works and invoke user callback
 * @param nano timeout in nanoseconds
 * @throw std::system_error
 *
 * @ingroup Network
 */
void poll_net_tasks(uint64_t nano) noexcept(false);

/**
 * @brief Thin wrapper of `getaddrinfo` for IPv4
 *
 * @param hint
 * @param host
 * @param serv
 * @param output
 * @return uint32_t Error code from the `getaddrinfo` that can be the argument of `gai_strerror`
 * @see getaddrinfo
 * @see gai_strerror
 *
 * @ingroup Network
 */
uint32_t get_address(const addrinfo& hint, //
                     gsl::czstring host, gsl::czstring serv,
                     gsl::span<sockaddr_in> output) noexcept;

/**
 * @brief Thin wrapper of `getaddrinfo` for IPv6
 *
 * @param hint
 * @param host
 * @param serv
 * @param output
 * @return uint32_t Error code from the `getaddrinfo` that can be the argument of `gai_strerror`
 * @see getaddrinfo
 * @see gai_strerror
 *
 * @ingroup Network
 */
uint32_t get_address(const addrinfo& hint, //
                     gsl::czstring host, gsl::czstring serv,
                     gsl::span<sockaddr_in6> output) noexcept;

/**
 * @brief Thin wrapper of `getnameinfo`
 *
 * @param addr
 * @param name
 * @param serv can be `nullptr`
 * @param flags
 * @return uint32_t EAI_AGAIN ...
 * @see getnameinfo
 *
 * @ingroup Network
 */
uint32_t get_name(const sockaddr_in& addr, //
                  gsl::basic_zstring<char, NI_MAXHOST> name, gsl::basic_zstring<char, NI_MAXSERV> serv,
                  int32_t flags = NI_NUMERICHOST | NI_NUMERICSERV) noexcept;

/**
 * @brief Thin wrapper of `getnameinfo`
 * @param addr
 * @param name
 * @param serv can be `nullptr`
 * @param flags
 * @return uint32_t EAI_AGAIN ...
 * @see getnameinfo
 *
 * @ingroup Network
 */
uint32_t get_name(const sockaddr_in6& addr, //
                  gsl::basic_zstring<char, NI_MAXHOST> name, gsl::basic_zstring<char, NI_MAXSERV> serv,
                  int32_t flags = NI_NUMERICHOST | NI_NUMERICSERV) noexcept;

} // namespace coro

#endif // COROUTINE_NET_IO_H


/* Copyright (c) 2020 Felix Kutzner (github.com/fkutzner)

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.

 Except as contained in this notice, the name(s) of the above copyright holders
 shall not be used in advertising or otherwise to promote the sale, use or
 other dealings in this Software without prior written authorization.

*/

#pragma once

#include <libincmonk/verifier/Traits.h>

#include <cstdint>
#include <gsl/span>
#include <iterator>
#include <limits>
#include <memory>
#include <optional>
#include <ostream>
#include <unordered_map>
#include <vector>

namespace incmonk::verifier {

class Var {
public:
  constexpr explicit Var(uint32_t id);
  constexpr Var();

  constexpr auto getRawValue() const -> uint32_t;

  constexpr auto operator==(Var rhs) const -> bool;
  constexpr auto operator!=(Var rhs) const -> bool;
  constexpr auto operator<(Var rhs) const -> bool;
  constexpr auto operator<=(Var rhs) const -> bool;
  constexpr auto operator>(Var rhs) const -> bool;
  constexpr auto operator>=(Var rhs) const -> bool;

private:
  uint32_t m_rawValue;
};

template <>
struct Key<Var> {
  constexpr static auto get(Var const& item) -> std::size_t;
};

auto operator""_Var(unsigned long long cnfValue) -> Var;
auto operator<<(std::ostream& stream, Var var) -> std::ostream&;

class Lit {
public:
  constexpr explicit Lit(Var v, bool positive);
  constexpr Lit();

  constexpr auto getRawValue() const -> uint32_t;
  constexpr auto getVar() const -> Var;
  constexpr auto isPositive() const -> bool;
  constexpr auto operator-() const -> Lit;

  constexpr auto operator==(Lit rhs) const -> bool;
  constexpr auto operator!=(Lit rhs) const -> bool;
  constexpr auto operator<(Lit rhs) const -> bool;
  constexpr auto operator<=(Lit rhs) const -> bool;
  constexpr auto operator>(Lit rhs) const -> bool;
  constexpr auto operator>=(Lit rhs) const -> bool;

private:
  uint32_t m_rawValue;
};

auto operator""_Lit(unsigned long long cnfValue) -> Lit;
auto operator<<(std::ostream& stream, Lit lit) -> std::ostream&;

template <>
struct Key<Lit> {
  constexpr static auto get(Lit const& item) -> std::size_t;
};

auto maxLit(Var var) noexcept -> Lit;

enum class ClauseVerificationState : uint8_t {
  /// The clause is part of the problem instance, no verification required
  Irredundant = 0,

  /// The clause is a lemma and has not yet been determined to be relevant for the proof
  Passive = 1,

  /// The clause is a lemma and has been determinined relevant for the proof. RAT property
  /// verification is pending
  VerificationPending = 2,

  /// The clause is a lemma and its RAT property has been verified.
  Verified = 3
};

using ProofSequenceIdx = uint32_t;

class Clause final {
public:
  using size_type = uint32_t;
  using iterator = Lit*;             // TODO: write proper iterator
  using const_iterator = Lit const*; // TODO: write proper const_iterator

  auto operator[](size_type idx) noexcept -> Lit&;
  auto operator[](size_type idx) const noexcept -> Lit const&;

  auto getLiterals() noexcept -> gsl::span<Lit>;
  auto getLiterals() const noexcept -> gsl::span<Lit const>;

  auto size() const noexcept -> size_type;
  auto empty() const noexcept -> bool;

  void setState(ClauseVerificationState state) noexcept;
  auto getState() const noexcept -> ClauseVerificationState;

  auto getAddIdx() const noexcept -> ProofSequenceIdx;

private:
  friend class ClauseCollection;
  Clause(size_type size, ClauseVerificationState initialState, ProofSequenceIdx addIdx) noexcept;

  size_type m_size;
  uint32_t m_flags;
  ProofSequenceIdx m_pointOfAdd;
  Lit m_firstLit;
};

auto operator<<(std::ostream& stream, Clause const& clause) -> std::ostream&;

class ClauseFinder;
class ClauseOccurrences;

class ClauseCollection final {
public:
  ClauseCollection();
  ~ClauseCollection();

  class Ref {
  public:
    auto operator==(Ref rhs) const noexcept -> bool;
    auto operator!=(Ref rhs) const noexcept -> bool;

  private:
    std::size_t m_offset = 0;
    friend class ClauseCollection;
    friend class RefIterator;
    friend struct std::hash<Ref>;
  };

  class RefIterator {
  public:
    using value_type = Ref;
    using reference = Ref&;
    using pointer = Ref*;
    using difference_type = intptr_t;
    using iterator_category = std::input_iterator_tag;

    RefIterator(char const* m_allocatorMemory, std::size_t highWaterMark) noexcept;
    RefIterator() noexcept;

    auto operator*() const noexcept -> Ref const&;
    auto operator->() const noexcept -> Ref const*;
    auto operator++(int) noexcept -> RefIterator;
    auto operator++() noexcept -> RefIterator&;

    auto operator==(RefIterator const& rhs) const noexcept -> bool;
    auto operator!=(RefIterator const& rhs) const noexcept -> bool;

    RefIterator(RefIterator const& rhs) noexcept = default;
    RefIterator(RefIterator&& rhs) noexcept = default;
    auto operator=(RefIterator const& rhs) noexcept -> RefIterator& = default;
    auto operator=(RefIterator&& rhs) noexcept -> RefIterator& = default;

  private:
    char const* m_clausePtr;
    std::size_t m_distanceToEnd;
    Ref m_currentRef;
  };

  using LitSpan = gsl::span<Lit const>;
  using OccRng = gsl::span<Ref const>;

  auto add(LitSpan lits, ClauseVerificationState initialState, ProofSequenceIdx addIdx) -> Ref;
  auto resolve(Ref cref) noexcept -> Clause&;
  auto resolve(Ref cref) const noexcept -> Clause const&;
  auto find(LitSpan lits) const noexcept -> std::optional<Ref>;
  auto getOccurrences(Lit lit) const noexcept -> OccRng;

  auto begin() const noexcept -> RefIterator;
  auto end() const noexcept -> RefIterator;

  auto getMaxVar() const noexcept -> Var;

  ClauseCollection(ClauseCollection&&) noexcept;
  auto operator=(ClauseCollection &&) -> ClauseCollection&;

  ClauseCollection(ClauseCollection const&) = delete;
  auto operator=(ClauseCollection const&) -> ClauseCollection& = delete;

private:
  void resize(std::size_t newSize);
  auto isValidRef(Ref cref) const noexcept -> bool;

  char* m_memory = nullptr;
  std::size_t m_currentSize = 0;
  std::size_t m_highWaterMark = 0;

  Var m_maxVar = 0_Var;

  std::vector<Ref> m_deletedClauses;
  mutable std::unique_ptr<ClauseFinder> m_clauseFinder;
  mutable std::unique_ptr<ClauseOccurrences> m_clauseOccurrences;
};

using CRef = ClauseCollection::Ref;
using OptCRef = std::optional<CRef>;
}

#include "ClauseImpl.h"


#pragma once
#include "../utils/utils.h"
#include <gsl/gsl>
#include <limits>

namespace Halley {
	class Compression {
	public:
		static Bytes compress(const Bytes& bytes);
		static Bytes compress(gsl::span<const gsl::byte> bytes);
		static Bytes decompress(const Bytes& bytes, size_t maxSize = std::numeric_limits<size_t>::max());
		static Bytes decompress(gsl::span<const gsl::byte> bytes, size_t maxSize = std::numeric_limits<size_t>::max());
		static std::shared_ptr<const char> decompressToSharedPtr(gsl::span<const gsl::byte> bytes, size_t& outSize, size_t maxSize = std::numeric_limits<size_t>::max());

		static Bytes compressRaw(gsl::span<const gsl::byte> bytes, bool insertLength);
		static Bytes decompressRaw(gsl::span<const gsl::byte> bytes, size_t maxSize, size_t expectedSize = 0);
	};
}


#pragma once

#include "../common.h"
#include "../concepts.h"
#include <boost/container/flat_set.hpp>
#include <boost/outcome/result.hpp>
#include <boost/outcome/try.hpp>
#include <fmt/format.h>
#include <gsl/gsl-lite.hpp>
#include <string>
#include <string_view>
#include <yaml-cpp/yaml.h>

namespace angonoka::validation {
using boost::container::flat_set;
namespace bo = boost::outcome_v2;
using result = bo::result<void, std::string>;
using namespace fmt::literals;

template <typename T>
concept Check
    = std::is_invocable_v<T, const YAML::Node&, gsl::czstring>;
template <typename T>
concept Attribute = std::is_convertible_v<
    decltype(std::declval<T>().name),
    std::string_view> && Check<T>;
template <typename T>
concept AttrOrStr = String<T> || Attribute<T>;

/**
    YAML scalar.

    Example:

    hello: "world"
           ^
           | Scalar

    scalar()

    Means that the value has to be a scalar and
    not a map or a sequence, etc.

    @return Check function
*/
constexpr Check auto scalar()
{
    return
        [](const YAML::Node& node, std::string_view scope) -> result {
            if (!node || node.IsNull())
                return R"("{}" can't be empty.)"_format(scope);
            if (!node.IsScalar())
                return R"("{}" has invalid type.)"_format(scope);
            return bo::success();
        };
}

namespace detail {
    /**
        Helper class for required and optional YAML fields.

        @var name   Parameter's name
        @var check  Function to apply to the field
    */
    template <Check T> struct functor {
        gsl::czstring name;
        T check;

        constexpr functor(gsl::czstring name, T check)
            : name{name}
            , check{check}
        {
        }
        explicit constexpr functor(gsl::czstring name)
            : functor{name, scalar()}
        {
        }
    };

    /**
        Join YAML path parts.

        @param a Root path
        @param b New path

        @return Concatenation of parts separated by a ".".
    */
    template <String T1, String T2> std::string join(T1&& a, T2&& b)
    {
        if (std::empty(a)) return std::forward<T2>(b);
        return "{}.{}"_format(
            std::forward<T1>(a),
            std::forward<T2>(b));
    }
} // namespace detail

/**
    Requred YAML field.

    Example:

    required("hello")

    Means that the field "hello" is required and
    it has to be a scalar value.

    @var name   Parameter's name
    @var check  Function to apply to the field
*/
template <Check T> struct required : detail::functor<T> {
    using detail::functor<T>::functor;
    result
    operator()(const YAML::Node& node, std::string_view scope) const
    {
        if (const auto n = node[this->name])
            return this->check(n, detail::join(scope, this->name));
        return R"("{}" is missing a "{}" attribute.)"_format(
            scope,
            this->name);
    }
};

template <Check T> required(gsl::czstring, T) -> required<T>;
required(gsl::czstring)->required<decltype(scalar())>;

namespace detail {
    /**
        Extract the map attribute's name.

        If an attribute is a string literal, pass the argument as is.

        @param attr Either an attribute or a string literal

        @return Attribute name
    */
    constexpr auto attr_name(gsl::czstring attr) { return attr; }
    constexpr auto attr_name(Attribute auto&& attr)
    {
        return attr.name;
    }

    /**
        Extract or construct an attribute check function.

        If an attrubte is a string literal, construct
        the required attrubte with the string literal as it's
        name.

        @param attr Either an attribute or a string literal

        @return Check function
    */
    constexpr auto attr_check(gsl::czstring attr)
    {
        return required(attr);
    }
    constexpr auto attr_check(Attribute auto&& attr) { return attr; }
} // namespace detail

/**
    Optional YAML field.

    Example:

    optional("hello")

    Means that the field "hello" is optional and if
    present, it has to be a scalar value.

    @var name  Parameter's name
    @var check Function to apply to the field
*/
template <Check T> struct optional : detail::functor<T> {
    using detail::functor<T>::functor;

    result
    operator()(const YAML::Node& node, std::string_view scope) const
    {
        if (const auto n = node[this->name])
            return this->check(n, detail::join(scope, this->name));
        return bo::success();
    }
};

template <Check T> optional(gsl::czstring, T) -> optional<T>;
optional(gsl::czstring)->optional<decltype(scalar())>;

/**
    YAML array.

    Validates each value of the array with the provided function.

    Example:

    sequence(scalar())

    Means that the value has to be a sequence (array) of
    scalar values.

    @param check Function to apply to each item

    @return Check function
*/
constexpr Check auto sequence(Check auto check)
{
    return [=](const YAML::Node& node,
               std::string_view scope) -> result {
        if (!node || !node.IsSequence()) {
            return R"("{}" is expected to be a sequence.)"_format(
                scope);
        }
        for (gsl::index i{0}; i < std::size(node); ++i) {
            BOOST_OUTCOME_TRY(
                check(node[i], "{}[{}]"_format(scope, i)));
        }
        return bo::success();
    };
}

constexpr Check auto sequence() { return sequence(scalar()); }

/**
    YAML map.

    Matches specified parameters exactly, no extra fields permitted.

    Example:

    attributes("first", optional("second"))

    Means that the value has to be a map with a required field
    "first", which has to be a scalar and an optional field "second"
    which also has to be a scalar.

    @param attrs Sequence of optional or required parameters

    @return Check function
*/
constexpr Check auto attributes(AttrOrStr auto... attrs)
{
    return [=](const YAML::Node& node,
               std::string_view scope = {}) -> result {
        if (!node || node.IsScalar() || node.IsSequence())
            return R"("{}" is expected to be a map.)"_format(scope);
        flat_set<std::string_view> unique_fields;
        for (auto&& n : node) {
            const auto& attr_name = n.first.Scalar();
            if (attr_name.empty())
                return R"(Empty attribute in "{}".)"_format(scope);
            if (!unique_fields.emplace(attr_name).second) {
                return R"(Duplicate attribute "{}" in "{}".)"_format(
                    attr_name,
                    scope);
            }
            if (!((attr_name == detail::attr_name(attrs)) || ...)) {
                return R"(Unexpected attribute "{}" in "{}".)"_format(
                    attr_name,
                    scope);
            }
        }
        result r = bo::success();
        ((r = detail::attr_check(attrs)(node, scope)) && ...);
        return r;
    };
}

/**
    YAML map.

    Validates each value of the map with the provided function.
    Used when the number of map fields may vary.

    Example:

    foo:
      bar1: 1
      bar2: 2
      bar3: 3

    values(scalar())

    @param check Function to apply to each value

    @return Check function
*/
constexpr Check auto values(Check auto check)
{
    return [=](const YAML::Node& node,
               std::string_view scope) -> result {
        if (!node || !node.IsMap())
            return R"("{}" is expected to be a map.)"_format(scope);
        for (auto&& n : node) {
            BOOST_OUTCOME_TRY(check(
                n.second,
                detail::join(scope, n.first.Scalar())));
        }
        return bo::success();
    };
}

/**
    Match at least one of the validators.

    Example:

    required("example", any_of(scalar(), attributes("foo", "bar")))

    Means the value has to either be a singular scalar value or
    a map with 2 fields "foo" and "bar.

    example: "hello"

    or

    example:
      foo: 1
      bar: 2

    @param check Functions to match

    @return Check function
*/
constexpr Check auto any_of(Check auto... checks)
{
    return [=](const YAML::Node& node,
               std::string_view scope) -> result {
        result r = bo::success();
        ((r = checks(node, scope)) || ...);
        return r;
    };
}
} // namespace angonoka::validation


/*
 * edges.h -- élek effektív tárolása: lista az id-k szerint rendezve,
 * 	ebben gyorsan lehet keresni + binary heap az időpontok szerint, így
 * 	a legrégebbit gyorsan ki lehet választani
 *
 * Copyright 2013 Kondor Dániel <dani@thinkpad-r9r>
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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-1301, USA.
 *
 *
 */

#ifndef EDGES_H
#define EDGES_H

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include "idlist.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <ctype.h>
#include <sys/mman.h>
//~ #include <gsl/gsl_rng.h>
#include <limits.h>
//~ #include "mt19937.h"

//változás: hashmap használata helyett előre beolvassuk az összes lehetséges élt, sorbarendezve (feltétel:
//	a bemeneti fájlban sorbarendezve kell legyenek, minden él csak egyszer -- ezt egyszerűbb az SQL Serverrel
//	megcsináltatni), ebben tudunk már gyorsan keresni

typedef struct edgehelper_t {
	const idlist* il;
	uint64_t* index;
	uint64_t* outdeg;
} edgehelper;

//heap struktúra az élek tárolásához (összes él, idő szerint rendezve)
typedef struct edge_t { //egy él, heap ezekből, timestamp szerint rendezve
	uint32_t p1; //első pont -> itt az "igazi" ID-ket tároljuk (a hálózatbeli azonosítókat, ezeknek nem kell szekvenciálisnak lenni)
	uint32_t p2; //második pont (p1->p2 él)
	unsigned int offset; //heap-beli offset (heap[i].offset == i mindig) -- vagy: tranzakció ID-k
	unsigned int timestamp; //legutolsó aktivitás
} edge; //méret -- 16 bájt

typedef struct edges_t {
	edge* e; //élek tárolása itt, id-k szerint rendezve
	uint64_t nedges;
	uint64_t edges_size;
	uint64_t edges_grow;
	edgehelper* h;
} edges;

/*
 * egy tömb (unsigned int-ekből), amiben az edge-ekre mutató pointerek vannak, ezek binary heap-ben:
 * 	unsigned int* heap;
 * 	edges[heap[i]].timestamp < edges[heap[2*i+1 / 2*i+2]].timestamp
 * és edges[heap[i]].offset == i
 *
 */

//hash fv. (két 32-bites id-ből egy 64-bites szám, ezeket talán gyorsabb összehasonlítani
static inline uint64_t edgehash(const edge* e, const unsigned int i) {
		uint64_t r = e[i].p1;
		r = r << 32;
		uint64_t r2 = e[i].p2;
		return r | r2;
}

//két él összehasonlítása, eredmény: -1, ha eh2-nek e[i] előtt kell lenni, 1 ha utána, 0, ha megegyeznek
static inline int cmpedge(const edge* e, uint64_t i, uint64_t eh2) {
	uint64_t eh1 = edgehash(e,i);
	if(eh1 < eh2) return 1;
	if(eh1 > eh2) return -1;
	return 0;
}

static inline int cmpedge2(const edge* e, uint64_t i, uint64_t j) {
	uint64_t eh2 = edgehash(e,j);
	return cmpedge(e,i,eh2);
}

//éleket tároló struktúra lefoglalása / növesztése:
//	ha e == 0, akkor új struktúra lefoglalása
//	ha e != 0, akkor növesztés (mremap())
//eredmény: 0, ha hiba történt (nem sikerült memóriát lefoglalni)
//ha a bemeneti e nem volt 0, akkor azt nem szabadítottuk fel
//edges* pointer, ha rendben volt (ha e != 0, akkor e-t adjuk vissza)
edges* edges_grow(edges* e);

//élek felszabadítása
void edges_free(edges* e);

//élek beolvasása egy fájlból -- a fájlban már megfelelően rendezve kell lenniük
edges* edges_read(FILE* f);

//élek beolvasása egy fájlból -- a fájlban már megfelelően rendezve kell lenniük
edges* edges_read2(FILE* f); //+ timestamp-ok is meg vannak adva

//beolvasás általánosan:
//flags értékei:
#define EFLAGS_T1 1 //időpontokat is beolvasunk és idő szerint rendezzük az eredményt (ha nincs megadva, akkor csak
	//éleket olvasunk be és minden élből csak egyet tartunk meg)
#define EFLAGS_SELF 2 //megengedünk önmagába mutató éleket (p1->p1)
#define EFLAGS_TXID 4 //tranzakció ID-ket is beolvasunk (legutolsó oszlop, az offset tömbbe tároljuk el)
#define EFLAGS_ERROR_OVERFLOW 8 // overflow hibát jelent (különben csak kihagyjuk)
edges* edges_read0(FILE* f, int flags);

//élek átmásolása egy új tömbbe, ha r == 1, akkor fordítva (p2->p1, p1->p2)
edges* edges_copy(edges* e1, int r);

//részhalmaz átmásolása
edges* edges_copy2(edges* e1, uint64_t start, uint64_t end, int r);

//él megkeresése, eredmény: a tömbön (e->e) belüli sorszám, ha megtaláltuk, e->nedges, ha nem találtuk
uint64_t edges_find(edges* e, uint32_t p1, uint32_t p2);

//olyan él keresése, ahol az első ID megegyezik a megadottal
uint64_t edges_findfirst(edges* e, uint32_t p1);

//élek sorbarendezése az ID-k szerint
void edges_sort1(edges* e);

//véletlen cserék
//a->b, c->d helyett a->d, c->b
//N darab csere
/*
void edges_rand1(edges* e, uint64_t N, gsl_rng* r);

//véletlen cserék, a sikertelen próbálkozások is beleszámítanak a cserék számába
//a->b, c->d helyett a->d, c->b
void edges_rand2(edges* e, uint64_t N, gsl_rng* r);
*/

// create a "helper" for faster edge lookups: store the starting index for each addr, making it unnecessary to perform binary
//	search for the whole set, and also return result in O(1) time for addresses with outdeg == 1
int edges_createhelper(edges* e, const idlist* il);

//ID-k generálása N db él alapján
idlist* ids_gen(const edge* e, unsigned int N);

//N db élben az ID-k kicserélése a sorszámukra
int ids_replace(const idlist* il, edge* e, uint64_t N);

#endif


/* ieee-utils/fp-unknown.c
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 Brian Gough
 *
 * 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; either version 2 of the License, or (at
 * your option) any later version.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <config.h>
#include <gsl/gsl_ieee_utils.h>
#include <gsl/gsl_errno.h>

int
gsl_ieee_set_mode (int precision, int rounding, int exception_mask)
{
  GSL_ERROR (
"the IEEE interface for this platform is unsupported or could not be "
"determined at configure time\n", GSL_EUNSUP) ;
}


#ifndef RENDERING_ENTITY_STEREO_VIEWERCENTRICENTITY_H
#define RENDERING_ENTITY_STEREO_VIEWERCENTRICENTITY_H

#include "stereoimageentity.h"

#include <gsl/gsl>

class QPaintEvent;
class QPainter;

namespace s3d {
struct ViewerContext;
}  // namespace s3d

class ViewerCentricEntity : public StereoImageEntity {
 public:
  ViewerCentricEntity();

  void init() override;
  void setHorizontalShift(float shift) override;
  void setAspectRatio(float ratio) override;
  void setTextureLeft(QOpenGLTexture* texture) override;
  void setTextureRight(QOpenGLTexture* texture) override;
  void setViewerContext(gsl::not_null<s3d::ViewerContext*> viewerContext);
  void setDepthRangeMeters(float min, float max);
  void setDisplayZoom(float displayZoom);

  void draw(QPaintDevice* paintDevice) override;

  QPoint worldToWidget(QPointF pos, int deviceWidth, int deviceHeight);
  float worldYToWidgetX(float y, int deviceWidth, int deviceHeight);
  float worldZToWidgetY(float z, int deviceHeight);
  float getPixelToWorldRatio(float deviceHeight);

 private:
  void drawScreen(QPainter* painter);
  void drawViewer(QPainter* painter);

 private:
  s3d::ViewerContext* m_viewerContext{};
  float m_displayZoom{1.0f};

  float m_minZ{-1.0f};
  float m_maxZ{3.0f};
};

#endif  // RENDERING_ENTITY_STEREO_VIEWERCENTRICENTITY_H


// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#pragma once

#include "arcana/type_traits.h"
#include "arcana/either.h"
#include "arcana/iterators.h"

#include <memory>
#include <gsl/gsl>
#include <utility>

#include <cereal/cereal.hpp>
#include <cereal/archives/json.hpp>

namespace mira
{
    //
    // Generic object used to iterate an object hierarchy in order to
    // iterate all its members in the goal of introspecting objects as they
    // flow through a system.
    //
    // CallableT is anything that can get invoked to introspect an object hierarchy.
    // It supports cereal serialization out of the box in order to help writing out
    // object hierarchies to file, so one thing to be aware of is that when iterating
    // if you need to handle T's and cereal::NameValuePair<T>'s.
    //
    // The introspection contract isn't as strict as the serialization contract.
    // If a type is already serializable using cereal, it should already support
    // being introspected.
    //
    // If the type can't be serialized for any reason it can still be introspected.
    // All a type needs in order to be introspected is a free function with the following
    // signature:
    //
    // template<typename ArchiveT>
    // void introspect_object(introspector<ArchiveT>& archive, const Foo& object)
    // {
    //      archive(
    //              cereal::make_nvp("Param1", object.param1),
    //              object.param2 // if the name doesn't matter, but it usually helps.
    //      );
    // }
    //

    namespace internal
    {
        // helper struct to support nested introspectable types
        // that create objects in the serializer.
        struct nested_serialize_object
        {
            std::function<void()> callback;

            template<typename ArchiveT>
            void save(ArchiveT&) const
            {
                callback();
            }
        };

        // helper struct to support nested introspectable objects
        // that don't create objects but act as additional data to the parent.
        struct nested_serialize_inline
        {
            std::function<void()> callback;

            template<typename ArchiveT>
            void save(ArchiveT&) const
            {
                callback();
            }
        };

        inline void prologue(cereal::JSONOutputArchive&, const nested_serialize_inline&)
        {}

        inline void epilogue(cereal::JSONOutputArchive&, const nested_serialize_inline&)
        {}
    }

    template<typename CallableT>
    class introspector
    {
        template<typename L>
        struct has_internal_introspection_function_test
        {
            template<typename T,
                typename = decltype(introspect_object_internal(instance_of<introspector<CallableT>>(), instance_of<T>()))>
                static std::true_type test(T* = nullptr);

            static std::false_type test(...);
        };

        template<typename T>
        using has_internal_introspection_function = decltype(has_internal_introspection_function_test<CallableT>::test(&instance_of<T>()));

        template<typename L>
        struct has_custom_introspection_function_test
        {
            template<typename T,
                typename = decltype(introspect_object(instance_of<introspector<CallableT>>(), instance_of<T>()))>
            static std::true_type test(T* = nullptr);

            static std::false_type test(...);
        };

        template<typename T>
        using has_custom_introspection_function = decltype(has_custom_introspection_function_test<CallableT>::test(&instance_of<T>()));
    public:
        template<typename ItrT>
        explicit introspector(ItrT&& i)
            : m_callable{ std::forward<ItrT>(i) }
        {}

        using internally_introspected = std::integral_constant<size_t, 0>;
        using externally_introspected = std::integral_constant<size_t, 1>;
        using default_introspected = std::integral_constant<size_t, 2>;

        template<typename T>
        using introspection_type =
            find_first_index<has_internal_introspection_function<T>, has_custom_introspection_function<T>>;

        template<typename T>
        static constexpr bool should_introspect()
        {
            return introspection_type<T>::value < default_introspected::value;
        }

        template<typename ...ArgTs>
        void operator()(ArgTs&& ...args)
        {
            mira::static_foreach([this](auto&& arg)
            {
                introspect(std::forward<decltype(arg)>(arg));
            }, std::forward<ArgTs>(args)...);
        }

        template<typename T>
        void introspect(const T& value)
        {
            introspect_dispatch(value, introspection_type<T>{});
        }

        CallableT& callable()
        {
            return m_callable;
        }

        const CallableT& callable() const
        {
            return m_callable;
        }

    private:
        template<typename T>
        void introspect_dispatch(const T& value, internally_introspected)
        {
            introspect_object_internal(*this, value);
        }

        template<typename T>
        void introspect_dispatch(const T& value, externally_introspected)
        {
            internal::nested_serialize_object nested{ [&]
            {
                introspect_object(*this, value);
            } };
            m_callable(nested);
        }

        template<typename T>
        void introspect_dispatch(const T& value, default_introspected)
        {
            m_callable(value);
        }

        CallableT m_callable;
    };

    template<typename CallableT, typename T>
    void introspect_object_internal(introspector<CallableT>& ar, const cereal::NameValuePair<T>& named)
    {
        internal::nested_serialize_inline nested{ [&]
        {
            ar.introspect(named.value);
        } };
        ar.callable()(cereal::make_nvp(named.name, nested));
    }

    template<typename CallableT, typename T>
    void introspect_object_internal(introspector<CallableT>& ar, const T* object)
    {
        if (!object)
        {
            ar.callable()(cereal::make_nvp("pointer", nullptr));
        }
        else
        {
            internal::nested_serialize_inline nested{ [&]
            {
                ar.introspect(*object);
            } };
            ar.callable()(cereal::make_nvp("pointer", nested));
        }
    }

    template<typename CallableT, typename T>
    void introspect_object(introspector<CallableT>& ar, const std::shared_ptr<T>& ptr)
    {
        ar.introspect(ptr.get());
    }

    template<typename CallableT, typename T, typename D>
    void introspect_object(introspector<CallableT>& ar, const std::unique_ptr<T, D>& ptr)
    {
        ar.introspect(ptr.get());
    }

    template<typename CallableT, typename T, std::ptrdiff_t Extent>
    void introspect_object(introspector<CallableT>& ar, gsl::span<T, Extent> elements)
    {
        ar.callable()(cereal::SizeTag<cereal::size_type>(elements.size()));

        for (auto& el : elements)
        {
            ar.introspect(el);
        }
    }

    template<typename CallableT, typename T, typename AllocT>
    void introspect_object(introspector<CallableT>& ar, const std::vector<T, AllocT>& elements)
    {
        introspect_object(ar, gsl::make_span(elements));
    }

    template<typename CallableT, typename A, typename B>
    void introspect_object(introspector<CallableT>& ar, const std::pair<A, B>& elements)
    {
        ar(
            cereal::make_nvp("First", elements.first),
            cereal::make_nvp("Second", elements.second)
        );
    }
}


// The MIT License (MIT)
//
// Copyright (c) 2018 Mateusz Pusz
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#pragma once

// IWYU pragma: begin_exports
#include <units/bits/external/fixed_string.h>
#include <compare>
#include <cstdint>
#include <cstddef>
// IWYU pragma: end_exports

#include <gsl/gsl-lite.hpp>

namespace units {

namespace detail {

constexpr void validate_ascii_char([[maybe_unused]] char c) noexcept { gsl_Expects((c & 0x80) == 0); }

template<std::size_t N>
constexpr void validate_ascii_string([[maybe_unused]] const char (&s)[N + 1]) noexcept
{
#ifndef NDEBUG
  if constexpr (N != 0)
    for (size_t i = 0; i < N; ++i)
      validate_ascii_char(s[i]);
#endif
}

}  // namespace detail

/**
 * @brief A symbol text representation
 *
 * This class template is responsible for definition and handling of a symbol text
 * representation. In the libary it is used to define symbols of units and prefixes.
 * Each symbol can have two versions: Unicode and ASCI-only.
 *
 * @tparam StandardCharT Character type to be used for a Unicode representation
 * @tparam N The size of a Unicode symbol
 * @tparam M The size of the ASCII-only symbol
 */
template<typename StandardCharT, std::size_t N, std::size_t M>
struct basic_symbol_text {
  basic_fixed_string<StandardCharT, N> standard_;
  basic_fixed_string<char, M> ascii_;

  constexpr basic_symbol_text(char std) noexcept: standard_(std), ascii_(std) { detail::validate_ascii_char(std); }
  constexpr basic_symbol_text(StandardCharT std, char a) noexcept: standard_(std), ascii_(a) { detail::validate_ascii_char(a); }
  constexpr basic_symbol_text(const char (&std)[N + 1]) noexcept: standard_(std), ascii_(std) { detail::validate_ascii_string<N>(std); }
  constexpr basic_symbol_text(const basic_fixed_string<char, N>& std) noexcept: standard_(std), ascii_(std) { detail::validate_ascii_string<N>(std.data_); }
  constexpr basic_symbol_text(const StandardCharT (&std)[N + 1], const char (&a)[M + 1]) noexcept: standard_(std), ascii_(a) { detail::validate_ascii_string<M>(a); }
  constexpr basic_symbol_text(const basic_fixed_string<StandardCharT, N>& std, const basic_fixed_string<char, M>& a) noexcept: standard_(std), ascii_(a) { detail::validate_ascii_string<M>(a.data_); }

  [[nodiscard]] constexpr auto& standard() { return standard_; }
  [[nodiscard]] constexpr const auto& standard() const { return standard_; }
  [[nodiscard]] constexpr auto& ascii() { return ascii_; }
  [[nodiscard]] constexpr const auto& ascii() const { return ascii_; }

  template<std::size_t N2, std::size_t M2>
  [[nodiscard]] constexpr friend basic_symbol_text<StandardCharT, N + N2, M + M2> operator+(
      const basic_symbol_text& lhs, const basic_symbol_text<StandardCharT, N2, M2>& rhs) noexcept
  {
    return basic_symbol_text<StandardCharT, N + N2, M + M2>(
        lhs.standard() + rhs.standard(), lhs.ascii() + rhs.ascii());
  }

  template<std::size_t N2>
  [[nodiscard]] constexpr friend basic_symbol_text<StandardCharT, N + N2, M + N2> operator+(
      const basic_symbol_text& lhs, const basic_fixed_string<StandardCharT, N2>& rhs) noexcept
  {
    return lhs + basic_symbol_text<StandardCharT, N2, N2>(rhs);
  }

  template<std::size_t N2>
  [[nodiscard]] constexpr friend basic_symbol_text<StandardCharT, N + N2, M + N2> operator+(
      const basic_fixed_string<StandardCharT, N2>& lhs, const basic_symbol_text& rhs) noexcept
  {
    return basic_symbol_text<StandardCharT, N2, N2>(lhs) + rhs;
  }

  template<std::size_t N2>
  [[nodiscard]] constexpr friend basic_symbol_text<StandardCharT, N + N2 - 1, M + N2 - 1> operator+(
      const basic_symbol_text& lhs, const StandardCharT (&rhs)[N2]) noexcept
  {
    return lhs + basic_symbol_text<StandardCharT, N2 - 1, N2 - 1>(rhs);
  }

  template<std::size_t N2>
  [[nodiscard]] constexpr friend basic_symbol_text<StandardCharT, N + N2 - 1, M + N2 - 1> operator+(
      const StandardCharT (&lhs)[N2], const basic_symbol_text& rhs) noexcept
  {
    return basic_symbol_text<StandardCharT, N2 - 1, N2 - 1>(lhs) + rhs;
  }

  [[nodiscard]] constexpr friend basic_symbol_text<StandardCharT, N + 1, M + 1> operator+(
      const basic_symbol_text& lhs, StandardCharT rhs) noexcept
  {
    return lhs + basic_symbol_text<StandardCharT, 1, 1>(rhs);
  }

  [[nodiscard]] constexpr friend basic_symbol_text<StandardCharT, N + 1, M + 1> operator+(
      StandardCharT lhs, const basic_symbol_text& rhs) noexcept
  {
    return basic_symbol_text<StandardCharT, 1, 1>(lhs) + rhs;
  }

  template<typename StandardCharT2, std::size_t N2, std::size_t M2>
  [[nodiscard]] friend constexpr auto operator<=>(const basic_symbol_text& lhs,
                                                  const basic_symbol_text<StandardCharT2, N2, M2>& rhs) noexcept
  {
    if (const auto cmp = lhs.standard() <=> rhs.standard(); cmp != 0) return cmp;
    return lhs.ascii() <=> rhs.ascii();
  }

  template<typename StandardCharT2, std::size_t N2, std::size_t M2>
  [[nodiscard]] friend constexpr bool operator==(const basic_symbol_text& lhs,
                                                 const basic_symbol_text<StandardCharT2, N2, M2>& rhs) noexcept
  {
    return lhs.standard() == rhs.standard() && lhs.ascii() == rhs.ascii();
  }
};

basic_symbol_text(char) -> basic_symbol_text<char, 1, 1>;

template<typename StandardCharT>
basic_symbol_text(StandardCharT, char) -> basic_symbol_text<StandardCharT, 1, 1>;

template<std::size_t N>
basic_symbol_text(const char (&)[N]) -> basic_symbol_text<char, N - 1, N - 1>;

template<std::size_t N>
basic_symbol_text(const basic_fixed_string<char, N>&) -> basic_symbol_text<char, N, N>;

template<typename StandardCharT, std::size_t N, std::size_t M>
basic_symbol_text(const StandardCharT (&)[N], const char (&)[M]) -> basic_symbol_text<StandardCharT, N - 1, M - 1>;

template<typename StandardCharT, std::size_t N, std::size_t M>
basic_symbol_text(const basic_fixed_string<StandardCharT, N>&,
                  const basic_fixed_string<char, M>&) -> basic_symbol_text<StandardCharT, N, M>;

}  // namespace units


// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#pragma once

#include "arcana/type_traits.h"
#include "arcana/utils/serialization/base_stream.h"
#include "arcana/utils/serialization/serializable.h"

#include <gsl/gsl>
#include <memory>
#include <type_traits>

namespace mira
{
    template<typename LambdaT>
    class binary_iterator
    {
        template<typename T>
        using is_serializable = std::is_base_of<serializable<std::remove_cv_t<T>>, T>;

        template<typename L>
        struct check_custom_iterator
        {
            template<typename T,
                     typename = decltype(binary_iterate(instance_of<const T>(),
                                                        instance_of<binary_iterator<LambdaT>>()))>
            static std::true_type test(T* = nullptr);
            static std::false_type test(...);
        };

        template<typename T>
        using has_custom_iterator = decltype(check_custom_iterator<LambdaT>::test(&instance_of<T>()));

        template<typename T>
        using is_valid_pod =
            std::integral_constant<bool,
                                   !has_custom_iterator<T>::value && std::is_pod<std::decay_t<T>>::value &&
                                       !std::is_pointer<std::decay_t<T>>::value>;

    public:
        template<typename... ArgTs>
        void iterate_all(ArgTs&&... args)
        {
            int unused[] = { (iterate(std::forward<ArgTs>(args)), 0)... };
            (void)unused;
        }

        template<typename T>
        std::enable_if_t<has_custom_iterator<std::decay_t<T>>::value> iterate(const T& value)
        {
            static_assert(!is_valid_pod<T>::value, "we've got some overlap");
            binary_iterate(value, *this);
        }

        template<typename T>
        std::enable_if_t<is_valid_pod<std::decay_t<T>>::value> iterate(const T& ptr)
        {
            static_assert(!has_custom_iterator<T>::value, "we've got some overlap");
            stream{ *this }.write(ptr);
        }

        void iterate(const void* data, size_t size)
        {
            m_iter(data, size);
        }

        template<typename ItrT>
        void iterate(ItrT beg, ItrT end)
        {
            while (beg != end)
            {
                iterate(*beg);
                beg++;
            }
        }

        template<typename A, typename B>
        void iterate(const std::pair<A, B>& pair)
        {
            iterate(pair.first);
            iterate(pair.second);
        }

        template<typename T>
        void iterate(const std::shared_ptr<T>& ptr)
        {
            iterate(ptr.get());
        }

        template<typename T, ptrdiff_t E = -1>
        void iterate(const gsl::span<T, E>& span)
        {
            iterate(span.begin(), span.end());
        }

        template<typename T, typename D>
        void iterate(const std::unique_ptr<T, D>& ptr)
        {
            iterate(ptr.get());
        }

        template<typename T>
        void iterate(const T* ptr)
        {
            if (!ptr)
                iterate((intptr_t)0);
            else
                iterate(*ptr);
        }

        template<typename T>
        void iterate(const serializable<T>& ptr)
        {
            stream s{ *this };
            ptr.serialize(s);
        }

        template<typename ItrT>
        explicit binary_iterator(ItrT&& i)
            : m_iter{ std::forward<ItrT>(i) }
        {}

        LambdaT& iterator()
        {
            return m_iter;
        }

        const LambdaT& iterator() const
        {
            return m_iter;
        }

    private:
        struct stream : public base_stream
        {
            template<typename T>
            void write(const T& obj)
            {
                m_itr.iterate(&obj, sizeof(T));
            }

            template<typename T>
            void write(const T* obj, size_t N)
            {
                m_itr.iterate(obj, N * sizeof(T));
            }

            stream(binary_iterator<LambdaT>& itr)
                : m_itr{ itr }
            {}

        private:
            binary_iterator<LambdaT>& m_itr;
        };

        LambdaT m_iter;
    };

    template<typename LambdaT>
    binary_iterator<std::decay_t<LambdaT>> make_binary_iterator(LambdaT&& lambda)
    {
        return binary_iterator<std::decay_t<LambdaT>>{ std::forward<LambdaT>(lambda) };
    }
}

namespace std
{
    template<typename Iter>
    void binary_iterate(const std::string& str, mira::binary_iterator<Iter>& iter)
    {
        iter.iterate(str.data(), str.size());
    }

    template<typename T, typename Iter>
    void binary_iterate(const std::vector<T>& vec, mira::binary_iterator<Iter>& iter)
    {
        iter.iterate(vec.size());
        iter.iterate(vec.begin(), vec.end());
    }

    template<typename IterT, typename FirstT, typename SecondT>
    void binary_iterate(const std::pair<FirstT, SecondT>& data, mira::binary_iterator<IterT>& itr)
    {
        itr.iterate_all(data.first, data.second);
    }

    template<typename Iter, typename T>
    void binary_iterate(const std::shared_ptr<T>& ptr, mira::binary_iterator<Iter>& iter)
    {
        iter.iterate(ptr.get());
    }

    template<typename Iter, typename T>
    void binary_iterate(const std::unique_ptr<T>& ptr, mira::binary_iterator<Iter>& iter)
    {
        iter.iterate(ptr.get());
    }
}

namespace gsl
{
    template<typename Iter, typename T, ptrdiff_t E = -1>
    void binary_iterate(const gsl::span<T, E>& span, mira::binary_iterator<Iter>& iter)
    {
        iter.iterate(span.begin(), span.end());
    }
}


/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 *     Copyright 2017 Couchbase, Inc
 *
 *   Licensed 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 "monotonic.h"

#include <memcached/dockey.h>
#include <platform/sized_buffer.h>
#include <gsl/gsl>
#include <unordered_map>
#include <vector>

namespace Collections {

// The reserved name of the system owned, default collection.
const char* const _DefaultCollectionIdentifier = "_default";
static cb::const_char_buffer DefaultCollectionIdentifier(
        _DefaultCollectionIdentifier);

const char* const _DefaultScopeIdentifier = "_default";
static cb::const_char_buffer DefaultScopeIdentifier(_DefaultScopeIdentifier);

// SystemEvent keys or parts which will be made into keys
const char* const SystemSeparator = ":"; // Note this never changes
const char* const SystemEventPrefix = "_collections";
const char* const SystemEventPrefixWithSeparator = "_collections:";
const char* const DeleteKey = "_collections_delete:";

// Couchstore private file name for manifest data
const char CouchstoreManifest[] = "_local/collections_manifest";

// Length of the string excluding the zero terminator (i.e. strlen)
const size_t CouchstoreManifestLen = sizeof(CouchstoreManifest) - 1;

using ManifestUid = WeaklyMonotonic<uint64_t>;

// Map used in summary stats
using Summary = std::unordered_map<CollectionID, uint64_t>;

struct ManifestUidNetworkOrder {
    ManifestUidNetworkOrder(ManifestUid uid) : uid(htonll(uid)) {
    }
    ManifestUid to_host() const {
        return ntohll(uid);
    }
    ManifestUid uid;
};

/**
 * Return a ManifestUid from a C-string.
 * A valid ManifestUid is a C-string where each character satisfies
 * std::isxdigit and can be converted to a ManifestUid by std::strtoull.
 *
 * @param uid C-string uid
 * @param len a length for validation purposes
 * @throws std::invalid_argument if uid is invalid
 */
ManifestUid makeUid(const char* uid, size_t len = 16);

/**
 * Return a ManifestUid from a std::string
 * A valid ManifestUid is a std::string where each character satisfies
 * std::isxdigit and can be converted to a ManifestUid by std::strtoull.
 *
 * @param uid std::string
 * @throws std::invalid_argument if uid is invalid
 */
static inline ManifestUid makeUid(const std::string& uid) {
    return makeUid(uid.c_str());
}

/**
 * Return a CollectionID from a C-string.
 * A valid CollectionID is a C-string where each character satisfies
 * std::isxdigit and can be converted to a CollectionID by std::strtoul.
 *
 * @param uid C-string uid
 * @throws std::invalid_argument if uid is invalid
 */
static inline CollectionID makeCollectionID(const char* uid) {
    // CollectionID is 8 characters max and smaller than a ManifestUid
    return gsl::narrow_cast<CollectionID>(makeUid(uid, 8));
}

/**
 * Return a CollectionID from a std::string
 * A valid CollectionID is a std::string where each character satisfies
 * std::isxdigit and can be converted to a CollectionID by std::strtoul
 *
 * @param uid std::string
 * @throws std::invalid_argument if uid is invalid
 */
static inline CollectionID makeCollectionID(const std::string& uid) {
    return makeCollectionID(uid.c_str());
}

/**
 * Return a ScopeID from a C-string.
 * A valid CollectionID is a std::string where each character satisfies
 * std::isxdigit and can be converted to a CollectionID by std::strtoul
 * @param uid C-string uid
 * @return std::invalid_argument if uid is invalid
 */
static inline ScopeID makeScopeID(const char* uid) {
    // ScopeId is 8 characters max and smaller than a ManifestUid
    return gsl::narrow_cast<ScopeID>(makeUid(uid, 8));
}

/**
 * Return a ScopeID from a std::string
 * A valid ScopeID is a std::string where each character satisfies
 * std::isxdigit and can be converted to a CollectionID by std::strtoul
 * @param uid std::string
 * @return std::invalid_argument if uid is invalid
 */
static inline ScopeID makeScopeID(const std::string& uid) {
    return makeScopeID(uid.c_str());
}

/**
 * All of the data a system event needs
 */
struct SystemEventData {
    ManifestUid manifestUid; // The Manifest which created the event
    ScopeID sid; // The scope that the collection belongs to
    CollectionID cid; // The collection the event belongs to
};

/**
 * All of the data a DCP system event message will transmit in the value of the
 * message. This is the layout to be used on the wire and is in the correct
 * byte order
 */
struct SystemEventDcpData {
    SystemEventDcpData(const SystemEventData& data)
        : manifestUid(data.manifestUid), sid(data.sid), cid(data.cid) {
    }
    /// The manifest uid stored in network byte order ready for sending
    ManifestUidNetworkOrder manifestUid;
    /// The scope id stored in network byte order ready for sending
    ScopeIDNetworkOrder sid;
    /// The collection id stored in network byte order ready for sending
    CollectionIDNetworkOrder cid;
    // The size is sizeof(manifestUid) + sizeof(cid) + sizeof(sid) (msvc won't
    // allow that expression)
    constexpr static size_t size{16};
};

namespace VB {
/**
 * The PersistedManifest which stores a copy of the VB::Manifest, the actual
 * format of the data is defined by VB::Manifest
 */
using PersistedManifest = std::vector<uint8_t>;

} // namespace VB
} // end namespace Collections

std::ostream& operator<<(std::ostream& os,
                         const Collections::VB::PersistedManifest& data);


#pragma once

#include <chrono>
#include <unordered_map>
#include <vector>

#include <gsl/span>
#include <json.hpp>

#include "Quiver/Animation/AnimationId.h"
#include "Quiver/Animation/AnimationLibrary.h"
#include "Quiver/Animation/AnimatorId.h"
#include "Quiver/Animation/Rect.h"
#include "Quiver/Graphics/ViewBuffer.h"

namespace qvr {

struct AnimatorTarget {
	ViewBuffer views;

	AnimatorTarget() = default;

	AnimatorTarget(const AnimatorTarget&) = delete;
	AnimatorTarget(const AnimatorTarget&&) = delete;

	AnimatorTarget& operator=(AnimatorTarget&) = delete;
	AnimatorTarget& operator=(AnimatorTarget&&) = delete;
};

class AnimatorRepeatSetting
{
public:
	explicit AnimatorRepeatSetting(const int repeatCount) : m_RepeatCount(repeatCount) {}

	AnimatorRepeatSetting() = default;

	int GetRepeatCount() const { return m_RepeatCount; }

	static const AnimatorRepeatSetting Forever;
	static const AnimatorRepeatSetting Never;
	static const AnimatorRepeatSetting Once;
	static const AnimatorRepeatSetting Twice;

private:
	int m_RepeatCount = Forever.GetRepeatCount();
};

struct AnimatorStartSetting
{
	AnimatorStartSetting(AnimationId animationId)
		: m_AnimationId(animationId)
	{}

	AnimatorStartSetting(
		AnimationId animationId,
		AnimatorRepeatSetting repeatSetting)
		: m_AnimationId(animationId)
		, m_RepeatSetting(repeatSetting)
	{}

	AnimationId m_AnimationId;
	AnimatorRepeatSetting m_RepeatSetting;
};

inline bool operator==(const AnimatorRepeatSetting& a, const AnimatorRepeatSetting& b) {
	return a.GetRepeatCount() == b.GetRepeatCount();
}

inline bool operator!=(const AnimatorRepeatSetting& a, const AnimatorRepeatSetting& b) {
	return a.GetRepeatCount() != b.GetRepeatCount();
}

struct AnimationLibraryEditorData;

class AnimationData;

class AnimatorCollection {
public:
	auto GetAnimations() const -> const AnimationLibrary& {
		return animations;
	}

	AnimationId AddAnimation(const AnimationData& anim);
	AnimationId AddAnimation(
		const AnimationData& anim,
		const AnimationSourceInfo& animSourceInfo);

	bool RemoveAnimation(const AnimationId id);

	int GetReferenceCount(const AnimationId animation) const {
		if (animationReferenceCounts.count(animation) == 0) {
			return 0;
		}

		return animationReferenceCounts.at(animation);
	}

	AnimatorId Add(
		AnimatorTarget& target,
		const AnimatorStartSetting& startSetting);

	bool Remove(const AnimatorId id);

	bool Exists(const AnimatorId id) const {
		return animators.states.count(id) != 0;
	}

	int GetCount() const {
		return animators.states.size();
	}

	void AnimatorGui(const AnimatorId id);

	bool SetAnimation(
		const AnimatorId animatorId,
		const AnimatorStartSetting& animation,
		const bool clearQueue = true);

	bool SetTarget(
		const AnimatorId id,
		AnimatorTarget& newTarget);

	bool SetFrame(
		const AnimatorId id,
		const int index);

	bool QueueAnimation(
		const AnimatorId animatorId,
		const AnimatorStartSetting& pendingAnimation);

	bool ClearAnimationQueue(const AnimatorId id);

	unsigned GetFrame(const AnimatorId animatorId) const;

	AnimationId GetAnimation(const AnimatorId animatorId) const;

	void Animate(const Animation::TimeUnit ms);

private:
	struct AnimatorState {
		AnimatorState(
			const AnimationId animation,
			const int frameIndex,
			const int index,
			AnimatorTarget& target,
			const AnimatorRepeatSetting& repeat)
			: index(index)
			, currentFrame(frameIndex)
			, currentAnimation(animation)
			, target(&target)
			, repeatSetting(repeat)
			, repeatCount(0)
		{}

		AnimatorState() = default;

		int index;
		int currentFrame;
		int repeatCount;
		AnimationId currentAnimation;
		AnimatorRepeatSetting repeatSetting;
		AnimatorTarget* target;
		std::vector<AnimatorStartSetting> queuedAnimations;
	};

	struct AnimatorState_Hot {
		AnimatorState_Hot(
			const AnimatorId animatorId,
			const Animation::TimeUnit timeLeft)
			: animatorId(animatorId)
			, timeLeftInFrame(timeLeft)
		{}

		Animation::TimeUnit timeLeftInFrame;
		AnimatorId animatorId;
	};

	struct Animators {
		std::vector<AnimatorState_Hot> hotStates;
		std::unordered_map<AnimatorId, AnimatorState> states;
		AnimatorId GetNextAnimatorId();
	private:
		AnimatorId lastId = AnimatorId::Invalid;
	} animators;

	AnimationLibrary animations;

	std::unordered_map<AnimationId, unsigned> animationReferenceCounts;

	// friends:

	friend void GuiControls(
		AnimatorCollection& animators,
		AnimationLibraryEditorData& editorData);

	friend void to_json(nlohmann::json& j, const AnimatorCollection& animators);
	friend void from_json(const nlohmann::json& j, AnimatorCollection& animators);
};

void GuiControls(AnimatorCollection& animators, AnimationLibraryEditorData& editorData);

void to_json(nlohmann::json& j, const AnimatorCollection& animators);
void from_json(const nlohmann::json& j, AnimatorCollection& animators);

}


/*
   Copyright [2020] [IBM Corporation]
   Licensed 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.
*/

#ifndef _MCAS_NUPM_ARENA_
#define _MCAS_NUPM_ARENA_

#include <nupm/region_descriptor.h>
#include <common/logging.h>
#include <common/string_view.h>
#include <gsl/pointers> /* not_null */
#include <sys/uio.h> /* ::iovec */

#include <cstddef>
#include <vector>

namespace nupm
{
	struct registry_memory_mapped;
	struct space_registered;
}

struct arena
  : private common::log_source
{
  using region_descriptor = nupm::region_descriptor;
  using registry_memory_mapped = nupm::registry_memory_mapped;
  using space_registered = nupm::space_registered;
  using string_view = common::string_view;

  arena(const common::log_source &ls) : common::log_source(ls) {}
  virtual ~arena() {}
  virtual void debug_dump() const = 0;
  virtual region_descriptor region_get(const string_view &id) = 0;
  virtual region_descriptor region_create(const string_view &id, gsl::not_null<registry_memory_mapped *> mh, std::size_t size) = 0;
  virtual void region_resize(gsl::not_null<space_registered *> mh, std::size_t size) = 0;
  /* It is unknown whether region_erase may be used on an open region.
   * arena_fs assumes that it may, just as ::unlink can be used against
   * an open file.
   */
  virtual void region_erase(const string_view &id, gsl::not_null<registry_memory_mapped *> mh) = 0;
  virtual std::size_t get_max_available() = 0;
  virtual bool is_file_backed() const = 0;
protected:
  using common::log_source::debug_level;
};

#endif