#ifndef	_host_security_user_
#define	_host_security_user_

/* Module host_security */

#include <string.h>
#include <mach/ndr.h>
#include <mach/boolean.h>
#include <mach/kern_return.h>
#include <mach/notify.h>
#include <mach/mach_types.h>
#include <mach/message.h>
#include <mach/mig_errors.h>
#include <mach/port.h>

#ifdef AUTOTEST
#ifndef FUNCTION_PTR_T
#define FUNCTION_PTR_T
typedef void (*function_ptr_t)(mach_port_t, char *, mach_msg_type_number_t);
typedef struct {
        char            *name;
        function_ptr_t  function;
} function_table_entry;
typedef function_table_entry 	*function_table_t;
#endif /* FUNCTION_PTR_T */
#endif /* AUTOTEST */

#ifndef	host_security_MSG_COUNT
#define	host_security_MSG_COUNT	2
#endif	/* host_security_MSG_COUNT */

#include <mach/std_types.h>
#include <mach/mig.h>
#include <mach/mig.h>
#include <mach/mach_types.h>

#ifdef __BeforeMigUserHeader
__BeforeMigUserHeader
#endif /* __BeforeMigUserHeader */

#include <sys/cdefs.h>
__BEGIN_DECLS

/* Routine host_security_create_task_token */
#ifdef	mig_external
mig_external
#else
extern
#endif	/* mig_external */
kern_return_t host_security_create_task_token
(
	host_security_t host_security,
	task_t parent_task,
	security_token_t sec_token,
	audit_token_t audit_token,
	host_t host,
	ledger_array_t ledgers,
	mach_msg_type_number_t ledgersCnt,
	boolean_t inherit_memory,
	task_t *child_task
);

/* Routine host_security_set_task_token */
#ifdef	mig_external
mig_external
#else
extern
#endif	/* mig_external */
kern_return_t host_security_set_task_token
(
	host_security_t host_security,
	task_t target_task,
	security_token_t sec_token,
	audit_token_t audit_token,
	host_t host
);

__END_DECLS

/********************** Caution **************************/
/* The following data types should be used to calculate  */
/* maximum message sizes only. The actual message may be */
/* smaller, and the position of the arguments within the */
/* message layout may vary from what is presented here.  */
/* For example, if any of the arguments are variable-    */
/* sized, and less than the maximum is sent, the data    */
/* will be packed tight in the actual message to reduce  */
/* the presence of holes.                                */
/********************** Caution **************************/

/* typedefs for all requests */

#ifndef __Request__host_security_subsystem__defined
#define __Request__host_security_subsystem__defined
	typedef struct {
		mach_msg_header_t Head;
		/* start of the kernel processed data */
		mach_msg_body_t msgh_body;
		mach_msg_port_descriptor_t parent_task;
		mach_msg_port_descriptor_t host;
		mach_msg_ool_ports_descriptor_t ledgers;
		/* end of the kernel processed data */
		NDR_record_t NDR;
		security_token_t sec_token;
		audit_token_t audit_token;
		mach_msg_type_number_t ledgersCnt;
		boolean_t inherit_memory;
	} __Request__host_security_create_task_token_t;

	typedef struct {
		mach_msg_header_t Head;
		/* start of the kernel processed data */
		mach_msg_body_t msgh_body;
		mach_msg_port_descriptor_t target_task;
		mach_msg_port_descriptor_t host;
		/* end of the kernel processed data */
		NDR_record_t NDR;
		security_token_t sec_token;
		audit_token_t audit_token;
	} __Request__host_security_set_task_token_t;

#endif /* !__Request__host_security_subsystem__defined */

/* union of all requests */

#ifndef __RequestUnion__host_security_subsystem__defined
#define __RequestUnion__host_security_subsystem__defined
union __RequestUnion__host_security_subsystem {
	__Request__host_security_create_task_token_t Request_host_security_create_task_token;
	__Request__host_security_set_task_token_t Request_host_security_set_task_token;
};
#endif /* !__RequestUnion__host_security_subsystem__defined */
/* typedefs for all replies */

#ifndef __Reply__host_security_subsystem__defined
#define __Reply__host_security_subsystem__defined
	typedef struct {
		mach_msg_header_t Head;
		/* start of the kernel processed data */
		mach_msg_body_t msgh_body;
		mach_msg_port_descriptor_t child_task;
		/* end of the kernel processed data */
	} __Reply__host_security_create_task_token_t;

	typedef struct {
		mach_msg_header_t Head;
		NDR_record_t NDR;
		kern_return_t RetCode;
	} __Reply__host_security_set_task_token_t;

#endif /* !__Reply__host_security_subsystem__defined */

/* union of all replies */

#ifndef __ReplyUnion__host_security_subsystem__defined
#define __ReplyUnion__host_security_subsystem__defined
union __ReplyUnion__host_security_subsystem {
	__Reply__host_security_create_task_token_t Reply_host_security_create_task_token;
	__Reply__host_security_set_task_token_t Reply_host_security_set_task_token;
};
#endif /* !__RequestUnion__host_security_subsystem__defined */

#ifndef subsystem_to_name_map_host_security
#define subsystem_to_name_map_host_security \
    { "host_security_create_task_token", 600 },\
    { "host_security_set_task_token", 601 }
#endif

#ifdef __AfterMigUserHeader
__AfterMigUserHeader
#endif /* __AfterMigUserHeader */

#endif	 /* _host_security_user_ */


package com.sora.util.akatsuki.parceler;

import android.app.Fragment;

import com.sora.util.akatsuki.TransformationTemplate;
import com.sora.util.akatsuki.TransformationTemplate.StatementTemplate;
import com.sora.util.akatsuki.TransformationTemplate.StatementTemplate.Type;
import com.sora.util.akatsuki.TypeConstraint;
import com.sora.util.akatsuki.TypeConstraint.Bound;
import com.sora.util.akatsuki.TypeFilter;

import org.parceler.Parcel;
import org.parceler.Parcels;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Add {@code @IncludeClasses(ParcelerSupport.class) } to any class to enable
 */
//@formatter:off
@SuppressWarnings("unused")
@TransformationTemplate(
		save = @StatementTemplate("{{bundle}}.putParcelable({{keyName}}, {{fieldName}}==null?null:org.parceler.Parcels.wrap(" +
				                          "com.sora.util.akatsuki.parceler.ParcelerSupport.resolveInterfaceClass(" +
				                          "{{fieldName}}), {{fieldName}}))"),
		restore = @StatementTemplate(type = Type.ASSIGNMENT,
				                     value = "org.parceler.Parcels.unwrap({{bundle}}.getParcelable({{keyName}}))",
				                     variable = "{{fieldName}}"),
		filters = {@TypeFilter(type = @TypeConstraint(type = Parcel.class)),
				   @TypeFilter(type = @TypeConstraint(type = List.class, bound = Bound.EXTENDS),
						       parameters = @TypeConstraint(type = Parcel.class)),
				   @TypeFilter(type = @TypeConstraint(type = Set.class, bound = Bound.EXTENDS),
				               parameters = @TypeConstraint(type = Parcel.class)),
				   @TypeFilter(type = @TypeConstraint(type = Map.class, bound = Bound.EXTENDS),
				               parameters = @TypeConstraint(type = Parcel.class))
		          }
		)
//@formatter:on
public class ParcelerSupport {

	public static Class<?> resolveInterfaceClass(Object input) {
		if (input instanceof List) {
			return List.class;
		} else if (input instanceof Set) {
			return Set.class;
		} else if (input instanceof Map) {
			return Map.class;
		}
		return input.getClass();
	}

}


/*
	SuperCollider real time audio synthesis system
 Copyright (c) 2002 James McCartney. All rights reserved.
	http://www.audiosynth.com

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

//Nick Collins 16/9/2014
//Beat Statistics; using first stages of a beat tracker. Form beat histogram, then take (assumed tempo independent) statistics: entropy, ratio of first to second peak, harmonicity (how much explained by multiple positions relating to main peak)
//44.1KHz only, precalculated ERB arrays

#include "SC_PlugIn.h"
#include "FFT_UGens.h"

InterfaceTable *ft;

//hard coded FFT size
#define N 1024
#define NOVER2 512

//CONVERT TO m_frameperiod to cope with different sampling rates?
//#define FRAMEPERIOD 0.046439909297052

//1024 size FFT, hop 512, at 44.1kHz

//weighting parameters

int eqlbandbins[43]= {1,2,3,4,5,6,7,8,9,11,13,15,17,19,22,25,28,32,36,41,46,52,58,65,73,82,92,103,116,129,144,161,180,201,225,251,280,312,348,388,433,483,513};
int eqlbandsizes[42]= {1,1,1,1,1,1,1,1,2,2,2,2,2,3,3,3,4,4,5,5,6,6,7,8,9,10,11,13,13,15,17,19,21,24,26,29,32,36,40,45,50,30};
float contours[42][11]= {{ 47.88, 59.68, 68.55, 75.48, 81.71, 87.54, 93.24, 98.84,104.44,109.94,115.31},{ 29.04, 41.78, 51.98, 60.18, 67.51, 74.54, 81.34, 87.97, 94.61,101.21,107.74},{ 20.72, 32.83, 43.44, 52.18, 60.24, 67.89, 75.34, 82.70, 89.97, 97.23,104.49},{ 15.87, 27.14, 37.84, 46.94, 55.44, 63.57, 71.51, 79.34, 87.14, 94.97,102.37},{ 12.64, 23.24, 33.91, 43.27, 52.07, 60.57, 68.87, 77.10, 85.24, 93.44,100.90},{ 10.31, 20.43, 31.03, 40.54, 49.59, 58.33, 66.89, 75.43, 83.89, 92.34,100.80},{  8.51, 18.23, 28.83, 38.41, 47.65, 56.59, 65.42, 74.16, 82.89, 91.61,100.33},{  7.14, 16.55, 27.11, 36.79, 46.16, 55.27, 64.29, 73.24, 82.15, 91.06, 99.97},{  5.52, 14.58, 25.07, 34.88, 44.40, 53.73, 62.95, 72.18, 81.31, 90.44, 99.57},{  3.98, 12.69, 23.10, 32.99, 42.69, 52.27, 61.66, 71.15, 80.54, 89.93, 99.31},{  2.99, 11.43, 21.76, 31.73, 41.49, 51.22, 60.88, 70.51, 80.11, 89.70, 99.30},{  2.35, 10.58, 20.83, 30.86, 40.68, 50.51, 60.33, 70.08, 79.83, 89.58, 99.32},{  2.05, 10.12, 20.27, 30.35, 40.22, 50.10, 59.97, 69.82, 79.67, 89.52, 99.38},{  2.00,  9.93, 20.00, 30.07, 40.00, 49.93, 59.87, 69.80, 79.73, 89.67, 99.60},{  2.19, 10.00, 20.00, 30.00, 40.00, 50.00, 59.99, 69.99, 79.98, 89.98, 99.97},{  2.71, 10.56, 20.61, 30.71, 40.76, 50.81, 60.86, 70.96, 81.01, 91.06,101.17},{  3.11, 11.05, 21.19, 31.41, 41.53, 51.64, 61.75, 71.95, 82.05, 92.15,102.33},{  2.39, 10.69, 21.14, 31.52, 41.73, 51.95, 62.11, 72.31, 82.46, 92.56,102.59},{  1.50, 10.11, 20.82, 31.32, 41.62, 51.92, 62.12, 72.32, 82.52, 92.63,102.56},{ -0.17,  8.50, 19.27, 29.77, 40.07, 50.37, 60.57, 70.77, 80.97, 91.13,101.23},{ -1.80,  6.96, 17.77, 28.29, 38.61, 48.91, 59.13, 69.33, 79.53, 89.71, 99.86},{ -3.42,  5.49, 16.36, 26.94, 37.31, 47.61, 57.88, 68.08, 78.28, 88.41, 98.39},{ -4.73,  4.38, 15.34, 25.99, 36.39, 46.71, 57.01, 67.21, 77.41, 87.51, 97.41},{ -5.73,  3.63, 14.74, 25.48, 35.88, 46.26, 56.56, 66.76, 76.96, 87.06, 96.96},{ -6.24,  3.33, 14.59, 25.39, 35.84, 46.22, 56.52, 66.72, 76.92, 87.04, 97.00},{ -6.09,  3.62, 15.03, 25.83, 36.37, 46.70, 57.00, 67.20, 77.40, 87.57, 97.68},{ -5.32,  4.44, 15.90, 26.70, 37.28, 47.60, 57.90, 68.10, 78.30, 88.52, 98.78},{ -3.49,  6.17, 17.52, 28.32, 38.85, 49.22, 59.52, 69.72, 79.92, 90.20,100.61},{ -0.81,  8.58, 19.73, 30.44, 40.90, 51.24, 61.52, 71.69, 81.87, 92.15,102.63},{  2.91, 11.82, 22.64, 33.17, 43.53, 53.73, 63.96, 74.09, 84.22, 94.45,104.89},{  6.68, 15.19, 25.71, 36.03, 46.25, 56.31, 66.45, 76.49, 86.54, 96.72,107.15},{ 10.43, 18.65, 28.94, 39.02, 49.01, 58.98, 68.93, 78.78, 88.69, 98.83,109.36},{ 13.56, 21.65, 31.78, 41.68, 51.45, 61.31, 71.07, 80.73, 90.48,100.51,111.01},{ 14.36, 22.91, 33.19, 43.09, 52.71, 62.37, 71.92, 81.38, 90.88,100.56,110.56},{ 15.06, 23.90, 34.23, 44.05, 53.48, 62.90, 72.21, 81.43, 90.65, 99.93,109.34},{ 15.36, 23.90, 33.89, 43.31, 52.40, 61.42, 70.29, 79.18, 88.00, 96.69,105.17},{ 15.60, 23.90, 33.60, 42.70, 51.50, 60.20, 68.70, 77.30, 85.80, 94.00,101.70},{ 15.60, 23.90, 33.60, 42.70, 51.50, 60.20, 68.70, 77.30, 85.80, 94.00,101.70},{ 15.60, 23.90, 33.60, 42.70, 51.50, 60.20, 68.70, 77.30, 85.80, 94.00,101.70},{ 15.60, 23.90, 33.60, 42.70, 51.50, 60.20, 68.70, 77.30, 85.80, 94.00,101.70},{ 15.60, 23.90, 33.60, 42.70, 51.50, 60.20, 68.70, 77.30, 85.80, 94.00,101.70},{ 15.60, 23.90, 33.60, 42.70, 51.50, 60.20, 68.70, 77.30, 85.80, 94.00,101.70}};
double phons[11]={2,10,20,30,40,50,60,70,80,90,100};

//assumes 512 hop size
float g_periods[100] = { 57.421875, 56.84765625, 56.284808168317, 55.732996323529, 55.191899271845, 54.661207932692, 54.140625, 53.629864386792, 53.128650700935, 52.63671875, 52.153813073394, 51.6796875, 51.21410472973, 50.7568359375, 50.30766039823, 49.866365131579, 49.432744565217, 49.006600215517, 48.587740384615, 48.175979872881, 47.771139705882, 47.373046875, 46.981534090909, 46.59643954918, 46.217606707317, 45.844884072581, 45.478125, 45.1171875, 44.761934055118, 44.412231445313, 44.067950581395, 43.728966346154, 43.395157442748, 43.06640625, 42.742598684211, 42.423624067164, 42.109375, 41.799747242647, 41.49463959854, 41.193953804348, 40.89759442446, 40.60546875, 40.317486702128, 40.033560739437, 39.753605769231, 39.4775390625, 39.205280172414, 38.936750856164, 38.671875, 38.410578547297, 38.15278942953, 37.8984375, 37.647454470199, 37.399773848684, 37.155330882353, 36.9140625, 36.675907258065, 36.440805288462, 36.208698248408, 35.979529272152, 35.753242924528, 35.52978515625, 35.30910326087, 35.091145833333, 34.875862730061, 34.663205030488, 34.453125, 34.245576054217, 34.040512724551, 33.837890625, 33.637666420118, 33.439797794118, 33.244243421053, 33.050962936047, 32.859916907514, 32.671066810345, 32.484375, 32.2998046875, 32.117319915254, 31.936885533708, 31.758467178771, 31.58203125, 31.407544889503, 31.234975961538, 31.064293032787, 30.895465353261, 30.728462837838, 30.563256048387, 30.399816176471, 30.238115026596, 30.078125, 29.919819078947, 29.763170811518, 29.608154296875, 29.454744170984, 29.302915592784, 29.152644230769, 29.00390625, 28.856678299492, 28.7109375 };

struct BeatStatistics : Unit {

	//FFT data
	float * m_FFTBuf;
    int numERBbands_;
    float * ERBpowers_;
    float * previousERBpowers_;

	//coping with different sampling rates
	float m_srate;		//use as a flag to check sample rate is correct
	float m_frameperiod;

    int numperiods_;
	int numpreviousvalues_;
	int storepos_;
	float * store_;  //for previous values
	float * crosscomby_;

    //statistics output
    float entropy_;
    float toptworatio_;
    float diversity_;
    float metricity_;

};

extern "C"
{

	void BeatStatistics_next(BeatStatistics *unit, int wrongNumSamples);
	void BeatStatistics_Ctor(BeatStatistics *unit);
	void BeatStatistics_Dtor(BeatStatistics *unit);

}

//other functions
static void BeatStatistics_calculate(BeatStatistics *, uint32);

void BeatStatistics_Ctor(BeatStatistics* unit)
{
	unit->m_srate = unit->mWorld->mFullRate.mSampleRate;

	//if sample rate is 88200 or 96000, assume taking double size FFT to start with
	if(unit->m_srate > (44100.0*1.5)) unit->m_srate = unit->m_srate*0.5;

	if(((int)(unit->m_srate+0.01))==44100)
	{
		//unit->m_weights = g_weights44100;
		//unit->m_bins = g_bins44100;
		unit->m_frameperiod = 0.046439909297052;
	}
	else  //else 48000; potentially dangerous if it isn't! Fortunately, shouldn't write any data to unknown memory
	{
		//unit->m_weights = g_weights48000;
		//unit->m_bins = g_bins48000;
		unit->m_frameperiod = 0.042666666666667;
	}

	//only need space for half!
	unit->m_FFTBuf = (float*)RTAlloc(unit->mWorld, NOVER2 * sizeof(float));

    unit->numERBbands_ = 40;
    unit->ERBpowers_ = (float*)RTAlloc(unit->mWorld, unit->numERBbands_ * sizeof(float));
    unit->previousERBpowers_ = (float*)RTAlloc(unit->mWorld, unit->numERBbands_ * sizeof(float));

    //start zeroed out
    Clear(unit->numERBbands_, unit->previousERBpowers_);

    unit->numperiods_ = 100;
    unit->numpreviousvalues_ = 350;
    unit->storepos_ = 0;
    unit->store_ = (float*)RTAlloc(unit->mWorld, unit->numpreviousvalues_ * sizeof(float)); //for previous values
    unit->crosscomby_ = (float*)RTAlloc(unit->mWorld, unit->numperiods_ * sizeof(float));

    Clear(unit->numpreviousvalues_,unit->store_);
    Clear(unit->numperiods_, unit->crosscomby_);

    //for(int i=0; i<unit->numperiods_; ++i)
    //    printf("initial cross i %d at %f\n",i,unit->crosscomby_[i]);

    unit->entropy_ = 0.f;
    unit->toptworatio_ = 0.f;
    unit->diversity_ = 0.f;
    unit->metricity_ = 0.f;

	unit->mCalcFunc = (UnitCalcFunc)&BeatStatistics_next;
}

void BeatStatistics_Dtor(BeatStatistics *unit)
{
	RTFree(unit->mWorld, unit->m_FFTBuf);
    RTFree(unit->mWorld, unit->ERBpowers_);
    RTFree(unit->mWorld, unit->previousERBpowers_);
    RTFree(unit->mWorld, unit->store_);
    RTFree(unit->mWorld, unit->crosscomby_);

}

void BeatStatistics_next(BeatStatistics *unit, int wrongNumSamples)
{
	//int numSamples = unit->mWorld->mFullRate.mBufLength;

	//float *output = ZOUT(0);

	float fbufnum = ZIN0(0)+0.001;

	//next FFT bufffer ready, update
	//assuming at this point that buffer precalculated for any resampling
	if (fbufnum > -0.01f) {  // && ( ZIN0(3)<0.5)

		//unit->m_frame= unit->m_frame+1;
		BeatStatistics_calculate(unit, (uint32)fbufnum);
	}

	//control rate output
	ZOUT0(0)=unit->entropy_;
    ZOUT0(1)=unit->toptworatio_;
    ZOUT0(2)=unit->diversity_;
    ZOUT0(3)=unit->metricity_;

}

//calculation function once FFT data ready
void BeatStatistics_calculate(BeatStatistics *unit, uint32 ibufnum)
{

    int i, j, k;
	int prev, next;
	float interp;

	World *world = unit->mWorld;

    SndBuf *buf;

    if (ibufnum >= world->mNumSndBufs) {
		int localBufNum = ibufnum - world->mNumSndBufs;
		Graph *parent = unit->mParent;
		if(localBufNum <= parent->localBufNum) {
			buf = parent->mLocalSndBufs + localBufNum;
		} else {
			buf = world->mSndBufs;
			if(unit->mWorld->mVerbosity > -1){ Print("BeatStatistics error: Buffer number overrun: %i\n", ibufnum); }
		}
	} else {
		buf = world->mSndBufs + ibufnum;
	}

	LOCK_SNDBUF(buf);

	//assumed in this representation
	ToComplexApx(buf);

	const float * data= buf->data;

	//memcpy(unit->m_FFTBuf, data, NOVER2);

	//to hold powers
	float * fftbuf= unit->m_FFTBuf;

	//get powers for bins
	//don't need to calculate past half Nyquist, because no indices involved of harmonics above 10000 Hz or so (see index data at top of file)
	for (i=0; i<N; i+=2) {
		//i>>1 is i/2
		fftbuf[i>>1] = ((data[i] * data[i]) + (data[i+1] * data[i+1]));
	}

    //ERB channel power accumulation

    int numERBbands = unit->numERBbands_;
    float * ERBpowers = unit->ERBpowers_;

    //was 0, looking for problems
	for (k=0; k<numERBbands; ++k){

		int bandstart=eqlbandbins[k];
		//int bandend=eqlbandbins[k+1];
		int bandsize= eqlbandsizes[k];

		float bsum=0.0;

        //printf("%d %d %d \n",k,bandstart,bandsize);

		for (int h=0; h<bandsize;++h) {
			bsum += fftbuf[h+bandstart];
		}

		//store recips of bandsizes?
		//why averaging wihin band?
		bsum= bsum/bandsize;

		//into dB, avoid log of 0
		//float db= 10*log10((bsum*10000000)+0.001);
		float db= 10*log10((bsum*32382)+0.001);

		//printf("bsum %f db %f \n",bsum,db);

		//convert via contour
		if(db<contours[k][0]) db=0;
        else if (db>contours[k][10]) db=phons[10];
        else {

            float prop=0.0;

            for (j=1; j<11; ++j) {
                if(db<contours[k][j]) {
                    prop= (db-contours[k][j-1])/(contours[k][j]-contours[k][j-1]);
                    break;
				}

				if(j==10)
					prop=1.0;
            }

            db= (1.0-prop)*phons[j-1]+ prop*phons[j];
			//printf("prop %f db %f j %d\n",prop,db,j);

		}

		//float lastloud=unit->m_loudbands[k];
		//float lastloud=0.0;
        //
        //		for(j=0;j<PASTERBBANDS; ++j)
        //			lastloud+=unit->m_loudbands[k][j];
        //
        //		lastloud /= PASTERBBANDS;
        //
        //		float diff= sc_max(db-lastloud,0.0);

		//dfsum=dfsum+diff; //(bweights[k]*diff);

		ERBpowers[k]=db;

        //printf("erb k %d is %f \n",k,db);

		//must sum as intensities, not dbs once corrected, pow used to be other way around
		//loudsum= loudsum+((pow(10, 0.1*db)-0.001)*0.0000308813538386);

	}

    //accumulate over channels to make onset detection function

	float diffsum= 0.0;
	float diffnow;

    float * previous = unit->previousERBpowers_;

	//just sum first 10 for bassier signal?

	//40
	for (i=0; i<numERBbands; ++i) {
		diffnow= ERBpowers[i]-previous[i];

		if(diffnow>0) diffsum += diffnow;

		previous[i]= ERBpowers[i]; //ready for next time
	}

	//value[0]= diffsum; //will auto adjust to normalized range

    float value = diffsum*0.01; //up to 100dB?

    //printf("diffsum %f, value %f \n",diffsum, value);

    float * store = unit->store_;
    float * crosscomby = unit->crosscomby_;
    int storepos = unit->storepos_;
    int numpreviousvalues = unit->numpreviousvalues_;
    int numperiods = unit->numperiods_;

	store[storepos] = value;

    float total = 0.f;

    //0.995 default for accumulator
    float leak = ZIN0(1);

    int numpreviousbeats = ZIN0(2);

    if(numpreviousbeats<1) numpreviousbeats = 1;
    if(numpreviousbeats>4) numpreviousbeats = 4;

	//update leaky integrators
	for (i=0; i<numperiods; ++i) {

		float periodtotest = g_periods[i];

		float sumup = 0.0;

		//sum up to previous four beats compared to now

		float basepos = ( storepos + numpreviousvalues );

        if(value>0.0000001f) {
		for (k=1; k<=numpreviousbeats; ++k) {

			float posthen = fmod( basepos - (k*periodtotest), numpreviousvalues);

			prev = posthen;

			next = (prev+1)%numpreviousvalues;

			interp = posthen-prev;

            //if(crosscomby[i]>10.f) {
            //printf("k %d posthen %f prev %d next %d interp %f result %f %f %f %f %f \n",k,posthen,prev,next,interp, store[prev], store[next], value, ((store[prev]*(1.0-interp)) + ((interp)*store[next])), value * ((store[prev]*(1.0-interp)) + ((interp)*store[next])));
            //}

			sumup +=  value * ((store[prev]*(1.0-interp)) + ((interp)*store[next]));

		}
        }

		//0.996
        //avoid denormal accumulation just in case
        if(crosscomby[i]>0.000001f)
            crosscomby[i] = (crosscomby[i] * leak) + sumup;
        else
            crosscomby[i] = sumup;

        //if(i<10)
        //printf("cross i %d with sum %f is %f \n",i,sumup,crosscomby[i]);

        total += crosscomby[i];
	}

    //for(i=0; i<10; ++i)
    //    printf("current cross i %d at %f\n",i,crosscomby[i]);

	storepos = (storepos + 1) % numpreviousvalues;

    unit->storepos_ = storepos;

    float totalmult = 1.f;

    if(total>0.0000001f) {
        totalmult = 1.0f/total; //normalization factor for working with all periods as probability distribution summing to 1.0
    }

    float entropy = 0.f;
    float largest = 0.f;
    float secondlargest = 0.f;
    float diversity = 1.0f;
    float metricity = 1.f;
    int largestindex = 0;

    for (i=0; i<numperiods; ++i) {

        float now = crosscomby[i]*totalmult;

        if (now>largest) {

            secondlargest = largest;
            largest = now;
            largestindex = i;

        } else if (now>secondlargest) {

            secondlargest = now;
        }

        if (now>0.00001f) {

            entropy -= now * log2(now);
        }

        diversity -= now*now;

    }

    float topperiod = g_periods[largestindex];
    float relativeperiod;

    //knowing largest, score energy in others relative to this with weighting based on how metrically related period is (e.g. look for near integer ratios)
    for (i=0; i<numperiods; ++i) {

        float now = crosscomby[i]*totalmult;
        float periodnow = g_periods[i];

        if(periodnow<topperiod)
            relativeperiod = topperiod/periodnow;
        else
            relativeperiod = periodnow/topperiod;

        float offinteger = relativeperiod - (int)relativeperiod;

        if(offinteger>0.5) offinteger = 1.f - offinteger;

        //if(offinteger<0.05) offinteger = 0.f;
        //else offinteger *= 5.0;

        metricity -= offinteger*now; //penalty for energy at off integer multiples/divisors

    }

    float ratio = 0.f;

    if(secondlargest>0.00001f) {

        ratio = largest/secondlargest;

    }

    //proportion largest versus total?
    //printf("diffsum %f, value %f \n",diffsum, value);
    //printf("entropy %f toptworatio %f \n",entropy,ratio);

    unit->entropy_ = entropy;
    unit->toptworatio_ = ratio;
    unit->diversity_ = diversity;
    unit->metricity_ = metricity;

	//float keyleak= ZIN0(1); //fade parameter to 0.01 for histogram in seconds, convert to FFT frames

	//unit->m_currentKey=bestkey;

}

PluginLoad(BeatStatistics) {

	init_SCComplex(inTable);

	ft = inTable;

	DefineDtorCantAliasUnit(BeatStatistics);

}


; File generated automatically from ../wine/dlls/olesvr32/olesvr32.spec; do not edit!

LIBRARY olesvr32.dll

EXPORTS
  WEP @1 PRIVATE
  OleRegisterServer @2
  OleRevokeServer @3
  OleBlockServer @4
  OleUnblockServer @5
  OleRegisterServerDoc @6
  OleRevokeServerDoc @7
  OleRenameServerDoc @8
  OleRevertServerDoc @9
  OleSavedServerDoc @10
  OleRevokeObject @11 PRIVATE
  OleQueryServerVersion @12 PRIVATE


var apply = require('./_apply'),
    castPath = require('./_castPath'),
    isKey = require('./_isKey'),
    last = require('./last'),
    parent = require('./_parent'),
    toKey = require('./_toKey');

/**
 * The base implementation of `_.invoke` without support for individual
 * method arguments.
 *
 * @private
 * @param {Object} object The object to query.
 * @param {Array|string} path The path of the method to invoke.
 * @param {Array} args The arguments to invoke the method with.
 * @returns {*} Returns the result of the invoked method.
 */
function baseInvoke(object, path, args) {
  if (!isKey(path, object)) {
    path = castPath(path);
    object = parent(object, path);
    path = last(path);
  }
  var func = object == null ? object : object[toKey(path)];
  return func == null ? undefined : apply(func, object, args);
}

module.exports = baseInvoke;


// Code generated by protoc-gen-go.
// source: control.proto
// DO NOT EDIT!

/*
Package grpc_testing is a generated protocol buffer package.

It is generated from these files:
	control.proto
	messages.proto
	payloads.proto
	services.proto
	stats.proto

It has these top-level messages:
	PoissonParams
	UniformParams
	DeterministicParams
	ParetoParams
	ClosedLoopParams
	LoadParams
	SecurityParams
	ClientConfig
	ClientStatus
	Mark
	ClientArgs
	ServerConfig
	ServerArgs
	ServerStatus
	CoreRequest
	CoreResponse
	Void
	Scenario
	Scenarios
	Payload
	EchoStatus
	SimpleRequest
	SimpleResponse
	StreamingInputCallRequest
	StreamingInputCallResponse
	ResponseParameters
	StreamingOutputCallRequest
	StreamingOutputCallResponse
	ReconnectParams
	ReconnectInfo
	ByteBufferParams
	SimpleProtoParams
	ComplexProtoParams
	PayloadConfig
	ServerStats
	HistogramParams
	HistogramData
	ClientStats
*/
package grpc_testing

import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package

type ClientType int32

const (
	ClientType_SYNC_CLIENT  ClientType = 0
	ClientType_ASYNC_CLIENT ClientType = 1
)

var ClientType_name = map[int32]string{
	0: "SYNC_CLIENT",
	1: "ASYNC_CLIENT",
}
var ClientType_value = map[string]int32{
	"SYNC_CLIENT":  0,
	"ASYNC_CLIENT": 1,
}

func (x ClientType) String() string {
	return proto.EnumName(ClientType_name, int32(x))
}
func (ClientType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }

type ServerType int32

const (
	ServerType_SYNC_SERVER          ServerType = 0
	ServerType_ASYNC_SERVER         ServerType = 1
	ServerType_ASYNC_GENERIC_SERVER ServerType = 2
)

var ServerType_name = map[int32]string{
	0: "SYNC_SERVER",
	1: "ASYNC_SERVER",
	2: "ASYNC_GENERIC_SERVER",
}
var ServerType_value = map[string]int32{
	"SYNC_SERVER":          0,
	"ASYNC_SERVER":         1,
	"ASYNC_GENERIC_SERVER": 2,
}

func (x ServerType) String() string {
	return proto.EnumName(ServerType_name, int32(x))
}
func (ServerType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }

type RpcType int32

const (
	RpcType_UNARY     RpcType = 0
	RpcType_STREAMING RpcType = 1
)

var RpcType_name = map[int32]string{
	0: "UNARY",
	1: "STREAMING",
}
var RpcType_value = map[string]int32{
	"UNARY":     0,
	"STREAMING": 1,
}

func (x RpcType) String() string {
	return proto.EnumName(RpcType_name, int32(x))
}
func (RpcType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }

// Parameters of poisson process distribution, which is a good representation
// of activity coming in from independent identical stationary sources.
type PoissonParams struct {
	// The rate of arrivals (a.k.a. lambda parameter of the exp distribution).
	OfferedLoad float64 `protobuf:"fixed64,1,opt,name=offered_load,json=offeredLoad" json:"offered_load,omitempty"`
}

func (m *PoissonParams) Reset()                    { *m = PoissonParams{} }
func (m *PoissonParams) String() string            { return proto.CompactTextString(m) }
func (*PoissonParams) ProtoMessage()               {}
func (*PoissonParams) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }

type UniformParams struct {
	InterarrivalLo float64 `protobuf:"fixed64,1,opt,name=interarrival_lo,json=interarrivalLo" json:"interarrival_lo,omitempty"`
	InterarrivalHi float64 `protobuf:"fixed64,2,opt,name=interarrival_hi,json=interarrivalHi" json:"interarrival_hi,omitempty"`
}

func (m *UniformParams) Reset()                    { *m = UniformParams{} }
func (m *UniformParams) String() string            { return proto.CompactTextString(m) }
func (*UniformParams) ProtoMessage()               {}
func (*UniformParams) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }

type DeterministicParams struct {
	OfferedLoad float64 `protobuf:"fixed64,1,opt,name=offered_load,json=offeredLoad" json:"offered_load,omitempty"`
}

func (m *DeterministicParams) Reset()                    { *m = DeterministicParams{} }
func (m *DeterministicParams) String() string            { return proto.CompactTextString(m) }
func (*DeterministicParams) ProtoMessage()               {}
func (*DeterministicParams) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }

type ParetoParams struct {
	InterarrivalBase float64 `protobuf:"fixed64,1,opt,name=interarrival_base,json=interarrivalBase" json:"interarrival_base,omitempty"`
	Alpha            float64 `protobuf:"fixed64,2,opt,name=alpha" json:"alpha,omitempty"`
}

func (m *ParetoParams) Reset()                    { *m = ParetoParams{} }
func (m *ParetoParams) String() string            { return proto.CompactTextString(m) }
func (*ParetoParams) ProtoMessage()               {}
func (*ParetoParams) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }

// Once an RPC finishes, immediately start a new one.
// No configuration parameters needed.
type ClosedLoopParams struct {
}

func (m *ClosedLoopParams) Reset()                    { *m = ClosedLoopParams{} }
func (m *ClosedLoopParams) String() string            { return proto.CompactTextString(m) }
func (*ClosedLoopParams) ProtoMessage()               {}
func (*ClosedLoopParams) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }

type LoadParams struct {
	// Types that are valid to be assigned to Load:
	//	*LoadParams_ClosedLoop
	//	*LoadParams_Poisson
	//	*LoadParams_Uniform
	//	*LoadParams_Determ
	//	*LoadParams_Pareto
	Load isLoadParams_Load `protobuf_oneof:"load"`
}

func (m *LoadParams) Reset()                    { *m = LoadParams{} }
func (m *LoadParams) String() string            { return proto.CompactTextString(m) }
func (*LoadParams) ProtoMessage()               {}
func (*LoadParams) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }

type isLoadParams_Load interface {
	isLoadParams_Load()
}

type LoadParams_ClosedLoop struct {
	ClosedLoop *ClosedLoopParams `protobuf:"bytes,1,opt,name=closed_loop,json=closedLoop,oneof"`
}
type LoadParams_Poisson struct {
	Poisson *PoissonParams `protobuf:"bytes,2,opt,name=poisson,oneof"`
}
type LoadParams_Uniform struct {
	Uniform *UniformParams `protobuf:"bytes,3,opt,name=uniform,oneof"`
}
type LoadParams_Determ struct {
	Determ *DeterministicParams `protobuf:"bytes,4,opt,name=determ,oneof"`
}
type LoadParams_Pareto struct {
	Pareto *ParetoParams `protobuf:"bytes,5,opt,name=pareto,oneof"`
}

func (*LoadParams_ClosedLoop) isLoadParams_Load() {}
func (*LoadParams_Poisson) isLoadParams_Load()    {}
func (*LoadParams_Uniform) isLoadParams_Load()    {}
func (*LoadParams_Determ) isLoadParams_Load()     {}
func (*LoadParams_Pareto) isLoadParams_Load()     {}

func (m *LoadParams) GetLoad() isLoadParams_Load {
	if m != nil {
		return m.Load
	}
	return nil
}

func (m *LoadParams) GetClosedLoop() *ClosedLoopParams {
	if x, ok := m.GetLoad().(*LoadParams_ClosedLoop); ok {
		return x.ClosedLoop
	}
	return nil
}

func (m *LoadParams) GetPoisson() *PoissonParams {
	if x, ok := m.GetLoad().(*LoadParams_Poisson); ok {
		return x.Poisson
	}
	return nil
}

func (m *LoadParams) GetUniform() *UniformParams {
	if x, ok := m.GetLoad().(*LoadParams_Uniform); ok {
		return x.Uniform
	}
	return nil
}

func (m *LoadParams) GetDeterm() *DeterministicParams {
	if x, ok := m.GetLoad().(*LoadParams_Determ); ok {
		return x.Determ
	}
	return nil
}

func (m *LoadParams) GetPareto() *ParetoParams {
	if x, ok := m.GetLoad().(*LoadParams_Pareto); ok {
		return x.Pareto
	}
	return nil
}

// XXX_OneofFuncs is for the internal use of the proto package.
func (*LoadParams) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) {
	return _LoadParams_OneofMarshaler, _LoadParams_OneofUnmarshaler, _LoadParams_OneofSizer, []interface{}{
		(*LoadParams_ClosedLoop)(nil),
		(*LoadParams_Poisson)(nil),
		(*LoadParams_Uniform)(nil),
		(*LoadParams_Determ)(nil),
		(*LoadParams_Pareto)(nil),
	}
}

func _LoadParams_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
	m := msg.(*LoadParams)
	// load
	switch x := m.Load.(type) {
	case *LoadParams_ClosedLoop:
		b.EncodeVarint(1<<3 | proto.WireBytes)
		if err := b.EncodeMessage(x.ClosedLoop); err != nil {
			return err
		}
	case *LoadParams_Poisson:
		b.EncodeVarint(2<<3 | proto.WireBytes)
		if err := b.EncodeMessage(x.Poisson); err != nil {
			return err
		}
	case *LoadParams_Uniform:
		b.EncodeVarint(3<<3 | proto.WireBytes)
		if err := b.EncodeMessage(x.Uniform); err != nil {
			return err
		}
	case *LoadParams_Determ:
		b.EncodeVarint(4<<3 | proto.WireBytes)
		if err := b.EncodeMessage(x.Determ); err != nil {
			return err
		}
	case *LoadParams_Pareto:
		b.EncodeVarint(5<<3 | proto.WireBytes)
		if err := b.EncodeMessage(x.Pareto); err != nil {
			return err
		}
	case nil:
	default:
		return fmt.Errorf("LoadParams.Load has unexpected type %T", x)
	}
	return nil
}

func _LoadParams_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
	m := msg.(*LoadParams)
	switch tag {
	case 1: // load.closed_loop
		if wire != proto.WireBytes {
			return true, proto.ErrInternalBadWireType
		}
		msg := new(ClosedLoopParams)
		err := b.DecodeMessage(msg)
		m.Load = &LoadParams_ClosedLoop{msg}
		return true, err
	case 2: // load.poisson
		if wire != proto.WireBytes {
			return true, proto.ErrInternalBadWireType
		}
		msg := new(PoissonParams)
		err := b.DecodeMessage(msg)
		m.Load = &LoadParams_Poisson{msg}
		return true, err
	case 3: // load.uniform
		if wire != proto.WireBytes {
			return true, proto.ErrInternalBadWireType
		}
		msg := new(UniformParams)
		err := b.DecodeMessage(msg)
		m.Load = &LoadParams_Uniform{msg}
		return true, err
	case 4: // load.determ
		if wire != proto.WireBytes {
			return true, proto.ErrInternalBadWireType
		}
		msg := new(DeterministicParams)
		err := b.DecodeMessage(msg)
		m.Load = &LoadParams_Determ{msg}
		return true, err
	case 5: // load.pareto
		if wire != proto.WireBytes {
			return true, proto.ErrInternalBadWireType
		}
		msg := new(ParetoParams)
		err := b.DecodeMessage(msg)
		m.Load = &LoadParams_Pareto{msg}
		return true, err
	default:
		return false, nil
	}
}

func _LoadParams_OneofSizer(msg proto.Message) (n int) {
	m := msg.(*LoadParams)
	// load
	switch x := m.Load.(type) {
	case *LoadParams_ClosedLoop:
		s := proto.Size(x.ClosedLoop)
		n += proto.SizeVarint(1<<3 | proto.WireBytes)
		n += proto.SizeVarint(uint64(s))
		n += s
	case *LoadParams_Poisson:
		s := proto.Size(x.Poisson)
		n += proto.SizeVarint(2<<3 | proto.WireBytes)
		n += proto.SizeVarint(uint64(s))
		n += s
	case *LoadParams_Uniform:
		s := proto.Size(x.Uniform)
		n += proto.SizeVarint(3<<3 | proto.WireBytes)
		n += proto.SizeVarint(uint64(s))
		n += s
	case *LoadParams_Determ:
		s := proto.Size(x.Determ)
		n += proto.SizeVarint(4<<3 | proto.WireBytes)
		n += proto.SizeVarint(uint64(s))
		n += s
	case *LoadParams_Pareto:
		s := proto.Size(x.Pareto)
		n += proto.SizeVarint(5<<3 | proto.WireBytes)
		n += proto.SizeVarint(uint64(s))
		n += s
	case nil:
	default:
		panic(fmt.Sprintf("proto: unexpected type %T in oneof", x))
	}
	return n
}

// presence of SecurityParams implies use of TLS
type SecurityParams struct {
	UseTestCa          bool   `protobuf:"varint,1,opt,name=use_test_ca,json=useTestCa" json:"use_test_ca,omitempty"`
	ServerHostOverride string `protobuf:"bytes,2,opt,name=server_host_override,json=serverHostOverride" json:"server_host_override,omitempty"`
}

func (m *SecurityParams) Reset()                    { *m = SecurityParams{} }
func (m *SecurityParams) String() string            { return proto.CompactTextString(m) }
func (*SecurityParams) ProtoMessage()               {}
func (*SecurityParams) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }

type ClientConfig struct {
	// List of targets to connect to. At least one target needs to be specified.
	ServerTargets  []string        `protobuf:"bytes,1,rep,name=server_targets,json=serverTargets" json:"server_targets,omitempty"`
	ClientType     ClientType      `protobuf:"varint,2,opt,name=client_type,json=clientType,enum=grpc.testing.ClientType" json:"client_type,omitempty"`
	SecurityParams *SecurityParams `protobuf:"bytes,3,opt,name=security_params,json=securityParams" json:"security_params,omitempty"`
	// How many concurrent RPCs to start for each channel.
	// For synchronous client, use a separate thread for each outstanding RPC.
	OutstandingRpcsPerChannel int32 `protobuf:"varint,4,opt,name=outstanding_rpcs_per_channel,json=outstandingRpcsPerChannel" json:"outstanding_rpcs_per_channel,omitempty"`
	// Number of independent client channels to create.
	// i-th channel will connect to server_target[i % server_targets.size()]
	ClientChannels int32 `protobuf:"varint,5,opt,name=client_channels,json=clientChannels" json:"client_channels,omitempty"`
	// Only for async client. Number of threads to use to start/manage RPCs.
	AsyncClientThreads int32   `protobuf:"varint,7,opt,name=async_client_threads,json=asyncClientThreads" json:"async_client_threads,omitempty"`
	RpcType            RpcType `protobuf:"varint,8,opt,name=rpc_type,json=rpcType,enum=grpc.testing.RpcType" json:"rpc_type,omitempty"`
	// The requested load for the entire client (aggregated over all the threads).
	LoadParams      *LoadParams      `protobuf:"bytes,10,opt,name=load_params,json=loadParams" json:"load_params,omitempty"`
	PayloadConfig   *PayloadConfig   `protobuf:"bytes,11,opt,name=payload_config,json=payloadConfig" json:"payload_config,omitempty"`
	HistogramParams *HistogramParams `protobuf:"bytes,12,opt,name=histogram_params,json=histogramParams" json:"histogram_params,omitempty"`
	// Specify the cores we should run the client on, if desired
	CoreList  []int32 `protobuf:"varint,13,rep,name=core_list,json=coreList" json:"core_list,omitempty"`
	CoreLimit int32   `protobuf:"varint,14,opt,name=core_limit,json=coreLimit" json:"core_limit,omitempty"`
}

func (m *ClientConfig) Reset()                    { *m = ClientConfig{} }
func (m *ClientConfig) String() string            { return proto.CompactTextString(m) }
func (*ClientConfig) ProtoMessage()               {}
func (*ClientConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} }

func (m *ClientConfig) GetSecurityParams() *SecurityParams {
	if m != nil {
		return m.SecurityParams
	}
	return nil
}

func (m *ClientConfig) GetLoadParams() *LoadParams {
	if m != nil {
		return m.LoadParams
	}
	return nil
}

func (m *ClientConfig) GetPayloadConfig() *PayloadConfig {
	if m != nil {
		return m.PayloadConfig
	}
	return nil
}

func (m *ClientConfig) GetHistogramParams() *HistogramParams {
	if m != nil {
		return m.HistogramParams
	}
	return nil
}

type ClientStatus struct {
	Stats *ClientStats `protobuf:"bytes,1,opt,name=stats" json:"stats,omitempty"`
}

func (m *ClientStatus) Reset()                    { *m = ClientStatus{} }
func (m *ClientStatus) String() string            { return proto.CompactTextString(m) }
func (*ClientStatus) ProtoMessage()               {}
func (*ClientStatus) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} }

func (m *ClientStatus) GetStats() *ClientStats {
	if m != nil {
		return m.Stats
	}
	return nil
}

// Request current stats
type Mark struct {
	// if true, the stats will be reset after taking their snapshot.
	Reset_ bool `protobuf:"varint,1,opt,name=reset" json:"reset,omitempty"`
}

func (m *Mark) Reset()                    { *m = Mark{} }
func (m *Mark) String() string            { return proto.CompactTextString(m) }
func (*Mark) ProtoMessage()               {}
func (*Mark) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} }

type ClientArgs struct {
	// Types that are valid to be assigned to Argtype:
	//	*ClientArgs_Setup
	//	*ClientArgs_Mark
	Argtype isClientArgs_Argtype `protobuf_oneof:"argtype"`
}

func (m *ClientArgs) Reset()                    { *m = ClientArgs{} }
func (m *ClientArgs) String() string            { return proto.CompactTextString(m) }
func (*ClientArgs) ProtoMessage()               {}
func (*ClientArgs) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} }

type isClientArgs_Argtype interface {
	isClientArgs_Argtype()
}

type ClientArgs_Setup struct {
	Setup *ClientConfig `protobuf:"bytes,1,opt,name=setup,oneof"`
}
type ClientArgs_Mark struct {
	Mark *Mark `protobuf:"bytes,2,opt,name=mark,oneof"`
}

func (*ClientArgs_Setup) isClientArgs_Argtype() {}
func (*ClientArgs_Mark) isClientArgs_Argtype()  {}

func (m *ClientArgs) GetArgtype() isClientArgs_Argtype {
	if m != nil {
		return m.Argtype
	}
	return nil
}

func (m *ClientArgs) GetSetup() *ClientConfig {
	if x, ok := m.GetArgtype().(*ClientArgs_Setup); ok {
		return x.Setup
	}
	return nil
}

func (m *ClientArgs) GetMark() *Mark {
	if x, ok := m.GetArgtype().(*ClientArgs_Mark); ok {
		return x.Mark
	}
	return nil
}

// XXX_OneofFuncs is for the internal use of the proto package.
func (*ClientArgs) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) {
	return _ClientArgs_OneofMarshaler, _ClientArgs_OneofUnmarshaler, _ClientArgs_OneofSizer, []interface{}{
		(*ClientArgs_Setup)(nil),
		(*ClientArgs_Mark)(nil),
	}
}

func _ClientArgs_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
	m := msg.(*ClientArgs)
	// argtype
	switch x := m.Argtype.(type) {
	case *ClientArgs_Setup:
		b.EncodeVarint(1<<3 | proto.WireBytes)
		if err := b.EncodeMessage(x.Setup); err != nil {
			return err
		}
	case *ClientArgs_Mark:
		b.EncodeVarint(2<<3 | proto.WireBytes)
		if err := b.EncodeMessage(x.Mark); err != nil {
			return err
		}
	case nil:
	default:
		return fmt.Errorf("ClientArgs.Argtype has unexpected type %T", x)
	}
	return nil
}

func _ClientArgs_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
	m := msg.(*ClientArgs)
	switch tag {
	case 1: // argtype.setup
		if wire != proto.WireBytes {
			return true, proto.ErrInternalBadWireType
		}
		msg := new(ClientConfig)
		err := b.DecodeMessage(msg)
		m.Argtype = &ClientArgs_Setup{msg}
		return true, err
	case 2: // argtype.mark
		if wire != proto.WireBytes {
			return true, proto.ErrInternalBadWireType
		}
		msg := new(Mark)
		err := b.DecodeMessage(msg)
		m.Argtype = &ClientArgs_Mark{msg}
		return true, err
	default:
		return false, nil
	}
}

func _ClientArgs_OneofSizer(msg proto.Message) (n int) {
	m := msg.(*ClientArgs)
	// argtype
	switch x := m.Argtype.(type) {
	case *ClientArgs_Setup:
		s := proto.Size(x.Setup)
		n += proto.SizeVarint(1<<3 | proto.WireBytes)
		n += proto.SizeVarint(uint64(s))
		n += s
	case *ClientArgs_Mark:
		s := proto.Size(x.Mark)
		n += proto.SizeVarint(2<<3 | proto.WireBytes)
		n += proto.SizeVarint(uint64(s))
		n += s
	case nil:
	default:
		panic(fmt.Sprintf("proto: unexpected type %T in oneof", x))
	}
	return n
}

type ServerConfig struct {
	ServerType     ServerType      `protobuf:"varint,1,opt,name=server_type,json=serverType,enum=grpc.testing.ServerType" json:"server_type,omitempty"`
	SecurityParams *SecurityParams `protobuf:"bytes,2,opt,name=security_params,json=securityParams" json:"security_params,omitempty"`
	// Port on which to listen. Zero means pick unused port.
	Port int32 `protobuf:"varint,4,opt,name=port" json:"port,omitempty"`
	// Only for async server. Number of threads used to serve the requests.
	AsyncServerThreads int32 `protobuf:"varint,7,opt,name=async_server_threads,json=asyncServerThreads" json:"async_server_threads,omitempty"`
	// Specify the number of cores to limit server to, if desired
	CoreLimit int32 `protobuf:"varint,8,opt,name=core_limit,json=coreLimit" json:"core_limit,omitempty"`
	// payload config, used in generic server
	PayloadConfig *PayloadConfig `protobuf:"bytes,9,opt,name=payload_config,json=payloadConfig" json:"payload_config,omitempty"`
	// Specify the cores we should run the server on, if desired
	CoreList []int32 `protobuf:"varint,10,rep,name=core_list,json=coreList" json:"core_list,omitempty"`
}

func (m *ServerConfig) Reset()                    { *m = ServerConfig{} }
func (m *ServerConfig) String() string            { return proto.CompactTextString(m) }
func (*ServerConfig) ProtoMessage()               {}
func (*ServerConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} }

func (m *ServerConfig) GetSecurityParams() *SecurityParams {
	if m != nil {
		return m.SecurityParams
	}
	return nil
}

func (m *ServerConfig) GetPayloadConfig() *PayloadConfig {
	if m != nil {
		return m.PayloadConfig
	}
	return nil
}

type ServerArgs struct {
	// Types that are valid to be assigned to Argtype:
	//	*ServerArgs_Setup
	//	*ServerArgs_Mark
	Argtype isServerArgs_Argtype `protobuf_oneof:"argtype"`
}

func (m *ServerArgs) Reset()                    { *m = ServerArgs{} }
func (m *ServerArgs) String() string            { return proto.CompactTextString(m) }
func (*ServerArgs) ProtoMessage()               {}
func (*ServerArgs) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} }

type isServerArgs_Argtype interface {
	isServerArgs_Argtype()
}

type ServerArgs_Setup struct {
	Setup *ServerConfig `protobuf:"bytes,1,opt,name=setup,oneof"`
}
type ServerArgs_Mark struct {
	Mark *Mark `protobuf:"bytes,2,opt,name=mark,oneof"`
}

func (*ServerArgs_Setup) isServerArgs_Argtype() {}
func (*ServerArgs_Mark) isServerArgs_Argtype()  {}

func (m *ServerArgs) GetArgtype() isServerArgs_Argtype {
	if m != nil {
		return m.Argtype
	}
	return nil
}

func (m *ServerArgs) GetSetup() *ServerConfig {
	if x, ok := m.GetArgtype().(*ServerArgs_Setup); ok {
		return x.Setup
	}
	return nil
}

func (m *ServerArgs) GetMark() *Mark {
	if x, ok := m.GetArgtype().(*ServerArgs_Mark); ok {
		return x.Mark
	}
	return nil
}

// XXX_OneofFuncs is for the internal use of the proto package.
func (*ServerArgs) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) {
	return _ServerArgs_OneofMarshaler, _ServerArgs_OneofUnmarshaler, _ServerArgs_OneofSizer, []interface{}{
		(*ServerArgs_Setup)(nil),
		(*ServerArgs_Mark)(nil),
	}
}

func _ServerArgs_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
	m := msg.(*ServerArgs)
	// argtype
	switch x := m.Argtype.(type) {
	case *ServerArgs_Setup:
		b.EncodeVarint(1<<3 | proto.WireBytes)
		if err := b.EncodeMessage(x.Setup); err != nil {
			return err
		}
	case *ServerArgs_Mark:
		b.EncodeVarint(2<<3 | proto.WireBytes)
		if err := b.EncodeMessage(x.Mark); err != nil {
			return err
		}
	case nil:
	default:
		return fmt.Errorf("ServerArgs.Argtype has unexpected type %T", x)
	}
	return nil
}

func _ServerArgs_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
	m := msg.(*ServerArgs)
	switch tag {
	case 1: // argtype.setup
		if wire != proto.WireBytes {
			return true, proto.ErrInternalBadWireType
		}
		msg := new(ServerConfig)
		err := b.DecodeMessage(msg)
		m.Argtype = &ServerArgs_Setup{msg}
		return true, err
	case 2: // argtype.mark
		if wire != proto.WireBytes {
			return true, proto.ErrInternalBadWireType
		}
		msg := new(Mark)
		err := b.DecodeMessage(msg)
		m.Argtype = &ServerArgs_Mark{msg}
		return true, err
	default:
		return false, nil
	}
}

func _ServerArgs_OneofSizer(msg proto.Message) (n int) {
	m := msg.(*ServerArgs)
	// argtype
	switch x := m.Argtype.(type) {
	case *ServerArgs_Setup:
		s := proto.Size(x.Setup)
		n += proto.SizeVarint(1<<3 | proto.WireBytes)
		n += proto.SizeVarint(uint64(s))
		n += s
	case *ServerArgs_Mark:
		s := proto.Size(x.Mark)
		n += proto.SizeVarint(2<<3 | proto.WireBytes)
		n += proto.SizeVarint(uint64(s))
		n += s
	case nil:
	default:
		panic(fmt.Sprintf("proto: unexpected type %T in oneof", x))
	}
	return n
}

type ServerStatus struct {
	Stats *ServerStats `protobuf:"bytes,1,opt,name=stats" json:"stats,omitempty"`
	// the port bound by the server
	Port int32 `protobuf:"varint,2,opt,name=port" json:"port,omitempty"`
	// Number of cores available to the server
	Cores int32 `protobuf:"varint,3,opt,name=cores" json:"cores,omitempty"`
}

func (m *ServerStatus) Reset()                    { *m = ServerStatus{} }
func (m *ServerStatus) String() string            { return proto.CompactTextString(m) }
func (*ServerStatus) ProtoMessage()               {}
func (*ServerStatus) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} }

func (m *ServerStatus) GetStats() *ServerStats {
	if m != nil {
		return m.Stats
	}
	return nil
}

type CoreRequest struct {
}

func (m *CoreRequest) Reset()                    { *m = CoreRequest{} }
func (m *CoreRequest) String() string            { return proto.CompactTextString(m) }
func (*CoreRequest) ProtoMessage()               {}
func (*CoreRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} }

type CoreResponse struct {
	// Number of cores available on the server
	Cores int32 `protobuf:"varint,1,opt,name=cores" json:"cores,omitempty"`
}

func (m *CoreResponse) Reset()                    { *m = CoreResponse{} }
func (m *CoreResponse) String() string            { return proto.CompactTextString(m) }
func (*CoreResponse) ProtoMessage()               {}
func (*CoreResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} }

type Void struct {
}

func (m *Void) Reset()                    { *m = Void{} }
func (m *Void) String() string            { return proto.CompactTextString(m) }
func (*Void) ProtoMessage()               {}
func (*Void) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{16} }

// A single performance scenario: input to qps_json_driver
type Scenario struct {
	// Human readable name for this scenario
	Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
	// Client configuration
	ClientConfig *ClientConfig `protobuf:"bytes,2,opt,name=client_config,json=clientConfig" json:"client_config,omitempty"`
	// Number of clients to start for the test
	NumClients int32 `protobuf:"varint,3,opt,name=num_clients,json=numClients" json:"num_clients,omitempty"`
	// Server configuration
	ServerConfig *ServerConfig `protobuf:"bytes,4,opt,name=server_config,json=serverConfig" json:"server_config,omitempty"`
	// Number of servers to start for the test
	NumServers int32 `protobuf:"varint,5,opt,name=num_servers,json=numServers" json:"num_servers,omitempty"`
	// Warmup period, in seconds
	WarmupSeconds int32 `protobuf:"varint,6,opt,name=warmup_seconds,json=warmupSeconds" json:"warmup_seconds,omitempty"`
	// Benchmark time, in seconds
	BenchmarkSeconds int32 `protobuf:"varint,7,opt,name=benchmark_seconds,json=benchmarkSeconds" json:"benchmark_seconds,omitempty"`
	// Number of workers to spawn locally (usually zero)
	SpawnLocalWorkerCount int32 `protobuf:"varint,8,opt,name=spawn_local_worker_count,json=spawnLocalWorkerCount" json:"spawn_local_worker_count,omitempty"`
}

func (m *Scenario) Reset()                    { *m = Scenario{} }
func (m *Scenario) String() string            { return proto.CompactTextString(m) }
func (*Scenario) ProtoMessage()               {}
func (*Scenario) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{17} }

func (m *Scenario) GetClientConfig() *ClientConfig {
	if m != nil {
		return m.ClientConfig
	}
	return nil
}

func (m *Scenario) GetServerConfig() *ServerConfig {
	if m != nil {
		return m.ServerConfig
	}
	return nil
}

// A set of scenarios to be run with qps_json_driver
type Scenarios struct {
	Scenarios []*Scenario `protobuf:"bytes,1,rep,name=scenarios" json:"scenarios,omitempty"`
}

func (m *Scenarios) Reset()                    { *m = Scenarios{} }
func (m *Scenarios) String() string            { return proto.CompactTextString(m) }
func (*Scenarios) ProtoMessage()               {}
func (*Scenarios) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{18} }

func (m *Scenarios) GetScenarios() []*Scenario {
	if m != nil {
		return m.Scenarios
	}
	return nil
}

func init() {
	proto.RegisterType((*PoissonParams)(nil), "grpc.testing.PoissonParams")
	proto.RegisterType((*UniformParams)(nil), "grpc.testing.UniformParams")
	proto.RegisterType((*DeterministicParams)(nil), "grpc.testing.DeterministicParams")
	proto.RegisterType((*ParetoParams)(nil), "grpc.testing.ParetoParams")
	proto.RegisterType((*ClosedLoopParams)(nil), "grpc.testing.ClosedLoopParams")
	proto.RegisterType((*LoadParams)(nil), "grpc.testing.LoadParams")
	proto.RegisterType((*SecurityParams)(nil), "grpc.testing.SecurityParams")
	proto.RegisterType((*ClientConfig)(nil), "grpc.testing.ClientConfig")
	proto.RegisterType((*ClientStatus)(nil), "grpc.testing.ClientStatus")
	proto.RegisterType((*Mark)(nil), "grpc.testing.Mark")
	proto.RegisterType((*ClientArgs)(nil), "grpc.testing.ClientArgs")
	proto.RegisterType((*ServerConfig)(nil), "grpc.testing.ServerConfig")
	proto.RegisterType((*ServerArgs)(nil), "grpc.testing.ServerArgs")
	proto.RegisterType((*ServerStatus)(nil), "grpc.testing.ServerStatus")
	proto.RegisterType((*CoreRequest)(nil), "grpc.testing.CoreRequest")
	proto.RegisterType((*CoreResponse)(nil), "grpc.testing.CoreResponse")
	proto.RegisterType((*Void)(nil), "grpc.testing.Void")
	proto.RegisterType((*Scenario)(nil), "grpc.testing.Scenario")
	proto.RegisterType((*Scenarios)(nil), "grpc.testing.Scenarios")
	proto.RegisterEnum("grpc.testing.ClientType", ClientType_name, ClientType_value)
	proto.RegisterEnum("grpc.testing.ServerType", ServerType_name, ServerType_value)
	proto.RegisterEnum("grpc.testing.RpcType", RpcType_name, RpcType_value)
}

func init() { proto.RegisterFile("control.proto", fileDescriptor0) }

var fileDescriptor0 = []byte{
	// 1162 bytes of a gzipped FileDescriptorProto
	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x56, 0xdd, 0x6e, 0xdb, 0x46,
	0x13, 0x8d, 0x14, 0xc9, 0x96, 0x86, 0x92, 0xac, 0x6f, 0xbf, 0xa4, 0x60, 0x1c, 0x27, 0x6d, 0xd8,
	0x16, 0x0d, 0x5c, 0xc0, 0x29, 0xd4, 0x02, 0x69, 0xd1, 0x8b, 0x40, 0x56, 0x85, 0xd8, 0x80, 0xe3,
	0xba, 0x2b, 0x27, 0x45, 0xae, 0x08, 0x9a, 0x5a, 0x4b, 0x44, 0x24, 0x2e, 0xbb, 0x4b, 0xc6, 0xf0,
	0x2b, 0xf4, 0x99, 0xfa, 0x1c, 0x7d, 0x8d, 0xbe, 0x42, 0x67, 0xff, 0x64, 0x52, 0x11, 0x10, 0xb7,
	0xbd, 0xe3, 0xce, 0x9c, 0xb3, 0x3b, 0x3b, 0x67, 0x66, 0x96, 0xd0, 0x8d, 0x79, 0x9a, 0x0b, 0xbe,
	0x38, 0xc8, 0x04, 0xcf, 0x39, 0xe9, 0xcc, 0x44, 0x16, 0x1f, 0xe4, 0x4c, 0xe6, 0x49, 0x3a, 0xdb,
	0xed, 0x65, 0xd1, 0xf5, 0x82, 0x47, 0x53, 0x69, 0xbc, 0xbb, 0x9e, 0xcc, 0xa3, 0xdc, 0x2e, 0x82,
	0x01, 0x74, 0xcf, 0x78, 0x22, 0x25, 0x4f, 0xcf, 0x22, 0x11, 0x2d, 0x25, 0x79, 0x02, 0x1d, 0x7e,
	0x79, 0xc9, 0x04, 0x9b, 0x86, 0x8a, 0xe4, 0xd7, 0x3e, 0xab, 0x3d, 0xad, 0x51, 0xcf, 0xda, 0x4e,
	0xd0, 0x14, 0x44, 0xd0, 0x7d, 0x9d, 0x26, 0x97, 0x5c, 0x2c, 0x2d, 0xe7, 0x2b, 0xd8, 0x49, 0xd2,
	0x9c, 0x89, 0x48, 0x88, 0xe4, 0x7d, 0xb4, 0x40, 0xa2, 0xa5, 0xf5, 0xca, 0xe6, 0x13, 0xfe, 0x01,
	0x70, 0x9e, 0xf8, 0xf5, 0x0f, 0x81, 0x47, 0x49, 0xf0, 0x3d, 0xfc, 0xff, 0x27, 0x86, 0x96, 0x65,
	0x92, 0x26, 0x78, 0x8b, 0xf8, 0xf6, 0xc1, 0xfd, 0x02, 0x1d, 0x04, 0xb3, 0x9c, 0x5b, 0xca, 0xd7,
	0xf0, 0xbf, 0xca, 0x91, 0x17, 0x91, 0x64, 0x96, 0xd7, 0x2f, 0x3b, 0x0e, 0xd1, 0x4e, 0xee, 0x41,
	0x33, 0x5a, 0x64, 0xf3, 0xc8, 0x46, 0x65, 0x16, 0x01, 0x81, 0xfe, 0x68, 0xc1, 0xa5, 0x3a, 0x80,
	0x67, 0x66, 0xdb, 0xe0, 0x8f, 0x3a, 0x80, 0x3a, 0xcf, 0x9e, 0x32, 0x04, 0x2f, 0xd6, 0x10, 0x8c,
	0x8b, 0x67, 0x7a, 0x7f, 0x6f, 0xf0, 0xf8, 0xa0, 0xac, 0xc3, 0xc1, 0xfa, 0x1e, 0x47, 0x77, 0x28,
	0xc4, 0x2b, 0x1b, 0x79, 0x0e, 0xdb, 0x99, 0x51, 0x42, 0x9f, 0xee, 0x0d, 0x1e, 0x56, 0xe9, 0x15,
	0x99, 0x90, 0xeb, 0xd0, 0x8a, 0x58, 0x18, 0x39, 0xfc, 0xbb, 0x9b, 0x88, 0x15, 0xad, 0x14, 0xd1,
	0xa2, 0xc9, 0x8f, 0xb0, 0x35, 0xd5, 0x49, 0xf6, 0x1b, 0x9a, 0xf7, 0xa4, 0xca, 0xdb, 0x20, 0x00,
	0xb2, 0x2d, 0x85, 0x7c, 0x07, 0x5b, 0x99, 0xce, 0xb3, 0xdf, 0xd4, 0xe4, 0xdd, 0xb5, 0x68, 0x4b,
	0x1a, 0x28, 0x96, 0xc1, 0x1e, 0x6e, 0x41, 0x43, 0x09, 0x17, 0x5c, 0x40, 0x6f, 0xc2, 0xe2, 0x42,
	0x24, 0xf9, 0xb5, 0xcd, 0xe0, 0x63, 0xf0, 0x0a, 0xc9, 0x42, 0xc5, 0x0f, 0xe3, 0x48, 0x67, 0xb0,
	0x45, 0xdb, 0x68, 0x3a, 0x47, 0xcb, 0x28, 0x22, 0xdf, 0xc0, 0x3d, 0xc9, 0xc4, 0x7b, 0x26, 0xc2,
	0x39, 0x47, 0x08, 0xc7, 0x2f, 0x91, 0x4c, 0x99, 0xce, 0x55, 0x9b, 0x12, 0xe3, 0x3b, 0x42, 0xd7,
	0xcf, 0xd6, 0x13, 0xfc, 0xde, 0x84, 0xce, 0x68, 0x91, 0xb0, 0x34, 0x1f, 0xf1, 0xf4, 0x32, 0x99,
	0x91, 0x2f, 0xa1, 0x67, 0xb7, 0xc8, 0x23, 0x31, 0x63, 0xb9, 0xc4, 0x53, 0xee, 0x22, 0xb9, 0x6b,
	0xac, 0xe7, 0xc6, 0x48, 0x7e, 0x50, 0x5a, 0x2a, 0x5a, 0x98, 0x5f, 0x67, 0xe6, 0x80, 0xde, 0xc0,
	0x5f, 0xd7, 0x52, 0x01, 0xce, 0xd1, 0xaf, 0x34, 0x74, 0xdf, 0x64, 0x0c, 0x3b, 0xd2, 0x5e, 0x2b,
	0xcc, 0xf4, 0xbd, 0xac, 0x24, 0x7b, 0x55, 0x7a, 0xf5, 0xee, 0xb4, 0x27, 0xab, 0xb9, 0x78, 0x01,
	0x7b, 0xbc, 0xc8, 0xb1, 0x4d, 0xd3, 0x29, 0xa2, 0x43, 0x64, 0xca, 0x30, 0xc3, 0xb0, 0xe3, 0x79,
	0x94, 0xa6, 0x6c, 0xa1, 0xe5, 0x6a, 0xd2, 0x07, 0x25, 0x0c, 0x45, 0xc8, 0x19, 0x13, 0x23, 0x03,
	0x50, 0x7d, 0x66, 0xaf, 0x60, 0x29, 0x52, 0xab, 0xd4, 0xa4, 0x3d, 0x63, 0xb6, 0x38, 0xa9, 0xb2,
	0x1a, 0xc9, 0xeb, 0x34, 0x0e, 0xdd, 0x8d, 0xe7, 0x82, 0xe1, 0xa4, 0xf0, 0xb7, 0x35, 0x9a, 0x68,
	0x9f, 0xbd, 0xab, 0xf1, 0x20, 0xa3, 0x85, 0xf1, 0x98, 0xd4, 0xb4, 0x74, 0x6a, 0xee, 0x57, 0xef,
	0x86, 0xa1, 0xe8, 0xbc, 0x6c, 0x0b, 0xf3, 0xa1, 0xf2, 0xa9, 0x34, 0x77, 0x09, 0x01, 0x9d, 0x90,
	0xb5, 0x7c, 0xde, 0xb4, 0x12, 0x85, 0xc5, 0x4d, 0x5b, 0x1d, 0x82, 0x1b, 0x5e, 0x61, 0xac, 0x35,
	0xf4, 0xbd, 0x8d, 0xad, 0x61, 0x30, 0x46, 0x66, 0xda, 0xcd, 0xca, 0x4b, 0x72, 0x04, 0xfd, 0x39,
	0x96, 0x30, 0x9f, 0xe1, 0x8e, 0x2e, 0x86, 0x8e, 0xde, 0xe5, 0x51, 0x75, 0x97, 0x23, 0x87, 0xb2,
	0x81, 0xec, 0xcc, 0xab, 0x06, 0xf2, 0x10, 0xda, 0x31, 0x17, 0x2c, 0x5c, 0xa0, 0xdd, 0xef, 0x62,
	0xe9, 0x34, 0x69, 0x4b, 0x19, 0x4e, 0x70, 0x4d, 0x1e, 0x01, 0x58, 0xe7, 0x32, 0xc9, 0xfd, 0x9e,
	0xce, 0x5f, 0xdb, 0x78, 0xd1, 0x10, 0xbc, 0x70, 0xb5, 0x38, 0xc1, 0xe1, 0x5b, 0x48, 0xf2, 0x0c,
	0x9a, 0x7a, 0x0c, 0xdb, 0x51, 0xf1, 0x60, 0x53, 0x79, 0x29, 0xa8, 0xa4, 0x06, 0x17, 0xec, 0x41,
	0xe3, 0x55, 0x24, 0xde, 0xa9, 0x11, 0x25, 0x98, 0x64, 0xb9, 0xed, 0x10, 0xb3, 0x08, 0x0a, 0x00,
	0xc3, 0x19, 0x8a, 0x99, 0x24, 0x03, 0xdc, 0x9c, 0xe5, 0x85, 0x9b, 0x43, 0xbb, 0x9b, 0x36, 0x37,
	0xd9, 0xc1, 0xd6, 0x34, 0x50, 0xf2, 0x14, 0x1a, 0x4b, 0xdc, 0xdf, 0xce, 0x1e, 0x52, 0xa5, 0xa8,
	0x93, 0x11, 0xaa, 0x11, 0x87, 0x6d, 0xd8, 0xc6, 0x4e, 0x51, 0x05, 0x10, 0xfc, 0x59, 0x87, 0xce,
	0x44, 0x37, 0x8f, 0x4d, 0x36, 0x6a, 0xed, 0x5a, 0x4c, 0x15, 0x48, 0x6d, 0x53, 0xef, 0x18, 0x82,
	0xe9, 0x1d, 0xb9, 0xfa, 0xde, 0xd4, 0x3b, 0xf5, 0x7f, 0xd1, 0x3b, 0x04, 0x1a, 0x19, 0x17, 0xb9,
	0xed, 0x11, 0xfd, 0x7d, 0x53, 0xe5, 0x2e, 0xb6, 0x0d, 0x55, 0x6e, 0xa3, 0xb2, 0x55, 0x5e, 0x55,
	0xb3, 0xb5, 0xa6, 0xe6, 0x86, 0xba, 0x6c, 0xff, 0xe3, 0xba, 0xac, 0x54, 0x13, 0x54, 0xab, 0x49,
	0xe9, 0x69, 0x02, 0xba, 0x85, 0x9e, 0x65, 0x01, 0xfe, 0xa3, 0x9e, 0x89, 0x93, 0xf3, 0x56, 0x55,
	0x7a, 0x03, 0x75, 0x55, 0xba, 0xca, 0x7e, 0xbd, 0x94, 0x7d, 0xac, 0x58, 0x75, 0x2f, 0x33, 0x0a,
	0x9b, 0xd4, 0x2c, 0x82, 0x2e, 0x78, 0x23, 0xfc, 0xa0, 0xec, 0xb7, 0x02, 0xb7, 0x0b, 0xbe, 0xc0,
	0xfe, 0xd0, 0x4b, 0x99, 0xf1, 0xd4, 0xbc, 0xc4, 0x86, 0x54, 0x2b, 0x93, 0xf0, 0xf9, 0x78, 0xc3,
	0x93, 0x69, 0xf0, 0x57, 0x1d, 0x5a, 0x93, 0x98, 0xa5, 0x91, 0x48, 0xb8, 0x3a, 0x33, 0x8d, 0x96,
	0xa6, 0xd8, 0xda, 0x54, 0x7f, 0xe3, 0x04, 0xed, 0xba, 0x01, 0x68, 0xf4, 0xa9, 0x7f, 0xac, 0x13,
	0x68, 0x27, 0x2e, 0xbf, 0x15, 0x9f, 0x82, 0x97, 0x16, 0x4b, 0x3b, 0x16, 0x5d, 0xe8, 0x80, 0x26,
	0xc3, 0x51, 0x33, 0xda, 0x3e, 0x1b, 0xee, 0x84, 0xc6, 0xc7, 0xb4, 0xa1, 0x1d, 0x59, 0x6e, 0x15,
	0x7b, 0x82, 0xb1, 0xb9, 0xf9, 0xac, 0x4e, 0x30, 0x1c, 0xa9, 0x9e, 0xab, 0xab, 0x48, 0x2c, 0x8b,
	0x0c, 0x31, 0x78, 0x06, 0xd6, 0xeb, 0x96, 0xc6, 0x74, 0x8d, 0x75, 0x62, 0x8c, 0xea, 0x07, 0xe7,
	0x82, 0xa5, 0xf1, 0x5c, 0x69, 0xb9, 0x42, 0x9a, 0xca, 0xee, 0xaf, 0x1c, 0x0e, 0xfc, 0x1c, 0x7c,
	0x99, 0x45, 0x57, 0x29, 0xfe, 0xa6, 0xc4, 0xf8, 0x33, 0x74, 0xc5, 0xc5, 0x3b, 0x7d, 0x83, 0x22,
	0x75, 0x55, 0x7e, 0x5f, 0xfb, 0x4f, 0x94, 0xfb, 0x57, 0xed, 0x1d, 0x29, 0x67, 0x30, 0x84, 0xb6,
	0x4b, 0xb8, 0xc4, 0xb7, 0xbf, 0x2d, 0xdd, 0x42, 0xbf, 0xa1, 0xde, 0xe0, 0x93, 0xb5, 0x7b, 0x5b,
	0x37, 0xbd, 0x01, 0xee, 0x3f, 0x73, 0x33, 0x4a, 0xb7, 0xfb, 0x0e, 0x78, 0x93, 0xb7, 0xa7, 0xa3,
	0x70, 0x74, 0x72, 0x3c, 0x3e, 0x3d, 0xef, 0xdf, 0x21, 0x7d, 0xe8, 0x0c, 0xcb, 0x96, 0xda, 0xfe,
	0xb1, 0x6b, 0x82, 0x0a, 0x61, 0x32, 0xa6, 0x6f, 0xc6, 0xb4, 0x4c, 0xb0, 0x96, 0x1a, 0xf1, 0xe1,
	0x9e, 0xb1, 0xbc, 0x1c, 0x9f, 0x8e, 0xe9, 0xf1, 0xca, 0x53, 0xdf, 0xff, 0x1c, 0xb6, 0xed, 0xbb,
	0x44, 0xda, 0xd0, 0x7c, 0x7d, 0x3a, 0xa4, 0x6f, 0x71, 0x87, 0x2e, 0x5e, 0xea, 0x9c, 0x8e, 0x87,
	0xaf, 0x8e, 0x4f, 0x5f, 0xf6, 0x6b, 0x17, 0x5b, 0xfa, 0x97, 0xf8, 0xdb, 0xbf, 0x03, 0x00, 0x00,
	0xff, 0xff, 0x75, 0x59, 0xf4, 0x03, 0x4e, 0x0b, 0x00, 0x00,
}


package NiHTest;

use strict;
use warnings;

use Cwd;
use File::Copy;
use File::Path qw(mkpath remove_tree);
use Getopt::Long qw(:config posix_default bundling no_ignore_case);
use IPC::Open3;
#use IPC::Cmd qw(run);
use Storable qw(dclone);
use Symbol 'gensym';
use UNIVERSAL;

#use Data::Dumper qw(Dumper);

#  NiHTest -- package to run regression tests
#  Copyright (C) 2002-2016 Dieter Baron and Thomas Klausner
#
#  This file is part of ckmame, a program to check rom sets for MAME.
#  The authors can be contacted at <ckmame@nih.at>
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions
#  are met:
#  1. Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#  2. Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in
#     the documentation and/or other materials provided with the
#     distribution.
#  3. The names of the authors may not be used to endorse or promote
#     products derived from this software without specific prior
#     written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
#  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
#  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
#  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
#  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
#  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
#  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
#  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# runtest TESTNAME
#
# files:
#   TESTNAME.test: test scenario
#
# test scenario:
#    Lines beginning with # are comments.
#
#    The following commands are recognized; return and args must
#    appear exactly once, the others are optional.
#
#	args ARGS
#	    run program with command line arguments ARGS
#
#	description TEXT
#	    description of what test is for
#
#	features FEATURE ...
#	    only run test if all FEATUREs are present, otherwise skip it.
#
#	file TEST IN OUT
#	    copy file IN as TEST, compare against OUT after program run.
#
#	file-del TEST IN
#	    copy file IN as TEST, check that it is removed by program.
#
#	file-new TEST OUT
#	    check that file TEST is created by program and compare
#	    against OUT.
#
#	mkdir MODE NAME
#	    create directory NAME with permissions MODE.
#
#   precheck COMMAND ARGS ...
#		if COMMAND exits with non-zero status, skip test.
#
#	preload LIBRARY
#	    pre-load LIBRARY before running program.
#
#	program PRG
#	    run PRG instead of ckmame.
#
#	return RET
#	    RET is the expected exit code
#
#	setenv VAR VALUE
#	    set environment variable VAR to VALUE.
#
#	stderr TEXT
#	    program is expected to produce the error message TEXT.  If
#	    multiple stderr commands are used, the messages are
#	    expected in the order given.
#
#       stderr-replace REGEX REPLACEMENT
#           run regex replacement over expected and got stderr output.
#
#	stdin TEST
#	    Provide TEXT to program's stdin.
#
#	stdin-file FILE
#	    pipe FILE to program's stdin.
#
#	stdout TEXT
#	    program is expected to print TEXT to stdout.  If multiple
#	    stdout commands are used, the messages are expected in
#	    the order given.
#
#	touch MTIME FILE
#	    set last modified timestamp of FILE to MTIME (seconds since epoch).
#	    If FILE doesn't exist, an empty file is created.
#
#	ulimit C VALUE
#	    set ulimit -C to VALUE while running the program.
#
# exit status
#	runtest uses the following exit codes:
#	    0: test passed
#	    1: test failed
#	    2: other error
#	   77: test was skipped
#
# environment variables:
#   RUN_GDB: if set, run gdb on program in test environment
#   KEEP_BROKEN: if set, don't delete test environment if test failed
#   NO_CLEANUP: if set, don't delete test environment
#   SETUP_ONLY: if set, exit after creating test environment
#   VERBOSE: if set, be more verbose (e. g., output diffs)

my %EXIT_CODES = (
	PASS => 0,
	FAIL => 1,
	SKIP => 77,
	ERROR => 99
    );

# MARK: - Public API

sub new {
	my $class = UNIVERSAL::isa ($_[0], __PACKAGE__) ? shift : __PACKAGE__;
	my $self = bless {}, $class;

	my ($opts) = @_;

	$self->{default_program} = $opts->{default_program};
	$self->{zipcmp} = $opts->{zipcmp} // 'zipcmp';
	$self->{zipcmp_flags} = $opts->{zipcmp_flags} // '-p';

	$self->{directives} = {
		args => { type => 'string...', once => 1, required => 1 },
		description => { type => 'string', once => 1 },
		features => { type => 'string...', once => 1 },
		file => { type => 'string string string' },
		'file-del' => { type => 'string string' },
		'file-new' => { type => 'string string' },
		mkdir => { type => 'string string' },
		precheck => { type => 'string...' },
		preload => { type => 'string', once => 1 },
		program => { type => 'string', once => 1 },
		'return' => { type => 'int', once => 1, required => 1 },
		setenv => { type => 'string string' },
		stderr => { type => 'string' },
		'stderr-replace' => { type => 'string string' },
		stdin => { type => 'string' },
		'stdin-file' => { type => 'string', once => 1 },
		stdout => { type => 'string' },
		touch => { type => 'int string' },
		ulimit => { type => 'char string' }
	};

	$self->{compare_by_type} = {};
	$self->{copy_by_type} = {};
	$self->{hooks} = {};

	$self->get_variable('srcdir', $opts);
	$self->get_variable('top_builddir', $opts);

	$self->{in_sandbox} = 0;

	$self->{verbose} = $ENV{VERBOSE};
	$self->{keep_broken} = $ENV{KEEP_BROKEN};
	$self->{no_cleanup} = $ENV{NO_CLEANUP};
	$self->{setup_only} = $ENV{SETUP_ONLY};

	return $self;
}

sub add_comparator {
	my ($self, $ext, $sub) = @_;

	return $self->add_file_proc('compare_by_type', $ext, $sub);
}

sub add_copier {
	my ($self, $ext, $sub) = @_;

	return $self->add_file_proc('copy_by_type', $ext, $sub);
}

sub add_directive {
	my ($self, $name, $def) = @_;

	if (exists($self->{directives}->{$name})) {
		$self->die("directive $name already defined");
	}

	# TODO: validate $def

	$self->{directives}->{$name} = $def;

	return 1;
}

sub add_file_proc {
	my ($self, $proc, $ext, $sub) = @_;

	$self->{$proc}->{$ext} = [] unless (defined($self->{$proc}->{$ext}));
	unshift @{$self->{$proc}->{$ext}}, $sub;

	return 1;
}

sub add_hook {
	my ($self, $hook, $sub) = @_;

	$self->{hooks}->{$hook} = [] unless (defined($self->{hooks}->{$hook}));
	push @{$self->{hooks}->{$hook}}, $sub;

	return 1;
}

sub add_variant {
	my ($self, $name, $hooks) = @_;

	if (!defined($self->{variants})) {
		$self->{variants} = [];
		$self->add_directive('variants' => { type => 'string...', once => 1 });
	}
	for my $variant (@{$self->{variants}}) {
		if ($variant->{name} eq $name) {
			$self->die("variant $name already defined");
		}
	}

	push @{$self->{variants}}, { name => $name, hooks => $hooks };

	return 1;
}

sub end {
	my ($self, @results) = @_;

	my $result = 'PASS';

	for my $r (@results) {
		if ($r eq 'ERROR' || ($r eq 'FAIL' && $result ne 'ERROR')) {
			$result = $r;
		}
	}

	$self->end_test($result);
}

sub run {
	my ($self, @argv) = @_;

	$self->setup(@argv);

	$self->end($self->runtest());
}

sub runtest {
	my ($self) = @_;

	if (defined($self->{variants})) {
		my @results = ();
		$self->{original_test} = $self->{test};

		my %variants;

		if (defined($self->{test}->{variants})) {
			%variants = map { $_ => 1; } @{$self->{test}->{variants}};
		}

		for my $variant (@{$self->{variants}}) {
			next if (defined($self->{test}->{variants}) && !exists($variants{$variant->{name}}));

			$self->{variant_hooks} = $variant->{hooks};
			$self->{test} = dclone($self->{original_test});
			$self->{variant} = $variant->{name};
			$self->mangle_test_for_variant();
			push @results, $self->runtest_one($variant->{name});
		}

		return @results;
	}
	else {
		return $self->runtest_one();
	}
}

sub runtest_one {
	my ($self, $tag) = @_;

	$ENV{TZ} = "UTC";
	$ENV{LC_CTYPE} = "C";
	$ENV{POSIXLY_CORRECT} = 1;
	$self->sandbox_create($tag);
	$self->sandbox_enter();

	my $ok = 1;
	$ok &= $self->copy_files();
	$ok &= $self->run_hook('post_copy_files');
	$ok &= $self->touch_files();
	$ok &= $self->run_hook('prepare_sandbox');
	return 'ERROR' unless ($ok);

	if ($self->{setup_only}) {
		$self->sandbox_leave();
		return 'SKIP';
	}

	for my $env (@{$self->{test}->{'setenv'}}) {
		$ENV{$env->[0]} = $env->[1];
	}
        my $preload_env_var = 'LD_PRELOAD';
        if ($^O eq 'darwin') {
                $preload_env_var = 'DYLD_INSERT_LIBRARIES';
        }
	if (defined($self->{test}->{'preload'})) {
		if (-f cwd() . "/../.libs/$self->{test}->{'preload'}") {
			$ENV{$preload_env_var} = cwd() . "/../.libs/$self->{test}->{'preload'}";
		} else {
			$ENV{$preload_env_var} = cwd() . "/../lib$self->{test}->{'preload'}";
		}
	}

	$self->run_program();

	for my $env (@{$self->{test}->{'setenv'}}) {
		delete ${ENV{$env->[0]}};
	}
	if (defined($self->{test}->{'preload'})) {
		delete ${ENV{$preload_env_var}};
	}

	if ($self->{test}->{stdin}) {
		$self->{stdin} = [ @{$self->{test}->{stdin}} ];
	}

	if ($self->{test}->{stdout}) {
		$self->{expected_stdout} = [ @{$self->{test}->{stdout}} ];
	}
	else {
		$self->{expected_stdout} = [];
	}
	if ($self->{test}->{stderr}) {
		$self->{expected_stderr} = [ @{$self->{test}->{stderr}} ];
	}
	else {
		$self->{expected_stderr} = [];
	}

	$self->run_hook('post_run_program');

	my @failed = ();

	if ($self->{exit_status} != ($self->{test}->{return} // 0)) {
		push @failed, 'exit status';
		if ($self->{verbose}) {
			print "Unexpected exit status:\n";
			print "-" . ($self->{test}->{return} // 0) . "\n+$self->{exit_status}\n";
		}
	}

	if (!$self->compare_arrays($self->{expected_stdout}, $self->{stdout}, 'output')) {
		push @failed, 'output';
	}
	if (!$self->compare_arrays($self->{expected_stderr}, $self->{stderr}, 'error output')) {
		push @failed, 'error output';
	}
	if (!$self->compare_files()) {
		push @failed, 'files';
	}

	$self->{failed} = \@failed;

	$self->run_hook('checks');

	my $result = scalar(@{$self->{failed}}) == 0 ? 'PASS' : 'FAIL';

	$self->sandbox_leave();
	if (!($self->{no_cleanup} || ($self->{keep_broken} && $result eq 'FAIL'))) {
		$self->sandbox_remove();
	}

	$self->print_test_result($tag, $result, join ', ', @{$self->{failed}});

	return $result;
}

sub setup {
	my ($self, @argv) = @_;

	my @save_argv = @ARGV;
	@ARGV = @argv;
	my $ok = GetOptions(
		'help|h' => \my $help,
	        'bin-sub-directory=s' => \$self->{bin_sub_directory},
		'keep-broken|k' => \$self->{keep_broken},
		'no-cleanup' => \$self->{no_cleanup},
		# 'run-gdb' => \$self->{run_gdb},
		'setup-only' => \$self->{setup_only},
		'verbose|v' => \$self->{verbose}
	);
	@argv = @ARGV;
	@ARGV = @save_argv;

	if (!$ok || scalar(@argv) != 1 || $help) {
		print STDERR "Usage: $0 [-hv] [--bin-sub-directory DIR] [--keep-broken] [--no-cleanup] [--setup-only] testcase\n";
		exit(1);
	}

	my $testcase = shift @argv;

	$testcase .= '.test' unless ($testcase =~ m/\.test$/);

	my $testcase_file = $self->find_file($testcase);

	$self->die("cannot find test case $testcase") unless ($testcase_file);

	$testcase =~ s,^(?:.*/)?([^/]*)\.test$,$1,;
	$self->{testname} = $testcase;

	$self->die("error in test case definition") unless $self->parse_case($testcase_file);

	$self->check_features_requirement() if ($self->{test}->{features});
	$self->run_precheck() if ($self->{test}->{precheck});

	$self->end_test('SKIP') if ($self->{test}->{preload} && ($^O eq 'darwin' || $^O eq 'MSWin32'));
	$self->end_test('SKIP') if (($self->{test}->{stdin} || $self->{test}->{'stdin-file'}) && $^O eq 'MSWin32');
}

# MARK: - Internal Methods

sub add_file {
	my ($self, $file) = @_;

	if (defined($self->{files}->{$file->{destination}})) {
		$self->warn("duplicate specification for input file $file->{destination}");
		return undef;
	}

	$self->{files}->{$file->{destination}} = $file;

	return 1;
}

sub check_features_requirement() {
	my ($self) = @_;

	my %features;

	my $fh;
	unless (open($fh, '<', "$self->{top_builddir}/config.h")) {
		$self->die("cannot open config.h in top builddir $self->{top_builddir}");
	}
	while (my $line = <$fh>) {
		if ($line =~ m/^#define HAVE_([A-Z0-9_a-z]*)/) {
			$features{$1} = 1;
		}
	}
	close($fh);

	my @missing = ();
	for my $feature (@{$self->{test}->{features}}) {
		if (!$features{$feature}) {
			push @missing, $feature;
		}
	}

	if (scalar @missing > 0) {
		my $reason = "missing features";
		if (scalar(@missing) == 1) {
			$reason = "missing feature";
		}
		$self->print_test_result('SKIP', "$reason: " . (join ' ', @missing));
		$self->end_test('SKIP');
	}

	return 1;
}

sub comparator_zip {
	my ($self, $got, $expected) = @_;

	my $zipcmp = (-f $self->{zipcmp}) ? $self->{zipcmp} : $self->find_program('zipcmp');
	my @args = ($zipcmp, $self->{verbose} ? '-v' : '-q');
	push @args, $self->{zipcmp_flags} if ($self->{zipcmp_flags});
	push @args, ($expected, $got);

	my $ret = system(@args);

	return $ret == 0;
}

sub compare_arrays() {
	my ($self, $a, $b, $tag) = @_;

	my $ok = 1;

	if (scalar(@$a) != scalar(@$b)) {
		$ok = 0;
	}
	else {
		for (my $i = 0; $i < scalar(@$a); $i++) {
			if ($a->[$i] ne $b->[$i]) {
				$ok = 0;
				last;
			}
		}
	}

	if (!$ok && $self->{verbose}) {
		print "Unexpected $tag:\n";
		print "--- expected\n+++ got\n";

		diff_arrays($a, $b);
	}

	return $ok;
}

sub file_cmp($$) {
	my ($a, $b) = @_;
	my $result = 0;
	open my $fha, "< $a";
	open my $fhb, "< $b";
	binmode $fha;
	binmode $fhb;
	BYTE: while (!eof $fha && !eof $fhb) {
		if (getc $fha ne getc $fhb) {
			$result = 1;
			last BYTE;
		}
	}
	$result = 1 if eof $fha != eof $fhb;
	close $fha;
	close $fhb;
	return $result;
}

sub compare_file($$$) {
	my ($self, $got, $expected) = @_;

	my $real_expected = $self->find_file($expected);
	unless ($real_expected) {
		$self->warn("cannot find expected result file $expected");
		return 0;
	}

	my $ok = $self->run_comparator($got, $real_expected);

	if (!defined($ok)) {
		my $ret;
		if ($self->{verbose}) {
			$ret = system('diff', '-u', $real_expected, $got);
		}
		else {
			$ret = file_cmp($real_expected, $got);
		}
		$ok = ($ret == 0);
	}

	return $ok;
}

sub list_files {
	my ($root) = @_;
        my $ls;

	my @files = ();
	my @dirs = ($root);

	while (scalar(@dirs) > 0) {
		my $dir = shift @dirs;

		opendir($ls, $dir);
		unless ($ls) {
			# TODO: handle error
		}
		while (my $entry = readdir($ls)) {
			my $file = "$dir/$entry";
			if ($dir eq '.') {
				$file = $entry;
			}

			if (-f $file) {
				push @files, "$file";
			}
			if (-d $file && $entry ne '.' && $entry ne '..') {
				push @dirs, "$file";
			}
		}
		closedir($ls);
	}

	return @files;
}

sub compare_files() {
	my ($self) = @_;

	my $ok = 1;

	my @files_got = sort(list_files("."));
	my @files_should = ();

        for my $file (sort keys %{$self->{files}}) {
		push @files_should, $file if ($self->{files}->{$file}->{result} || $self->{files}->{$file}->{ignore});
	}

	$self->{files_got} = \@files_got;
	$self->{files_should} = \@files_should;

	unless ($self->run_hook('post_list_files')) {
		return 0;
	}

	$ok = $self->compare_arrays($self->{files_should}, $self->{files_got}, 'files');

	for my $file (@{$self->{files_got}}) {
		my $file_def = $self->{files}->{$file};
		next unless ($file_def && $file_def->{result});

		$ok &= $self->compare_file($file, $file_def->{result});
	}

	return $ok;
}

sub copy_files {
	my ($self) = @_;

	my $ok = 1;

	for my $filename (sort keys %{$self->{files}}) {
		my $file = $self->{files}->{$filename};
		next unless ($file->{source});

		my $src = $self->find_file($file->{source});
		unless ($src) {
			$self->warn("cannot find input file $file->{source}");
			$ok = 0;
			next;
		}

		if ($file->{destination} =~ m,/,) {
			my $dir = $file->{destination};
			$dir =~ s,/[^/]*$,,;
			if (! -d $dir) {
				mkpath($dir);
			}
		}

		my $this_ok = $self->run_copier($src, $file->{destination});
		if (defined($this_ok)) {
			$ok &= $this_ok;
		}
		else {
			unless (copy($src, $file->{destination})) {
				$self->warn("cannot copy $src to $file->{destination}: $!");
				$ok = 0;
			}
		}
	}

	if (defined($self->{test}->{mkdir})) {
		for my $dir_spec (@{$self->{test}->{mkdir}}) {
			my ($mode, $dir) = @$dir_spec;
			if (! -d $dir) {
				unless (mkdir($dir, oct($mode))) {
					$self->warn("cannot create directory $dir: $!");
					$ok = 0;
				}
			}
		}
	}

	$self->die("failed to copy input files") unless ($ok);
}

sub die() {
	my ($self, $msg) = @_;

	print STDERR "$0: $msg\n" if ($msg);

	$self->end_test('ERROR');
}

sub end_test {
	my ($self, $status) = @_;

	my $exit_code = $EXIT_CODES{$status} // $EXIT_CODES{ERROR};

	$self->exit($exit_code);
}

sub exit() {
	my ($self, $status) = @_;
	### TODO: cleanup

	exit($status);
}

sub find_file() {
	my ($self, $fname) = @_;

	for my $dir (('', "$self->{srcdir}/")) {
		my $f = "$dir$fname";
		$f = "../$f" if ($self->{in_sandbox} && $dir !~ m,^(\w:)?/,);

		return $f if (-f $f);
	}

	return undef;
}

sub get_extension {
	my ($self, $fname) = @_;

	my $ext = $fname;
	if ($ext =~ m/\./) {
		$ext =~ s/.*\.//;
	}
	else {
		$ext = '';
	}

	return $ext;
}

sub get_variable {
	my ($self, $name, $opts) = @_;

	$self->{$name} = $opts->{$name} // $ENV{$name};
	if (!defined($self->{$name}) || $self->{$name} eq '') {
		my $fh;
		unless (open($fh, '<', 'Makefile')) {
			$self->die("cannot open Makefile: $!");
		}
		while (my $line = <$fh>) {
			chomp $line;
			if ($line =~ m/^$name = (.*)/) {
				$self->{$name} = $1;
				last;
			}
		}
		close ($fh);
	}
	if (!defined($self->{$name}) || $self->{$name} eq '') {
		$self->die("cannot get variable $name");
	}
}

sub mangle_test_for_variant {
	my ($self) = @_;

	$self->{test}->{stdout} = $self->strip_tags($self->{variant}, $self->{test}->{stdout});
	$self->{test}->{stderr} = $self->strip_tags($self->{variant}, $self->{test}->{stderr});
	$self->run_hook('mangle_test');

	return 1;
}

sub parse_args {
	my ($self, $type, $str) = @_;

	if ($type eq 'string...') {
		my $args = [];

		while ($str ne '') {
			if ($str =~ m/^\"/) {
				unless ($str =~ m/^\"([^\"]*)\"\s*(.*)/) {
					$self->warn_file_line("unclosed quote in [$str]");
					return undef;
				}
				push @$args, $1;
				$str = $2;
			}
			else {
				$str =~ m/^(\S+)\s*(.*)/;
				push @$args, $1;
				$str = $2;
			}
		}

		return $args;
	}
	elsif ($type =~ m/(\s|\.\.\.$)/) {
		my $ellipsis = 0;
		if ($type =~ m/(.*)\.\.\.$/) {
			$ellipsis = 1;
			$type = $1;
		}
		my @types = split /\s+/, $type;
		my @strs = split /\s+/, $str;
		my $optional = 0;
		for (my $i = scalar(@types) - 1; $i >= 0; $i--) {
			last unless ($types[$i] =~ m/(.*)\?$/);
			$types[$i] = $1;
			$optional++;
		}

		if ($ellipsis && $optional > 0) {
			# TODO: check this when registering a directive
			$self->warn_file_line("can't use ellipsis together with optional arguments");
			return undef;
		}
		if (!$ellipsis && (scalar(@strs) < scalar(@types) - $optional || scalar(@strs) > scalar(@types))) {
			my $expected = scalar(@types);
			if ($optional > 0) {
				$expected = ($expected - $optional) . "-$expected";
			}
			$self->warn_file_line("expected $expected arguments, got " . (scalar(@strs)));
			return undef;
		}

		my $args = [];

		my $n = scalar(@types);
		for (my $i=0; $i<scalar(@strs); $i++) {
			my $val = $self->parse_args(($i >= $n ? $types[$n-1] : $types[$i]), $strs[$i]);
			return undef unless (defined($val));
			push @$args, $val;
		}

		return $args;
	}
	else {
		if ($type eq 'string') {
			return $str;
		}
		elsif ($type eq 'int') {
			if ($str !~ m/^\d+$/) {
				$self->warn_file_line("illegal int [$str]");
				return undef;
			}
			return $str+0;
		}
		elsif ($type eq 'char') {
			if ($str !~ m/^.$/) {
				$self->warn_file_line("illegal char [$str]");
				return undef;
			}
			return $str;
		}
		else {
			$self->warn_file_line("unknown type $type");
			return undef;
		}
	}
}

sub parse_case() {
	my ($self, $fname) = @_;

	my $ok = 1;

	open TST, "< $fname" or $self->die("cannot open test case $fname: $!");

	$self->{testcase_fname} = $fname;

	my %test = ();

	while (my $line = <TST>) {
		$line =~ s/(\n|\r)//g;

		next if ($line =~ m/^\#/);

		unless ($line =~ m/(\S*)(?:\s(.*))?/) {
			$self->warn_file_line("cannot parse line $line");
			$ok = 0;
			next;
		}
		my ($cmd, $argstring) = ($1, $2//"");

		my $def = $self->{directives}->{$cmd};

		unless ($def) {
			$self->warn_file_line("unknown directive $cmd in test file");
			$ok = 0;
			next;
		}

		my $args = $self->parse_args($def->{type}, $argstring);

		unless (defined($args)) {
			$ok = 0;
			next;
		}

		if ($def->{once}) {
			if (defined($test{$cmd})) {
				$self->warn_file_line("directive $cmd appeared twice in test file");
			}
			$test{$cmd} = $args;
		}
		else {
			$test{$cmd} = [] unless (defined($test{$cmd}));
			push @{$test{$cmd}}, $args;
		}
	}

	close TST;

	return undef unless ($ok);

	for my $cmd (sort keys %test) {
		if ($self->{directives}->{$cmd}->{required} && !defined($test{$cmd})) {
			$self->warn_file("required directive $cmd missing in test file");
			$ok = 0;
		}
	}

	if ($test{'stdin-file'} && $test{stdin}) {
		$self->warn_file("both stdin-file and stdin provided, choose one");
		$ok = 0;
	}

	if (defined($self->{variants})) {
		if (defined($test{variants})) {
			for my $name (@{$test{variants}}) {
				my $found = 0;
				for my $variant (@{$self->{variants}}) {
					if ($name eq $variant->{name}) {
						$found = 1;
						last;
					}
				}
				if ($found == 0) {
					$self->warn_file("unknown variant $name");
					$ok = 0;
				}
			}
		}
	}

	return undef unless ($ok);

	if (defined($test{'stderr-replace'}) && defined($test{stderr})) {
		$test{stderr} = [ map { $self->stderr_rewrite($test{'stderr-replace'}, $_); } @{$test{stderr}} ];
	}

	if (!defined($test{program})) {
		$test{program} = $self->{default_program};
	}

	$self->{test} = \%test;

	$self->run_hook('mangle_program');

	if (!$self->parse_postprocess_files()) {
		return 0;
	}

	return $self->run_hook('post_parse');
}

sub parse_postprocess_files {
	my ($self) = @_;

	$self->{files} = {};

	my $ok = 1;

	for my $file (@{$self->{test}->{file}}) {
		$ok = 0 unless ($self->add_file({ source => $file->[1], destination => $file->[0], result => $file->[2] }));
	}

	for my $file (@{$self->{test}->{'file-del'}}) {
		$ok = 0 unless ($self->add_file({ source => $file->[1], destination => $file->[0], result => undef }));
	}

	for my $file (@{$self->{test}->{'file-new'}}) {
		$ok = 0 unless ($self->add_file({ source => undef, destination => $file->[0], result => $file->[1] }));
	}

	return $ok;
}

sub print_test_result {
	my ($self, $tag, $result, $reason) = @_;

	if ($self->{verbose}) {
		print "$self->{testname}";
		print " ($tag)" if ($tag);
		print " -- $result";
		print ": $reason" if ($reason);
		print "\n";
	}
}

sub run_comparator {
	my ($self, $got, $expected) = @_;

	return $self->run_file_proc('compare_by_type', $got, $expected);
}

sub run_copier {
	my ($self, $src, $dest) = @_;

	return $self->run_file_proc('copy_by_type', $src, $dest);
}

sub run_file_proc {
	my ($self, $proc, $got, $expected) = @_;

	my $ext = ($self->get_extension($got)) . '/' . ($self->get_extension($expected));

	if ($self->{variant}) {
		if (defined($self->{$proc}->{"$self->{variant}/$ext"})) {
			for my $sub (@{$self->{$proc}->{"$self->{variant}/$ext"}}) {
				my $ret = $sub->($self, $got, $expected);
				return $ret if (defined($ret));
			}
		}
	}
	if (defined($self->{$proc}->{$ext})) {
		for my $sub (@{$self->{$proc}->{$ext}}) {
			my $ret = $sub->($self, $got, $expected);
			return $ret if (defined($ret));
		}
	}

	return undef;
}

sub run_hook {
	my ($self, $hook) = @_;

	my $ok = 1;

	my @hooks = ();

	if (defined($self->{variant_hooks}) && defined($self->{variant_hooks}->{$hook})) {
		push @hooks, $self->{variant_hooks}->{$hook};
	}
	if (defined($self->{hooks}->{$hook})) {
		push @hooks, @{$self->{hooks}->{$hook}};
	}

	for my $sub (@hooks) {
		unless ($sub->($self, $hook, $self->{variant})) {
			$self->warn("hook $hook failed");
			$ok = 0;
		}
	}

	return $ok;
}

sub args_decode {
	my ($str, $srcdir) = @_;

	if ($str =~ m/\\/) {
		$str =~ s/\\a/\a/gi;
		$str =~ s/\\b/\b/gi;
		$str =~ s/\\f/\f/gi;
		$str =~ s/\\n/\n/gi;
		$str =~ s/\\r/\r/gi;
		$str =~ s/\\t/\t/gi;
		$str =~ s/\\v/\cK/gi;
		$str =~ s/\\s/ /gi;
		# TODO: \xhh, \ooo
		$str =~ s/\\(.)/$1/g;
	}

	if ($srcdir !~ m,^/,) {
		$srcdir = "../$srcdir";
	}

	if ($str =~ m/^\$srcdir(.*)/) {
		$str = "$srcdir$1";
	}

	return $str;
}

sub run_precheck {
	my ($self) = @_;

	for my $precheck (@{$self->{test}->{precheck}}) {
		unless (system(@{$precheck}) == 0) {
			$self->print_test_result('SKIP', "precheck failed");
			$self->end_test('SKIP');
		}
	}

	return 1;
}

sub find_program() {
        my ($self, $pname) = @_;

	my @directories = (".");
	if ($self->{bin_sub_directory}) {
	        push @directories, $self->{bin_sub_directory};
	}

	for my $up (('.', '..', '../..', '../../..')) {
		for my $sub (('.', 'src')) {
		        for my $dir (@directories) {
				for my $ext (('', '.exe')) {
					my $f = "$up/$sub/$dir/$pname$ext";
					return $f if (-f $f);
				}
			}
		}
	}

	return undef;
}

sub run_program {
	my ($self) = @_;
	my ($stdin, $stdout, $stderr);
	$stderr = gensym;

	my @cmd = ($self->find_program($self->{test}->{program}), map ({ args_decode($_, $self->{srcdir}); } @{$self->{test}->{args}}));

	### TODO: catch errors?

	my $pid;
        if ($self->{test}->{'stdin-file'}) {
                open(SPLAT, '<', $self->{test}->{'stdin-file'});
	        my $is_marked = eof SPLAT; # mark used
		$pid = open3("<&SPLAT", $stdout, $stderr, @cmd);
	}
	else {
		$pid = open3($stdin, $stdout, $stderr, @cmd);
	}
	$self->{stdout} = [];
	$self->{stderr} = [];

	if ($self->{test}->{stdin}) {
		foreach my $line (@{$self->{test}->{stdin}}) {
                        print $stdin $line . "\n";
                }
                close($stdin);
        }

	while (my $line = <$stdout>) {
		$line =~ s/(\n|\r)//g;
		push @{$self->{stdout}}, $line;
	}
	my $prg = $self->{test}->{program};
	$prg =~ s,.*/,,;
	while (my $line = <$stderr>) {
		$line =~ s/(\n|\r)//g;
		$line =~ s/^[^: ]*$prg(\.exe)?: //;
		if (defined($self->{test}->{'stderr-replace'})) {
			$line = $self->stderr_rewrite($self->{test}->{'stderr-replace'}, $line);
		}
		push @{$self->{stderr}}, $line;
	}

	waitpid($pid, 0);

	$self->{exit_status} = $? >> 8;
}

sub sandbox_create {
	my ($self, $tag) = @_;

	$tag = ($tag ? "-$tag" : "");
	$self->{sandbox_dir} = "sandbox-$self->{testname}$tag.d$$";

	$self->die("sandbox $self->{sandbox_dir} already exists") if (-e $self->{sandbox_dir});

	mkdir($self->{sandbox_dir}) or $self->die("cannot create sandbox $self->{sandbox_dir}: $!");

	return 1;
}

sub sandbox_enter {
	my ($self) = @_;

	$self->die("internal error: cannot enter sandbox before creating it") unless (defined($self->{sandbox_dir}));

	return if ($self->{in_sandbox});

	chdir($self->{sandbox_dir}) or $self->die("cannot cd into sandbox $self->{sandbox_dir}: $!");

	$self->{in_sandbox} = 1;
}

sub sandbox_leave {
	my ($self) = @_;

	return if (!$self->{in_sandbox});

	chdir('..') or $self->die("cannot leave sandbox: $!");

	$self->{in_sandbox} = 0;
}

sub sandbox_remove {
	my ($self) = @_;

	remove_tree($self->{sandbox_dir});

	return 1;
}

sub strip_tags {
	my ($self, $tag, $lines) = @_;

	my @stripped = ();

	for my $line (@$lines) {
		if ($line =~ m/^<([a-zA-Z0-9_]*)> (.*)/) {
			if ($1 eq $tag) {
				push @stripped, $2;
			}
		}
		else {
			push @stripped, $line;
		}
	}

	return \@stripped;
}

sub touch_files {
	my ($self) = @_;

	my $ok = 1;

	if (defined($self->{test}->{touch})) {
		for my $args (@{$self->{test}->{touch}}) {
			my ($mtime, $fname) = @$args;

			if (!-f $fname) {
				my $fh;
				unless (open($fh, "> $fname") and close($fh)) {
					# TODO: error message
					$ok = 0;
					next;
				}
			}
			unless (utime($mtime, $mtime, $fname) == 1) {
				# TODO: error message
				$ok = 0;
			}
		}
	}

	return $ok;
}

sub warn {
	my ($self, $msg) = @_;

	print STDERR "$0: $msg\n";
}

sub warn_file {
	my ($self, $msg) = @_;

	$self->warn("$self->{testcase_fname}: $msg");
}

sub warn_file_line {
	my ($self, $msg) = @_;

	$self->warn("$self->{testcase_fname}:$.: $msg");
}

sub stderr_rewrite {
	my ($self, $pattern, $line) = @_;
	for my $repl (@{$pattern}) {
		$line =~ s/$repl->[0]/$repl->[1]/;
	}
	return $line;
}

# MARK: array diff

sub diff_arrays {
	my ($a, $b) = @_;

	my ($i, $j);
	for ($i = $j = 0; $i < scalar(@$a) || $j < scalar(@$b);) {
		if ($i >= scalar(@$a)) {
			print "+$b->[$j]\n";
			$j++;
		}
		elsif ($j >= scalar(@$b)) {
			print "-$a->[$i]\n";
			$i++;
		}
		elsif ($a->[$i] eq $b->[$j]) {
			print " $a->[$i]\n";
			$i++;
			$j++;
		}
		else {
			my ($off_a, $off_b) = find_best_offsets($a, $i, $b, $j);
			my ($off_b_2, $off_a_2) = find_best_offsets($b, $j, $a, $i);

			if ($off_a + $off_b > $off_a_2 + $off_b_2) {
				$off_a = $off_a_2;
				$off_b = $off_b_2;
			}

			for (my $off = 0; $off < $off_a; $off++) {
				print "-$a->[$i]\n";
				$i++;
			}
			for (my $off = 0; $off < $off_b; $off++) {
				print "+$b->[$j]\n";
				$j++;
			}
		}
	}

}

sub find_best_offsets {
	my ($a, $i, $b, $j) = @_;

	my ($best_a, $best_b);

	for (my $off_a = 0; $off_a < (defined($best_a) ? $best_a + $best_b : scalar(@$a) - $i); $off_a++) {
		my $off_b = find_entry($a->[$i+$off_a], $b, $j, defined($best_a) ? $best_a + $best_b - $off_a : scalar(@$b) - $j);

		next unless (defined($off_b));

		if (!defined($best_a) || $best_a + $best_b > $off_a + $off_b) {
			$best_a = $off_a;
			$best_b = $off_b;
		}
	}

	if (!defined($best_a)) {
		return (scalar(@$a) - $i, scalar(@$b) - $j);
	}

	return ($best_a, $best_b);
}

sub find_entry {
	my ($entry, $array, $start, $max_offset) = @_;

	for (my $offset = 0; $offset < $max_offset; $offset++) {
		return $offset if ($array->[$start + $offset] eq $entry);
	}

	return undef;
}

1;


// TypeScript Version: 3.0

/// <reference types="node" />

import * as fs from "fs";
import { EventEmitter } from "events";

/**
 * The object's keys are all the directories (using absolute paths unless the `cwd` option was
 * used), and the values are arrays of the names of the items contained in each directory.
 */
export interface WatchedPaths {
  [directory: string]: string[];
}

export class FSWatcher extends EventEmitter implements fs.FSWatcher {
  /**
   * Constructs a new FSWatcher instance with optional WatchOptions parameter.
   */
  constructor(options?: WatchOptions);

  /**
   * Add files, directories, or glob patterns for tracking. Takes an array of strings or just one
   * string.
   */
  add(paths: string | string[]): void;

  /**
   * Stop watching files, directories, or glob patterns. Takes an array of strings or just one
   * string.
   */
  unwatch(paths: string | string[]): void;

  /**
   * Returns an object representing all the paths on the file system being watched by this
   * `FSWatcher` instance. The object's keys are all the directories (using absolute paths unless
   * the `cwd` option was used), and the values are arrays of the names of the items contained in
   * each directory.
   */
  getWatched(): WatchedPaths;

  /**
   * Removes all listeners from watched files.
   */
  close(): void;

  on(event: 'add'|'addDir'|'change', listener: (path: string, stats?: fs.Stats) => void): this;

  on(event: 'all', listener: (eventName: 'add'|'addDir'|'change'|'unlink'|'unlinkDir', path: string, stats?: fs.Stats) => void): this;

  /**
   * Error occured
   */
  on(event: 'error', listener: (error: Error) => void): this;

  /**
   * Exposes the native Node `fs.FSWatcher events`
   */
  on(event: 'raw', listener: (eventName: string, path: string, details: any) => void): this;

  /**
   * Fires when the initial scan is complete
   */
  on(event: 'ready', listener: () => void): this;

  on(event: 'unlink'|'unlinkDir', listener: (path: string) => void): this;

  on(event: string, listener: (...args: any[]) => void): this;
}

export interface WatchOptions {
  /**
   * Indicates whether the process should continue to run as long as files are being watched. If
   * set to `false` when using `fsevents` to watch, no more events will be emitted after `ready`,
   * even if the process continues to run.
   */
  persistent?: boolean;

  /**
   * ([anymatch](https://github.com/es128/anymatch)-compatible definition) Defines files/paths to
   * be ignored. The whole relative or absolute path is tested, not just filename. If a function
   * with two arguments is provided, it gets called twice per path - once with a single argument
   * (the path), second time with two arguments (the path and the
   * [`fs.Stats`](http://nodejs.org/api/fs.html#fs_class_fs_stats) object of that path).
   */
  ignored?: any;

  /**
   * If set to `false` then `add`/`addDir` events are also emitted for matching paths while
   * instantiating the watching as chokidar discovers these file paths (before the `ready` event).
   */
  ignoreInitial?: boolean;

  /**
   * When `false`, only the symlinks themselves will be watched for changes instead of following
   * the link references and bubbling events through the link's path.
   */
  followSymlinks?: boolean;

  /**
   * The base directory from which watch `paths` are to be derived. Paths emitted with events will
   * be relative to this.
   */
  cwd?: string;

  /**
   *  If set to true then the strings passed to .watch() and .add() are treated as literal path
   *  names, even if they look like globs. Default: false.
   */
  disableGlobbing?: boolean;

  /**
   * Whether to use fs.watchFile (backed by polling), or fs.watch. If polling leads to high CPU
   * utilization, consider setting this to `false`. It is typically necessary to **set this to
   * `true` to successfully watch files over a network**, and it may be necessary to successfully
   * watch files in other non-standard situations. Setting to `true` explicitly on OS X overrides
   * the `useFsEvents` default.
   */
  usePolling?: boolean;

  /**
   * Whether to use the `fsevents` watching interface if available. When set to `true` explicitly
   * and `fsevents` is available this supercedes the `usePolling` setting. When set to `false` on
   * OS X, `usePolling: true` becomes the default.
   */
  useFsEvents?: boolean;

  /**
   * If relying upon the [`fs.Stats`](http://nodejs.org/api/fs.html#fs_class_fs_stats) object that
   * may get passed with `add`, `addDir`, and `change` events, set this to `true` to ensure it is
   * provided even in cases where it wasn't already available from the underlying watch events.
   */
  alwaysStat?: boolean;

  /**
   * If set, limits how many levels of subdirectories will be traversed.
   */
  depth?: number;

  /**
   * Interval of file system polling.
   */
  interval?: number;

  /**
   * Interval of file system polling for binary files. ([see list of binary extensions](https://gi
   * thub.com/sindresorhus/binary-extensions/blob/master/binary-extensions.json))
   */
  binaryInterval?: number;

  /**
   *  Indicates whether to watch files that don't have read permissions if possible. If watching
   *  fails due to `EPERM` or `EACCES` with this set to `true`, the errors will be suppressed
   *  silently.
   */
  ignorePermissionErrors?: boolean;

  /**
   * `true` if `useFsEvents` and `usePolling` are `false`). Automatically filters out artifacts
   * that occur when using editors that use "atomic writes" instead of writing directly to the
   * source file. If a file is re-added within 100 ms of being deleted, Chokidar emits a `change`
   * event rather than `unlink` then `add`. If the default of 100 ms does not work well for you,
   * you can override it by setting `atomic` to a custom value, in milliseconds.
   */
  atomic?: boolean | number;

  /**
   * can be set to an object in order to adjust timing params:
   */
  awaitWriteFinish?: AwaitWriteFinishOptions | boolean;
}

export interface AwaitWriteFinishOptions {
  /**
   * Amount of time in milliseconds for a file size to remain constant before emitting its event.
   */
  stabilityThreshold?: number;

  /**
   * File size polling interval.
   */
  pollInterval?: number;
}

/**
 * produces an instance of `FSWatcher`.
 */
export function watch(
  paths: string | string[],
  options?: WatchOptions
): FSWatcher;