// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *  Copyright (c) by Uros Bizjak <uros@kss-loka.si>
 *
 *  Midi synth routines for OPL2/OPL3/OPL4 FM
 */

#undef DEBUG_ALLOC
#undef DEBUG_MIDI

#include "opl3_voice.h"
#include <sound/asoundef.h>

static void snd_opl3_note_off_unsafe(void *p, int note, int vel,
				     struct snd_midi_channel *chan);
/*
 * The next table looks magical, but it certainly is not. Its values have
 * been calculated as table[i]=8*log(i/64)/log(2) with an obvious exception
 * for i=0. This log-table converts a linear volume-scaling (0..127) to a
 * logarithmic scaling as present in the FM-synthesizer chips. so :    Volume
 * 64 =  0 db = relative volume  0 and:    Volume 32 = -6 db = relative
 * volume -8 it was implemented as a table because it is only 128 bytes and
 * it saves a lot of log() calculations. (Rob Hooft <hooft@chem.ruu.nl>)
 */

static const char opl3_volume_table[128] =
{
	-63, -48, -40, -35, -32, -29, -27, -26,
	-24, -23, -21, -20, -19, -18, -18, -17,
	-16, -15, -15, -14, -13, -13, -12, -12,
	-11, -11, -10, -10, -10, -9, -9, -8,
	-8, -8, -7, -7, -7, -6, -6, -6,
	-5, -5, -5, -5, -4, -4, -4, -4,
	-3, -3, -3, -3, -2, -2, -2, -2,
	-2, -1, -1, -1, -1, 0, 0, 0,
	0, 0, 0, 1, 1, 1, 1, 1,
	1, 2, 2, 2, 2, 2, 2, 2,
	3, 3, 3, 3, 3, 3, 3, 4,
	4, 4, 4, 4, 4, 4, 4, 5,
	5, 5, 5, 5, 5, 5, 5, 5,
	6, 6, 6, 6, 6, 6, 6, 6,
	6, 7, 7, 7, 7, 7, 7, 7,
	7, 7, 7, 8, 8, 8, 8, 8
};

void snd_opl3_calc_volume(unsigned char *volbyte, int vel,
			  struct snd_midi_channel *chan)
{
	int oldvol, newvol, n;
	int volume;

	volume = (vel * chan->gm_volume * chan->gm_expression) / (127*127);
	if (volume > 127)
		volume = 127;

	oldvol = OPL3_TOTAL_LEVEL_MASK - (*volbyte & OPL3_TOTAL_LEVEL_MASK);

	newvol = opl3_volume_table[volume] + oldvol;
	if (newvol > OPL3_TOTAL_LEVEL_MASK)
		newvol = OPL3_TOTAL_LEVEL_MASK;
	else if (newvol < 0)
		newvol = 0;

	n = OPL3_TOTAL_LEVEL_MASK - (newvol & OPL3_TOTAL_LEVEL_MASK);

	*volbyte = (*volbyte & OPL3_KSL_MASK) | (n & OPL3_TOTAL_LEVEL_MASK);
}

/*
 * Converts the note frequency to block and fnum values for the FM chip
 */
static const short opl3_note_table[16] =
{
	305, 323,	/* for pitch bending, -2 semitones */
	343, 363, 385, 408, 432, 458, 485, 514, 544, 577, 611, 647,
	686, 726	/* for pitch bending, +2 semitones */
};

static void snd_opl3_calc_pitch(unsigned char *fnum, unsigned char *blocknum,
				int note, struct snd_midi_channel *chan)
{
	int block = ((note / 12) & 0x07) - 1;
	int idx = (note % 12) + 2;
	int freq;

	if (chan->midi_pitchbend) {
		int pitchbend = chan->midi_pitchbend;
		int segment;

		if (pitchbend < -0x2000)
			pitchbend = -0x2000;
		if (pitchbend > 0x1FFF)
			pitchbend = 0x1FFF;

		segment = pitchbend / 0x1000;
		freq = opl3_note_table[idx+segment];
		freq += ((opl3_note_table[idx+segment+1] - freq) *
			 (pitchbend % 0x1000)) / 0x1000;
	} else {
		freq = opl3_note_table[idx];
	}

	*fnum = (unsigned char) freq;
	*blocknum = ((freq >> 8) & OPL3_FNUM_HIGH_MASK) |
		((block << 2) & OPL3_BLOCKNUM_MASK);
}

#ifdef DEBUG_ALLOC
static void debug_alloc(struct snd_opl3 *opl3, char *s, int voice) {
	int i;
	char *str = "x.24";

	printk(KERN_DEBUG "time %.5i: %s [%.2i]: ", opl3->use_time, s, voice);
	for (i = 0; i < opl3->max_voices; i++)
		printk(KERN_CONT "%c", *(str + opl3->voices[i].state + 1));
	printk(KERN_CONT "\n");
}
#endif

/*
 * Get a FM voice (channel) to play a note on.
 */
static int opl3_get_voice(struct snd_opl3 *opl3, int instr_4op,
			  struct snd_midi_channel *chan) {
	int chan_4op_1;		/* first voice for 4op instrument */
	int chan_4op_2;		/* second voice for 4op instrument */

	struct snd_opl3_voice *vp, *vp2;
	unsigned int voice_time;
	int i;

#ifdef DEBUG_ALLOC
	char *alloc_type[3] = { "FREE     ", "CHEAP    ", "EXPENSIVE" };
#endif

	/* This is our "allocation cost" table */
	enum {
		FREE = 0, CHEAP, EXPENSIVE, END
	};

	/* Keeps track of what we are finding */
	struct best {
		unsigned int time;
		int voice;
	} best[END];
	struct best *bp;

	for (i = 0; i < END; i++) {
		best[i].time = (unsigned int)(-1); /* XXX MAX_?INT really */
		best[i].voice = -1;
	}

	/* Look through all the channels for the most suitable. */
	for (i = 0; i < opl3->max_voices; i++) {
		vp = &opl3->voices[i];

		if (vp->state == SNDRV_OPL3_ST_NOT_AVAIL)
		  /* skip unavailable channels, allocated by
		     drum voices or by bounded 4op voices) */
			continue;

		voice_time = vp->time;
		bp = best;

		chan_4op_1 = ((i < 3) || (i > 8 && i < 12));
		chan_4op_2 = ((i > 2 && i < 6) || (i > 11 && i < 15));
		if (instr_4op) {
			/* allocate 4op voice */
			/* skip channels unavailable to 4op instrument */
			if (!chan_4op_1)
				continue;

			if (vp->state)
				/* kill one voice, CHEAP */
				bp++;
			/* get state of bounded 2op channel
			   to be allocated for 4op instrument */
			vp2 = &opl3->voices[i + 3];
			if (vp2->state == SNDRV_OPL3_ST_ON_2OP) {
				/* kill two voices, EXPENSIVE */
				bp++;
				voice_time = (voice_time > vp->time) ?
					voice_time : vp->time;
			}
		} else {
			/* allocate 2op voice */
			if ((chan_4op_1) || (chan_4op_2))
				/* use bounded channels for 2op, CHEAP */
				bp++;
			else if (vp->state)
				/* kill one voice on 2op channel, CHEAP */
				bp++;
			/* raise kill cost to EXPENSIVE for all channels */
			if (vp->state)
				bp++;
		}
		if (voice_time < bp->time) {
			bp->time = voice_time;
			bp->voice = i;
		}
	}

	for (i = 0; i < END; i++) {
		if (best[i].voice >= 0) {
#ifdef DEBUG_ALLOC
			printk(KERN_DEBUG "%s %iop allocation on voice %i\n",
			       alloc_type[i], instr_4op ? 4 : 2,
			       best[i].voice);
#endif
			return best[i].voice;
		}
	}
	/* not found */
	return -1;
}

/* ------------------------------ */

/*
 * System timer interrupt function
 */
void snd_opl3_timer_func(struct timer_list *t)
{

	struct snd_opl3 *opl3 = from_timer(opl3, t, tlist);
	unsigned long flags;
	int again = 0;
	int i;

	spin_lock_irqsave(&opl3->voice_lock, flags);
	for (i = 0; i < opl3->max_voices; i++) {
		struct snd_opl3_voice *vp = &opl3->voices[i];
		if (vp->state > 0 && vp->note_off_check) {
			if (vp->note_off == jiffies)
				snd_opl3_note_off_unsafe(opl3, vp->note, 0,
							 vp->chan);
			else
				again++;
		}
	}
	spin_unlock_irqrestore(&opl3->voice_lock, flags);

	spin_lock_irqsave(&opl3->sys_timer_lock, flags);
	if (again)
		mod_timer(&opl3->tlist, jiffies + 1);	/* invoke again */
	else
		opl3->sys_timer_status = 0;
	spin_unlock_irqrestore(&opl3->sys_timer_lock, flags);
}

/*
 * Start system timer
 */
static void snd_opl3_start_timer(struct snd_opl3 *opl3)
{
	unsigned long flags;
	spin_lock_irqsave(&opl3->sys_timer_lock, flags);
	if (! opl3->sys_timer_status) {
		mod_timer(&opl3->tlist, jiffies + 1);
		opl3->sys_timer_status = 1;
	}
	spin_unlock_irqrestore(&opl3->sys_timer_lock, flags);
}

/* ------------------------------ */

static const int snd_opl3_oss_map[MAX_OPL3_VOICES] = {
	0, 1, 2, 9, 10, 11, 6, 7, 8, 15, 16, 17, 3, 4 ,5, 12, 13, 14
};

/*
 * Start a note.
 */
void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
{
	struct snd_opl3 *opl3;
	int instr_4op;

	int voice;
	struct snd_opl3_voice *vp, *vp2;
	unsigned short connect_mask;
	unsigned char connection;
	unsigned char vol_op[4];

	int extra_prg = 0;

	unsigned short reg_side;
	unsigned char op_offset;
	unsigned char voice_offset;
	unsigned short opl3_reg;
	unsigned char reg_val;
	unsigned char prg, bank;

	int key = note;
	unsigned char fnum, blocknum;
	int i;

	struct fm_patch *patch;
	struct fm_instrument *fm;
	unsigned long flags;

	opl3 = p;

#ifdef DEBUG_MIDI
	snd_printk(KERN_DEBUG "Note on, ch %i, inst %i, note %i, vel %i\n",
		   chan->number, chan->midi_program, note, vel);
#endif

	/* in SYNTH mode, application takes care of voices */
	/* in SEQ mode, drum voice numbers are notes on drum channel */
	if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
		if (chan->drum_channel) {
			/* percussion instruments are located in bank 128 */
			bank = 128;
			prg = note;
		} else {
			bank = chan->gm_bank_select;
			prg = chan->midi_program;
		}
	} else {
		/* Prepare for OSS mode */
		if (chan->number >= MAX_OPL3_VOICES)
			return;

		/* OSS instruments are located in bank 127 */
		bank = 127;
		prg = chan->midi_program;
	}

	spin_lock_irqsave(&opl3->voice_lock, flags);

	if (use_internal_drums) {
		snd_opl3_drum_switch(opl3, note, vel, 1, chan);
		spin_unlock_irqrestore(&opl3->voice_lock, flags);
		return;
	}

 __extra_prg:
	patch = snd_opl3_find_patch(opl3, prg, bank, 0);
	if (!patch) {
		spin_unlock_irqrestore(&opl3->voice_lock, flags);
		return;
	}

	fm = &patch->inst;
	switch (patch->type) {
	case FM_PATCH_OPL2:
		instr_4op = 0;
		break;
	case FM_PATCH_OPL3:
		if (opl3->hardware >= OPL3_HW_OPL3) {
			instr_4op = 1;
			break;
		}
		fallthrough;
	default:
		spin_unlock_irqrestore(&opl3->voice_lock, flags);
		return;
	}
#ifdef DEBUG_MIDI
	snd_printk(KERN_DEBUG "  --> OPL%i instrument: %s\n",
		   instr_4op ? 3 : 2, patch->name);
#endif
	/* in SYNTH mode, application takes care of voices */
	/* in SEQ mode, allocate voice on free OPL3 channel */
	if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
		voice = opl3_get_voice(opl3, instr_4op, chan);
	} else {
		/* remap OSS voice */
		voice = snd_opl3_oss_map[chan->number];
	}

	if (voice < 0) {
		spin_unlock_irqrestore(&opl3->voice_lock, flags);
		return;
	}

	if (voice < MAX_OPL2_VOICES) {
		/* Left register block for voices 0 .. 8 */
		reg_side = OPL3_LEFT;
		voice_offset = voice;
		connect_mask = (OPL3_LEFT_4OP_0 << voice_offset) & 0x07;
	} else {
		/* Right register block for voices 9 .. 17 */
		reg_side = OPL3_RIGHT;
		voice_offset = voice - MAX_OPL2_VOICES;
		connect_mask = (OPL3_RIGHT_4OP_0 << voice_offset) & 0x38;
	}

	/* kill voice on channel */
	vp = &opl3->voices[voice];
	if (vp->state > 0) {
		opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
		reg_val = vp->keyon_reg & ~OPL3_KEYON_BIT;
		opl3->command(opl3, opl3_reg, reg_val);
	}
	if (instr_4op) {
		vp2 = &opl3->voices[voice + 3];
		if (vp->state > 0) {
			opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK +
					       voice_offset + 3);
			reg_val = vp->keyon_reg & ~OPL3_KEYON_BIT;
			opl3->command(opl3, opl3_reg, reg_val);
		}
	}

	/* set connection register */
	if (instr_4op) {
		if ((opl3->connection_reg ^ connect_mask) & connect_mask) {
			opl3->connection_reg |= connect_mask;
			/* set connection bit */
			opl3_reg = OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT;
			opl3->command(opl3, opl3_reg, opl3->connection_reg);
		}
	} else {
		if ((opl3->connection_reg ^ ~connect_mask) & connect_mask) {
			opl3->connection_reg &= ~connect_mask;
			/* clear connection bit */
			opl3_reg = OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT;
			opl3->command(opl3, opl3_reg, opl3->connection_reg);
		}
	}

#ifdef DEBUG_MIDI
	snd_printk(KERN_DEBUG "  --> setting OPL3 connection: 0x%x\n",
		   opl3->connection_reg);
#endif
	/*
	 * calculate volume depending on connection
	 * between FM operators (see include/opl3.h)
	 */
	for (i = 0; i < (instr_4op ? 4 : 2); i++)
		vol_op[i] = fm->op[i].ksl_level;

	connection = fm->feedback_connection[0] & 0x01;
	if (instr_4op) {
		connection <<= 1;
		connection |= fm->feedback_connection[1] & 0x01;

		snd_opl3_calc_volume(&vol_op[3], vel, chan);
		switch (connection) {
		case 0x03:
			snd_opl3_calc_volume(&vol_op[2], vel, chan);
			fallthrough;
		case 0x02:
			snd_opl3_calc_volume(&vol_op[0], vel, chan);
			break;
		case 0x01:
			snd_opl3_calc_volume(&vol_op[1], vel, chan);
		}
	} else {
		snd_opl3_calc_volume(&vol_op[1], vel, chan);
		if (connection)
			snd_opl3_calc_volume(&vol_op[0], vel, chan);
	}

	/* Program the FM voice characteristics */
	for (i = 0; i < (instr_4op ? 4 : 2); i++) {
#ifdef DEBUG_MIDI
		snd_printk(KERN_DEBUG "  --> programming operator %i\n", i);
#endif
		op_offset = snd_opl3_regmap[voice_offset][i];

		/* Set OPL3 AM_VIB register of requested voice/operator */
		reg_val = fm->op[i].am_vib;
		opl3_reg = reg_side | (OPL3_REG_AM_VIB + op_offset);
		opl3->command(opl3, opl3_reg, reg_val);

		/* Set OPL3 KSL_LEVEL register of requested voice/operator */
		reg_val = vol_op[i];
		opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + op_offset);
		opl3->command(opl3, opl3_reg, reg_val);

		/* Set OPL3 ATTACK_DECAY register of requested voice/operator */
		reg_val = fm->op[i].attack_decay;
		opl3_reg = reg_side | (OPL3_REG_ATTACK_DECAY + op_offset);
		opl3->command(opl3, opl3_reg, reg_val);

		/* Set OPL3 SUSTAIN_RELEASE register of requested voice/operator */
		reg_val = fm->op[i].sustain_release;
		opl3_reg = reg_side | (OPL3_REG_SUSTAIN_RELEASE + op_offset);
		opl3->command(opl3, opl3_reg, reg_val);

		/* Select waveform */
		reg_val = fm->op[i].wave_select;
		opl3_reg = reg_side | (OPL3_REG_WAVE_SELECT + op_offset);
		opl3->command(opl3, opl3_reg, reg_val);
	}

	/* Set operator feedback and 2op inter-operator connection */
	reg_val = fm->feedback_connection[0];
	/* Set output voice connection */
	reg_val |= OPL3_STEREO_BITS;
	if (chan->gm_pan < 43)
		reg_val &= ~OPL3_VOICE_TO_RIGHT;
	if (chan->gm_pan > 85)
		reg_val &= ~OPL3_VOICE_TO_LEFT;
	opl3_reg = reg_side | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset);
	opl3->command(opl3, opl3_reg, reg_val);

	if (instr_4op) {
		/* Set 4op inter-operator connection */
		reg_val = fm->feedback_connection[1] & OPL3_CONNECTION_BIT;
		/* Set output voice connection */
		reg_val |= OPL3_STEREO_BITS;
		if (chan->gm_pan < 43)
			reg_val &= ~OPL3_VOICE_TO_RIGHT;
		if (chan->gm_pan > 85)
			reg_val &= ~OPL3_VOICE_TO_LEFT;
		opl3_reg = reg_side | (OPL3_REG_FEEDBACK_CONNECTION +
				       voice_offset + 3);
		opl3->command(opl3, opl3_reg, reg_val);
	}

	/*
	 * Special treatment of percussion notes for fm:
	 * Requested pitch is really program, and pitch for
	 * device is whatever was specified in the patch library.
	 */
	if (fm->fix_key)
		note = fm->fix_key;
	/*
	 * use transpose if defined in patch library
	 */
	if (fm->trnsps)
		note += (fm->trnsps - 64);

	snd_opl3_calc_pitch(&fnum, &blocknum, note, chan);

	/* Set OPL3 FNUM_LOW register of requested voice */
	opl3_reg = reg_side | (OPL3_REG_FNUM_LOW + voice_offset);
	opl3->command(opl3, opl3_reg, fnum);

	opl3->voices[voice].keyon_reg = blocknum;

	/* Set output sound flag */
	blocknum |= OPL3_KEYON_BIT;

#ifdef DEBUG_MIDI
	snd_printk(KERN_DEBUG "  --> trigger voice %i\n", voice);
#endif
	/* Set OPL3 KEYON_BLOCK register of requested voice */
	opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
	opl3->command(opl3, opl3_reg, blocknum);

	/* kill note after fixed duration (in centiseconds) */
	if (fm->fix_dur) {
		opl3->voices[voice].note_off = jiffies +
			(fm->fix_dur * HZ) / 100;
		snd_opl3_start_timer(opl3);
		opl3->voices[voice].note_off_check = 1;
	} else
		opl3->voices[voice].note_off_check = 0;

	/* get extra pgm, but avoid possible loops */
	extra_prg = (extra_prg) ? 0 : fm->modes;

	/* do the bookkeeping */
	vp->time = opl3->use_time++;
	vp->note = key;
	vp->chan = chan;

	if (instr_4op) {
		vp->state = SNDRV_OPL3_ST_ON_4OP;

		vp2 = &opl3->voices[voice + 3];
		vp2->time = opl3->use_time++;
		vp2->note = key;
		vp2->chan = chan;
		vp2->state = SNDRV_OPL3_ST_NOT_AVAIL;
	} else {
		if (vp->state == SNDRV_OPL3_ST_ON_4OP) {
			/* 4op killed by 2op, release bounded voice */
			vp2 = &opl3->voices[voice + 3];
			vp2->time = opl3->use_time++;
			vp2->state = SNDRV_OPL3_ST_OFF;
		}
		vp->state = SNDRV_OPL3_ST_ON_2OP;
	}

#ifdef DEBUG_ALLOC
	debug_alloc(opl3, "note on ", voice);
#endif

	/* allocate extra program if specified in patch library */
	if (extra_prg) {
		if (extra_prg > 128) {
			bank = 128;
			/* percussions start at 35 */
			prg = extra_prg - 128 + 35 - 1;
		} else {
			bank = 0;
			prg = extra_prg - 1;
		}
#ifdef DEBUG_MIDI
		snd_printk(KERN_DEBUG " *** allocating extra program\n");
#endif
		goto __extra_prg;
	}
	spin_unlock_irqrestore(&opl3->voice_lock, flags);
}

static void snd_opl3_kill_voice(struct snd_opl3 *opl3, int voice)
{
	unsigned short reg_side;
	unsigned char voice_offset;
	unsigned short opl3_reg;

	struct snd_opl3_voice *vp, *vp2;

	if (snd_BUG_ON(voice >= MAX_OPL3_VOICES))
		return;

	vp = &opl3->voices[voice];
	if (voice < MAX_OPL2_VOICES) {
		/* Left register block for voices 0 .. 8 */
		reg_side = OPL3_LEFT;
		voice_offset = voice;
	} else {
		/* Right register block for voices 9 .. 17 */
		reg_side = OPL3_RIGHT;
		voice_offset = voice - MAX_OPL2_VOICES;
	}

	/* kill voice */
#ifdef DEBUG_MIDI
	snd_printk(KERN_DEBUG "  --> kill voice %i\n", voice);
#endif
	opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
	/* clear Key ON bit */
	opl3->command(opl3, opl3_reg, vp->keyon_reg);

	/* do the bookkeeping */
	vp->time = opl3->use_time++;

	if (vp->state == SNDRV_OPL3_ST_ON_4OP) {
		vp2 = &opl3->voices[voice + 3];

		vp2->time = opl3->use_time++;
		vp2->state = SNDRV_OPL3_ST_OFF;
	}
	vp->state = SNDRV_OPL3_ST_OFF;
#ifdef DEBUG_ALLOC
	debug_alloc(opl3, "note off", voice);
#endif

}

/*
 * Release a note in response to a midi note off.
 */
static void snd_opl3_note_off_unsafe(void *p, int note, int vel,
				     struct snd_midi_channel *chan)
{
  	struct snd_opl3 *opl3;

	int voice;
	struct snd_opl3_voice *vp;

	opl3 = p;

#ifdef DEBUG_MIDI
	snd_printk(KERN_DEBUG "Note off, ch %i, inst %i, note %i\n",
		   chan->number, chan->midi_program, note);
#endif

	if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
		if (chan->drum_channel && use_internal_drums) {
			snd_opl3_drum_switch(opl3, note, vel, 0, chan);
			return;
		}
		/* this loop will hopefully kill all extra voices, because
		   they are grouped by the same channel and note values */
		for (voice = 0; voice < opl3->max_voices; voice++) {
			vp = &opl3->voices[voice];
			if (vp->state > 0 && vp->chan == chan && vp->note == note) {
				snd_opl3_kill_voice(opl3, voice);
			}
		}
	} else {
		/* remap OSS voices */
		if (chan->number < MAX_OPL3_VOICES) {
			voice = snd_opl3_oss_map[chan->number];
			snd_opl3_kill_voice(opl3, voice);
		}
	}
}

void snd_opl3_note_off(void *p, int note, int vel,
		       struct snd_midi_channel *chan)
{
	struct snd_opl3 *opl3 = p;
	unsigned long flags;

	spin_lock_irqsave(&opl3->voice_lock, flags);
	snd_opl3_note_off_unsafe(p, note, vel, chan);
	spin_unlock_irqrestore(&opl3->voice_lock, flags);
}

/*
 * key pressure change
 */
void snd_opl3_key_press(void *p, int note, int vel, struct snd_midi_channel *chan)
{
#ifdef DEBUG_MIDI
	snd_printk(KERN_DEBUG "Key pressure, ch#: %i, inst#: %i\n",
		   chan->number, chan->midi_program);
#endif
}

/*
 * terminate note
 */
void snd_opl3_terminate_note(void *p, int note, struct snd_midi_channel *chan)
{
#ifdef DEBUG_MIDI
	snd_printk(KERN_DEBUG "Terminate note, ch#: %i, inst#: %i\n",
		   chan->number, chan->midi_program);
#endif
}

static void snd_opl3_update_pitch(struct snd_opl3 *opl3, int voice)
{
	unsigned short reg_side;
	unsigned char voice_offset;
	unsigned short opl3_reg;

	unsigned char fnum, blocknum;

	struct snd_opl3_voice *vp;

	if (snd_BUG_ON(voice >= MAX_OPL3_VOICES))
		return;

	vp = &opl3->voices[voice];
	if (vp->chan == NULL)
		return; /* not allocated? */

	if (voice < MAX_OPL2_VOICES) {
		/* Left register block for voices 0 .. 8 */
		reg_side = OPL3_LEFT;
		voice_offset = voice;
	} else {
		/* Right register block for voices 9 .. 17 */
		reg_side = OPL3_RIGHT;
		voice_offset = voice - MAX_OPL2_VOICES;
	}

	snd_opl3_calc_pitch(&fnum, &blocknum, vp->note, vp->chan);

	/* Set OPL3 FNUM_LOW register of requested voice */
	opl3_reg = reg_side | (OPL3_REG_FNUM_LOW + voice_offset);
	opl3->command(opl3, opl3_reg, fnum);

	vp->keyon_reg = blocknum;

	/* Set output sound flag */
	blocknum |= OPL3_KEYON_BIT;

	/* Set OPL3 KEYON_BLOCK register of requested voice */
	opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
	opl3->command(opl3, opl3_reg, blocknum);

	vp->time = opl3->use_time++;
}

/*
 * Update voice pitch controller
 */
static void snd_opl3_pitch_ctrl(struct snd_opl3 *opl3, struct snd_midi_channel *chan)
{
	int voice;
	struct snd_opl3_voice *vp;

	unsigned long flags;

	spin_lock_irqsave(&opl3->voice_lock, flags);

	if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
		for (voice = 0; voice < opl3->max_voices; voice++) {
			vp = &opl3->voices[voice];
			if (vp->state > 0 && vp->chan == chan) {
				snd_opl3_update_pitch(opl3, voice);
			}
		}
	} else {
		/* remap OSS voices */
		if (chan->number < MAX_OPL3_VOICES) {
			voice = snd_opl3_oss_map[chan->number];
			snd_opl3_update_pitch(opl3, voice);
		}
	}
	spin_unlock_irqrestore(&opl3->voice_lock, flags);
}

/*
 * Deal with a controller type event.  This includes all types of
 * control events, not just the midi controllers
 */
void snd_opl3_control(void *p, int type, struct snd_midi_channel *chan)
{
  	struct snd_opl3 *opl3;

	opl3 = p;
#ifdef DEBUG_MIDI
	snd_printk(KERN_DEBUG "Controller, TYPE = %i, ch#: %i, inst#: %i\n",
		   type, chan->number, chan->midi_program);
#endif

	switch (type) {
	case MIDI_CTL_MSB_MODWHEEL:
		if (chan->control[MIDI_CTL_MSB_MODWHEEL] > 63)
			opl3->drum_reg |= OPL3_VIBRATO_DEPTH;
		else
			opl3->drum_reg &= ~OPL3_VIBRATO_DEPTH;
		opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION,
				 opl3->drum_reg);
		break;
	case MIDI_CTL_E2_TREMOLO_DEPTH:
		if (chan->control[MIDI_CTL_E2_TREMOLO_DEPTH] > 63)
			opl3->drum_reg |= OPL3_TREMOLO_DEPTH;
		else
			opl3->drum_reg &= ~OPL3_TREMOLO_DEPTH;
		opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION,
				 opl3->drum_reg);
		break;
	case MIDI_CTL_PITCHBEND:
		snd_opl3_pitch_ctrl(opl3, chan);
		break;
	}
}

/*
 * NRPN events
 */
void snd_opl3_nrpn(void *p, struct snd_midi_channel *chan,
		   struct snd_midi_channel_set *chset)
{
#ifdef DEBUG_MIDI
	snd_printk(KERN_DEBUG "NRPN, ch#: %i, inst#: %i\n",
		   chan->number, chan->midi_program);
#endif
}

/*
 * receive sysex
 */
void snd_opl3_sysex(void *p, unsigned char *buf, int len,
		    int parsed, struct snd_midi_channel_set *chset)
{
#ifdef DEBUG_MIDI
	snd_printk(KERN_DEBUG "SYSEX\n");
#endif
}


SmartDraggableLocator

A DraggableLocator is used to place figures relative to the parent nearest corner. It is
possible to move a child node via drag&drop.

init

Constructs a locator with associated parent.

relocate

Controls the location of an I{@link draw2d.Figure}

index

child index of the figure

figure

the figure to control


/*
 * Copyright (C) by Olivier Goffart <ogoffart@owncloud.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.
 */

#include "config.h"
#include "propagateupload.h"
#include "owncloudpropagator_p.h"
#include "networkjobs.h"
#include "account.h"
#include "common/syncjournaldb.h"
#include "common/syncjournalfilerecord.h"
#include "common/utility.h"
#include "filesystem.h"
#include "propagatorjobs.h"
#include "common/checksums.h"
#include "syncengine.h"
#include "propagateremotedelete.h"
#include "common/asserts.h"

#include <QNetworkAccessManager>
#include <QFileInfo>
#include <QDir>
#include <cmath>
#include <cstring>

namespace OCC {

void PropagateUploadFileV1::doStartUpload()
{
    _chunkCount = std::ceil(_fileToUpload._size / double(chunkSize()));
    _startChunk = 0;
    _transferId = qrand() ^ _item->_modtime ^ (_fileToUpload._size << 16);

    const SyncJournalDb::UploadInfo progressInfo = propagator()->_journal->getUploadInfo(_item->_file);

    if (progressInfo._valid && progressInfo.isChunked() && progressInfo._modtime == _item->_modtime && progressInfo._size == qint64(_item->_size)
        && (progressInfo._contentChecksum == _item->_checksumHeader || progressInfo._contentChecksum.isEmpty() || _item->_checksumHeader.isEmpty())) {
        _startChunk = progressInfo._chunk;
        _transferId = progressInfo._transferid;
        qCInfo(lcPropagateUpload) << _item->_file << ": Resuming from chunk " << _startChunk;
    } else if (_chunkCount <= 1 && !_item->_checksumHeader.isEmpty()) {
        // If there is only one chunk, write the checksum in the database, so if the PUT is sent
        // to the server, but the connection drops before we get the etag, we can check the checksum
        // in reconcile (issue #5106)
        SyncJournalDb::UploadInfo pi;
        pi._valid = true;
        pi._chunk = 0;
        pi._transferid = 0; // We set a null transfer id because it is not chunked.
        pi._modtime = _item->_modtime;
        pi._errorCount = 0;
        pi._contentChecksum = _item->_checksumHeader;
        pi._size = _item->_size;
        propagator()->_journal->setUploadInfo(_item->_file, pi);
        propagator()->_journal->commit("Upload info");
    }

    _currentChunk = 0;

    propagator()->reportProgress(*_item, 0);
    startNextChunk();
}

void PropagateUploadFileV1::startNextChunk()
{
    if (propagator()->_abortRequested.fetchAndAddRelaxed(0))
        return;

    if (!_jobs.isEmpty() && _currentChunk + _startChunk >= _chunkCount - 1) {
        // Don't do parallel upload of chunk if this might be the last chunk because the server cannot handle that
        // https://github.com/owncloud/core/issues/11106
        // We return now and when the _jobs are finished we will proceed with the last chunk
        // NOTE: Some other parts of the code such as slotUploadProgress also assume that the last chunk
        // is sent last.
        return;
    }
    quint64 fileSize = _fileToUpload._size;
    auto headers = PropagateUploadFileCommon::headers();
    headers["OC-Total-Length"] = QByteArray::number(fileSize);
    headers["OC-Chunk-Size"] = QByteArray::number(quint64(chunkSize()));

    QString path = _fileToUpload._file;

    auto device = std::make_unique<UploadDevice>(&propagator()->_bandwidthManager);
    qint64 chunkStart = 0;
    qint64 currentChunkSize = fileSize;
    bool isFinalChunk = false;
    if (_chunkCount > 1) {
        int sendingChunk = (_currentChunk + _startChunk) % _chunkCount;
        // XOR with chunk size to make sure everything goes well if chunk size changes between runs
        uint transid = _transferId ^ chunkSize();
        qCInfo(lcPropagateUpload) << "Upload chunk" << sendingChunk << "of" << _chunkCount << "transferid(remote)=" << transid;
        path += QString("-chunking-%1-%2-%3").arg(transid).arg(_chunkCount).arg(sendingChunk);

        headers["OC-Chunked"] = "1";

        chunkStart = chunkSize() * quint64(sendingChunk);
        currentChunkSize = chunkSize();
        if (sendingChunk == _chunkCount - 1) { // last chunk
            currentChunkSize = (fileSize % chunkSize());
            if (currentChunkSize == 0) { // if the last chunk pretends to be 0, its actually the full chunk size.
                currentChunkSize = chunkSize();
            }
            isFinalChunk = true;
        }
    } else {
        // if there's only one chunk, it's the final one
        isFinalChunk = true;
    }
    qCDebug(lcPropagateUpload) << _chunkCount << isFinalChunk << chunkStart << currentChunkSize;

    if (isFinalChunk && !_transmissionChecksumHeader.isEmpty()) {
        qCInfo(lcPropagateUpload) << propagator()->_remoteFolder + path << _transmissionChecksumHeader;
        headers[checkSumHeaderC] = _transmissionChecksumHeader;
    }

    const QString fileName = _fileToUpload._path;
    qDebug() << "Trying to upload" << fileName;
    if (!device->prepareAndOpen(fileName, chunkStart, currentChunkSize)) {
        qCWarning(lcPropagateUpload) << "Could not prepare upload device: " << device->errorString();

        // If the file is currently locked, we want to retry the sync
        // when it becomes available again.
        if (FileSystem::isFileLocked(fileName)) {
            emit propagator()->seenLockedFile(fileName);
        }
        // Soft error because this is likely caused by the user modifying his files while syncing
        abortWithError(SyncFileItem::SoftError, device->errorString());
        return;
    }

    // job takes ownership of device via a QScopedPointer. Job deletes itself when finishing
    auto devicePtr = device.get(); // for connections later
    auto *job = new PUTFileJob(propagator()->account(), propagator()->_remoteFolder + path, std::move(device), headers, _currentChunk, this);
    _jobs.append(job);
    connect(job, &PUTFileJob::finishedSignal, this, &PropagateUploadFileV1::slotPutFinished);
    connect(job, &PUTFileJob::uploadProgress, this, &PropagateUploadFileV1::slotUploadProgress);
    connect(job, &PUTFileJob::uploadProgress, devicePtr, &UploadDevice::slotJobUploadProgress);
    connect(job, &QObject::destroyed, this, &PropagateUploadFileCommon::slotJobDestroyed);
    if (isFinalChunk)
        adjustLastJobTimeout(job, fileSize);
    job->start();
    propagator()->_activeJobList.append(this);
    _currentChunk++;

    bool parallelChunkUpload = true;

    if (propagator()->account()->capabilities().chunkingParallelUploadDisabled()) {
        // Server may also disable parallel chunked upload for any higher version
        parallelChunkUpload = false;
    } else {
        QByteArray env = qgetenv("OWNCLOUD_PARALLEL_CHUNK");
        if (!env.isEmpty()) {
            parallelChunkUpload = env != "false" && env != "0";
        } else {
            int versionNum = propagator()->account()->serverVersionInt();
            if (versionNum < Account::makeServerVersion(8, 0, 3)) {
                // Disable parallel chunk upload severs older than 8.0.3 to avoid too many
                // internal sever errors (#2743, #2938)
                parallelChunkUpload = false;
            }
        }
    }

    if (_currentChunk + _startChunk >= _chunkCount - 1) {
        // Don't do parallel upload of chunk if this might be the last chunk because the server cannot handle that
        // https://github.com/owncloud/core/issues/11106
        parallelChunkUpload = false;
    }

    if (parallelChunkUpload && (propagator()->_activeJobList.count() < propagator()->maximumActiveTransferJob())
        && _currentChunk < _chunkCount) {
        startNextChunk();
    }
    if (!parallelChunkUpload || _chunkCount - _currentChunk <= 0) {
        propagator()->scheduleNextJob();
    }
}

void PropagateUploadFileV1::slotPutFinished()
{
    auto *job = qobject_cast<PUTFileJob *>(sender());
    ASSERT(job);

    slotJobDestroyed(job); // remove it from the _jobs list

    propagator()->_activeJobList.removeOne(this);

    if (_finished) {
        // We have sent the finished signal already. We don't need to handle any remaining jobs
        return;
    }

    _item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
    QNetworkReply::NetworkError err = job->reply()->error();
    if (err != QNetworkReply::NoError) {
        commonErrorHandling(job);
        return;
    }

    // The server needs some time to process the request and provide us with a poll URL
    if (_item->_httpErrorCode == 202) {
        QString path = QString::fromUtf8(job->reply()->rawHeader("OC-Finish-Poll"));
        if (path.isEmpty()) {
            done(SyncFileItem::NormalError, tr("Poll URL missing"));
            return;
        }
        _finished = true;
        startPollJob(path);
        return;
    }

    // Check the file again post upload.
    // Two cases must be considered separately: If the upload is finished,
    // the file is on the server and has a changed ETag. In that case,
    // the etag has to be properly updated in the client journal, and because
    // of that we can bail out here with an error. But we can reschedule a
    // sync ASAP.
    // But if the upload is ongoing, because not all chunks were uploaded
    // yet, the upload can be stopped and an error can be displayed, because
    // the server hasn't registered the new file yet.
    QByteArray etag = getEtagFromReply(job->reply());
    _finished = etag.length() > 0;

    /* Check if the file still exists,
     * but we could be operating in a temporary file, so check both if
     * the file to upload is different than the file on disk
     */
    const QString fullFilePath(propagator()->getFilePath(_item->_file));
    if (!FileSystem::fileExists(fullFilePath)) {
        if (!_finished) {
            abortWithError(SyncFileItem::SoftError, tr("The local file was removed during sync."));
            return;
        } else {
            propagator()->_anotherSyncNeeded = true;
        }
    }

    // Check whether the file changed since discovery. the file check here is the original and not the temprary.
    if (!FileSystem::verifyFileUnchanged(fullFilePath, _item->_size, _item->_modtime)) {
        propagator()->_anotherSyncNeeded = true;
        if (!_finished) {
            abortWithError(SyncFileItem::SoftError, tr("Local file changed during sync."));
            // FIXME:  the legacy code was retrying for a few seconds.
            //         and also checking that after the last chunk, and removed the file in case of INSTRUCTION_NEW
            return;
        }
    }

    if (!_finished) {
        // Proceed to next chunk.
        if (_currentChunk >= _chunkCount) {
            if (!_jobs.empty()) {
                // just wait for the other job to finish.
                return;
            }
            done(SyncFileItem::NormalError, tr("The server did not acknowledge the last chunk. (No e-tag was present)"));
            return;
        }

        // Deletes an existing blacklist entry on successful chunk upload
        if (_item->_hasBlacklistEntry) {
            propagator()->_journal->wipeErrorBlacklistEntry(_item->_file);
            _item->_hasBlacklistEntry = false;
        }

        SyncJournalDb::UploadInfo pi;
        pi._valid = true;
        auto currentChunk = job->_chunk;
        foreach (auto *job, _jobs) {
            // Take the minimum finished one
            if (auto putJob = qobject_cast<PUTFileJob *>(job)) {
                currentChunk = qMin(currentChunk, putJob->_chunk - 1);
            }
        }
        pi._chunk = (currentChunk + _startChunk + 1) % _chunkCount; // next chunk to start with
        pi._transferid = _transferId;
        pi._modtime = _item->_modtime;
        pi._errorCount = 0; // successful chunk upload resets
        pi._contentChecksum = _item->_checksumHeader;
        pi._size = _item->_size;
        propagator()->_journal->setUploadInfo(_item->_file, pi);
        propagator()->_journal->commit("Upload info");
        startNextChunk();
        return;
    }
    // the following code only happens after all chunks were uploaded.

    // the file id should only be empty for new files up- or downloaded
    QByteArray fid = job->reply()->rawHeader("OC-FileID");
    if (!fid.isEmpty()) {
        if (!_item->_fileId.isEmpty() && _item->_fileId != fid) {
            qCWarning(lcPropagateUpload) << "File ID changed!" << _item->_fileId << fid;
        }
        _item->_fileId = fid;
    }

    _item->_etag = etag;

    _item->_responseTimeStamp = job->responseTimestamp();

    if (job->reply()->rawHeader("X-OC-MTime") != "accepted") {
        // X-OC-MTime is supported since owncloud 5.0.   But not when chunking.
        // Normally Owncloud 6 always puts X-OC-MTime
        qCWarning(lcPropagateUpload) << "Server does not support X-OC-MTime" << job->reply()->rawHeader("X-OC-MTime");
        // Well, the mtime was not set
        done(SyncFileItem::SoftError, "Server does not support X-OC-MTime");
        return;
    }

    finalize();
}

void PropagateUploadFileV1::slotUploadProgress(qint64 sent, qint64 total)
{
    // Completion is signaled with sent=0, total=0; avoid accidentally
    // resetting progress due to the sent being zero by ignoring it.
    // finishedSignal() is bound to be emitted soon anyway.
    // See https://bugreports.qt.io/browse/QTBUG-44782.
    if (sent == 0 && total == 0) {
        return;
    }

    int progressChunk = _currentChunk + _startChunk - 1;
    if (progressChunk >= _chunkCount)
        progressChunk = _currentChunk - 1;

    // amount is the number of bytes already sent by all the other chunks that were sent
    // not including this one.
    // FIXME: this assumes all chunks have the same size, which is true only if the last chunk
    // has not been finished (which should not happen because the last chunk is sent sequentially)
    quint64 amount = progressChunk * chunkSize();

    sender()->setProperty("byteWritten", sent);
    if (_jobs.count() > 1) {
        amount -= (_jobs.count() - 1) * chunkSize();
        foreach (QObject *j, _jobs) {
            amount += j->property("byteWritten").toULongLong();
        }
    } else {
        // sender() is the only current job, no need to look at the byteWritten properties
        amount += sent;
    }
    propagator()->reportProgress(*_item, amount);
}

void PropagateUploadFileV1::abort(PropagatorJob::AbortType abortType)
{
    abortNetworkJobs(
        abortType,
        [this, abortType](AbstractNetworkJob *job) {
            if (auto *putJob = qobject_cast<PUTFileJob *>(job)){
                if (abortType == AbortType::Asynchronous
                    && _chunkCount > 0
                    && (((_currentChunk + _startChunk) % _chunkCount) == 0)
                    && putJob->device()->atEnd()) {
                    return false;
                }
            }
            return true;
        });
}

}


/**
 * Copyright (c) 2016-present, RxJava Contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
 * the License for the specific language governing permissions and limitations under the License.
 */

package io.reactivex.rxjava3.internal.operators.maybe;

import static org.junit.Assert.assertSame;

import org.junit.Test;

import io.reactivex.rxjava3.core.*;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.internal.fuseable.HasUpstreamSingleSource;
import io.reactivex.rxjava3.processors.PublishProcessor;
import io.reactivex.rxjava3.testsupport.TestHelper;

public class MaybeFromSingleTest extends RxJavaTest {
    @Test
    public void fromSingle() {
        Maybe.fromSingle(Single.just(1))
            .test()
            .assertResult(1);
    }

    @Test
    public void fromSingleThrows() {
        Maybe.fromSingle(Single.error(new UnsupportedOperationException()))
            .test()
            .assertFailure(UnsupportedOperationException.class);
    }

    @Test
    public void source() {
        Single<Integer> c = Single.never();

        assertSame(c, ((HasUpstreamSingleSource<?>)Maybe.fromSingle(c)).source());
    }

    @Test
    public void dispose() {
        TestHelper.checkDisposed(Maybe.fromSingle(PublishProcessor.create().singleOrError()));
    }

    @Test
    public void doubleOnSubscribe() {
        TestHelper.checkDoubleOnSubscribeSingleToMaybe(new Function<Single<Object>, MaybeSource<Object>>() {
            @Override
            public MaybeSource<Object> apply(Single<Object> v) throws Exception {
                return Maybe.fromSingle(v);
            }
        });
    }
}


---
title: "Figure 2a"
output: html_document
---

TSNE of all FACS cells

```{r}
library(tidyverse)
library(stringr)
library(Seurat)
library(here)
```

```{r}
load(file=here("00_data_ingest", "11_global_robj", "FACS_all.Robj"))
```

```{r}
tissue_colors = read_csv(here("00_data_ingest", "15_color_palette", "tissue_colors.csv"))
tissue_colors <- rename(tissue_colors, tissue = X1)
```

```{r, fig.width = 8, fig.height = 6}
lims = FetchData(tiss_FACS, c('tSNE_1', 'tSNE_2')) %>% summarize(xmin = min(tSNE_1), xmax = max(tSNE_1), ymin = min(tSNE_2), ymax = max(tSNE_2))

plot_min = min(lims$xmin, lims$ymin)
plot_max = max(lims$xmax, lims$ymax)

FetchData(tiss_FACS, vars.all = c('tSNE_1','tSNE_2', 'color')) %>%
  ggplot(aes(x = tSNE_1, y = tSNE_2)) + geom_point(aes(color = color), size=0.1)  +
   scale_color_identity(breaks = tissue_colors$color,
                        labels = tissue_colors$tissue,
                        guide = "legend") +
  guides(colour = guide_legend(override.aes = list(size=2))) +
  xlim(plot_min, plot_max) + ylim(plot_min, plot_max) + coord_fixed(ratio = 1) +
  xlab("tSNE 1") + ylab("tSNE 2")

ggsave(here("02_figure2","facs_tsne_by_tissue.pdf"), width = 14, height = 7, units = "in")

TSNEPlot(tiss_FACS, do.label = T, pt.size = 0.1, do.return = T) +
  xlim(plot_min, plot_max) + ylim(plot_min, plot_max) + coord_fixed(ratio = 1) +
  xlab("tSNE 1") + ylab("tSNE 2")

ggsave(here("02_figure2","facs_tsne_by_cluster.pdf"), width = 14, height = 7, units = "in")
```

```{r, fig.width = 8, fig.height = 6}
TSNEPlot(tiss_FACS, group.by = 'cell_ontology_class', do.label = T, pt.size = 0.1, do.return = T) +
  xlim(plot_min, plot_max) + ylim(plot_min, plot_max) + coord_fixed(ratio = 1) +
  xlab("tSNE 1") + ylab("tSNE 2")
ggsave(here("02_figure2","facs_tsne_by_cell_ontology_class.pdf"), width = 40, height = 25, units = "in")
```


// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package idna_test

import (
	"fmt"

	"golang.org/x/text/internal/export/idna"
)

func ExampleProfile() {
	// Raw Punycode has no restrictions and does no mappings.
	fmt.Println(idna.ToASCII(""))
	fmt.Println(idna.ToASCII("*.faß.com"))
	fmt.Println(idna.Punycode.ToASCII("*.faß.com"))

	// Rewrite IDN for lookup. This (currently) uses transitional mappings to
	// find a balance between IDNA2003 and IDNA2008 compatibility.
	fmt.Println(idna.Lookup.ToASCII(""))
	fmt.Println(idna.Lookup.ToASCII("www.faß.com"))

	// Convert an IDN to ASCII for registration purposes. This changes the
	// encoding, but reports an error if the input was illformed.
	fmt.Println(idna.Registration.ToASCII(""))
	fmt.Println(idna.Registration.ToASCII("www.faß.com"))

	// Output:
	//  <nil>
	// *.xn--fa-hia.com <nil>
	// *.xn--fa-hia.com <nil>
	//  <nil>
	// www.fass.com <nil>
	//  idna: invalid label ""
	// www.xn--fa-hia.com <nil>
}

func ExampleNew() {
	var p *idna.Profile

	// Raw Punycode has no restrictions and does no mappings.
	p = idna.New()
	fmt.Println(p.ToASCII("*.faß.com"))

	// Do mappings. Note that star is not allowed in a DNS lookup.
	p = idna.New(
		idna.MapForLookup(),
		idna.Transitional(true)) // Map ß -> ss
	fmt.Println(p.ToASCII("*.faß.com"))

	// Lookup for registration. Also does not allow '*'.
	p = idna.New(idna.ValidateForRegistration())
	fmt.Println(p.ToUnicode("*.faß.com"))

	// Set up a profile maps for lookup, but allows wild cards.
	p = idna.New(
		idna.MapForLookup(),
		idna.Transitional(true),      // Map ß -> ss
		idna.StrictDomainName(false)) // Set more permissive ASCII rules.
	fmt.Println(p.ToASCII("*.faß.com"))

	// Output:
	// *.xn--fa-hia.com <nil>
	// *.fass.com idna: disallowed rune U+002A
	// *.faß.com idna: disallowed rune U+002A
	// *.fass.com <nil>
}


//
//  PKSParserGenVisitor.m
//  ParseKit
//
//  Created by Todd Ditchendorf on 3/16/13.
//
//

#import "PKSParserGenVisitor.h"
#import <ParseKit/ParseKit.h>

#import <ParseKit/PKSParser.h>
#import "PKSTokenKindDescriptor.h"
#import "NSString+ParseKitAdditions.h"

#import "MGTemplateEngine.h"
#import "ICUTemplateMatcher.h"

#define CLASS_NAME @"className"
#define MANUAL_MEMORY @"manualMemory"
#define TOKEN_KINDS_START_INDEX @"startIndex"
#define TOKEN_KINDS @"tokenKinds"
#define RULE_METHOD_NAMES @"ruleMethodNames"
#define ENABLE_MEMOIZATION @"enableMemoization"
#define ENABLE_ERROR_RECOVERY @"enableAutomaticErrorRecovery"
#define PARSE_TREE @"parseTree"
#define METHODS @"methods"
#define METHOD_NAME @"methodName"
#define METHOD_BODY @"methodBody"
#define PRE_CALLBACK @"preCallback"
#define POST_CALLBACK @"postCallback"
#define TOKEN_KIND @"tokenKind"
#define CHILD_NAME @"childName"
#define DEPTH @"depth"
#define LAST @"last"
#define LOOKAHEAD_SET @"lookaheadSet"
#define OPT_BODY @"optBody"
#define DISCARD @"discard"
#define NEEDS_BACKTRACK @"needsBacktrack"
#define CHILD_STRING @"childString"
#define TERMINAL_CALL_STRING @"terminalCallString"
#define IF_TEST @"ifTest"
#define ACTION_BODY @"actionBody"
#define PREDICATE_BODY @"predicateBody"
#define PREDICATE @"predicate"
#define PREFIX @"prefix"
#define SUFFIX @"suffix"
#define PATTERN @"pattern"

@interface PKSParserGenVisitor ()
- (void)push:(NSMutableString *)mstr;
- (NSMutableString *)pop;
- (NSArray *)sortedLookaheadSetForNode:(PKBaseNode *)node;
- (NSArray *)sortedArrayFromLookaheadSet:(NSSet *)set;
- (NSSet *)lookaheadSetForNode:(PKBaseNode *)node;

@property (nonatomic, retain) NSMutableArray *outputStringStack;
@property (nonatomic, retain) NSString *currentDefName;
@end

@implementation PKSParserGenVisitor

- (id)init {
    self = [super init];
    if (self) {
        self.enableHybridDFA = YES;
        self.enableMemoization = YES;
        self.preassemblerSettingBehavior = PKParserFactoryAssemblerSettingBehaviorNone;
        self.assemblerSettingBehavior = PKParserFactoryAssemblerSettingBehaviorAll;

        [self setUpTemplateEngine];
    }
    return self;
}

- (void)dealloc {
    self.engine = nil;
    self.interfaceOutputString = nil;
    self.implementationOutputString = nil;
    self.ruleMethodNames = nil;
    self.outputStringStack = nil;
    self.currentDefName = nil;
    [super dealloc];
}

- (NSString *)templateStringNamed:(NSString *)filename {
    NSError *err = nil;
    NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:filename ofType:@"txt"];
    NSString *template = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&err];
    NSAssert([template length], @"");
    if (!template) {
        if (err) NSLog(@"%@", err);
    }
    return template;
}

- (void)setUpTemplateEngine {
    self.engine = [MGTemplateEngine templateEngine];
    _engine.delegate = self;
    _engine.matcher = [ICUTemplateMatcher matcherWithTemplateEngine:_engine];
}

- (void)push:(NSMutableString *)mstr {
    NSParameterAssert([mstr isKindOfClass:[NSMutableString class]]);

    [_outputStringStack addObject:mstr];
}

- (NSMutableString *)pop {
    NSAssert([_outputStringStack count], @"");
    NSMutableString *mstr = [[[_outputStringStack lastObject] retain] autorelease];
    [_outputStringStack removeLastObject];

    NSAssert([mstr isKindOfClass:[NSMutableString class]], @"");
    return mstr;
}

- (NSArray *)sortedLookaheadSetForNode:(PKBaseNode *)node {
    return [self sortedArrayFromLookaheadSet:[self lookaheadSetForNode:node]];
}

- (NSArray *)sortedArrayFromLookaheadSet:(NSSet *)set {
    NSArray *result = [[set allObjects] sortedArrayUsingComparator:^NSComparisonResult(PKSTokenKindDescriptor *desc1, PKSTokenKindDescriptor *desc2) {
        return [desc1.name compare:desc2.name];
    }];

    return result;
}

- (NSSet *)lookaheadSetForNode:(PKBaseNode *)node {
    NSParameterAssert(node);
    NSAssert(self.symbolTable, @"");

    NSMutableSet *set = [NSMutableSet set];

    switch (node.type) {
        case PKNodeTypeConstant: {
            PKConstantNode *constNode = (PKConstantNode *)node;
            [set addObject:constNode.tokenKind];
        } break;
        case PKNodeTypeLiteral: {
            PKLiteralNode *litNode = (PKLiteralNode *)node;
            [set addObject:litNode.tokenKind];
        } break;
        case PKNodeTypeDelimited: {
            PKDelimitedNode *delimNode = (PKDelimitedNode *)node;
            [set addObject:delimNode.tokenKind];
        } break;
        case PKNodeTypeReference: {
            NSString *name = node.token.stringValue;
            PKDefinitionNode *defNode = self.symbolTable[name];
            if (!defNode) {
                NSLog(@"missing rule named: `%@`", name);
            }
            NSAssert1(defNode, @"missing: %@", name);
            [set unionSet:[self lookaheadSetForNode:defNode]];
        } break;
        case PKNodeTypeAlternation: {
            for (PKBaseNode *child in node.children) {
                [set unionSet:[self lookaheadSetForNode:child]];
            }
        } break;
        default: {
            for (PKBaseNode *child in node.children) {
                [set unionSet:[self lookaheadSetForNode:child]];
                break; // single look ahead. to implement full LL(*), this would need to be enhanced here.
            }
        } break;
    }

    return set;
}

- (void)setUpSymbolTableFromRoot:(PKRootNode *)node {

    NSUInteger c = [node.children count];

    NSMutableDictionary *symTab = [NSMutableDictionary dictionaryWithCapacity:c];

    for (PKBaseNode *child in node.children) {
        NSString *key = child.token.stringValue;
        symTab[key] = child;
    }

    self.symbolTable = symTab;
}

#pragma mark -
#pragma mark PKVisitor

- (void)visitRoot:(PKRootNode *)node {
    //NSLog(@"%s %@", __PRETTY_FUNCTION__, node);
    NSParameterAssert(node);

    // setup symbol table
    [self setUpSymbolTableFromRoot:node];

    // setup stack
    self.outputStringStack = [NSMutableArray array];

    self.ruleMethodNames = [NSMutableArray array];

    // add namespace to token kinds
    for (PKSTokenKindDescriptor *desc in node.tokenKinds) {
        NSString *newName = [NSString stringWithFormat:@"%@_%@", [node.grammarName uppercaseString], desc.name];
        desc.name = newName;
    }

    // setup vars
    id vars = [NSMutableDictionary dictionary];
    vars[MANUAL_MEMORY] = @(!_enableARC);
    vars[TOKEN_KINDS_START_INDEX] = @(TOKEN_KIND_BUILTIN_ANY + 1);
    vars[TOKEN_KINDS] = node.tokenKinds;
    NSString *className = node.grammarName;
    if (![className hasSuffix:@"Parser"]) {
        className = [NSString stringWithFormat:@"%@Parser", className];
    }
    vars[CLASS_NAME] = className;

    // do interface (header)
    NSString *intTemplate = [self templateStringNamed:@"PKSClassInterfaceTemplate"];
    self.interfaceOutputString = [_engine processTemplate:intTemplate withVariables:vars];

    // do impl (.m)
    // setup child str buffer
    NSMutableString *childStr = [NSMutableString string];

    // recurse
    for (PKBaseNode *child in node.children) {
        [child visit:self];

        // pop
        [childStr appendString:[self pop]];
    }

    // merge
    vars[METHODS] = childStr;
    vars[RULE_METHOD_NAMES] = self.ruleMethodNames;
    vars[ENABLE_MEMOIZATION] = @(self.enableMemoization);
    vars[ENABLE_ERROR_RECOVERY] = @(self.enableAutomaticErrorRecovery);
    vars[PARSE_TREE] = @((_preassemblerSettingBehavior == PKParserFactoryAssemblerSettingBehaviorSyntax || _assemblerSettingBehavior == PKParserFactoryAssemblerSettingBehaviorSyntax));

    NSString *implTemplate = [self templateStringNamed:@"PKSClassImplementationTemplate"];
    self.implementationOutputString = [_engine processTemplate:implTemplate withVariables:vars];

    //NSLog(@"%@", _interfaceOutputString);
    //NSLog(@"%@", _implementationOutputString);
}

- (NSString *)actionStringFrom:(PKActionNode *)actNode {
    if (!actNode || self.isSpeculating) return @"";

    id vars = @{ACTION_BODY: actNode.source, DEPTH: @(_depth)};
    NSString *result = [_engine processTemplate:[self templateStringNamed:@"PKSActionTemplate"] withVariables:vars];

    return result;
}

- (NSString *)callbackStringForNode:(PKBaseNode *)node methodName:(NSString *)methodName isPre:(BOOL)isPre {
    // determine if we should include an assembler callback call
    BOOL fireCallback = NO;
    BOOL isTerminal = 1 == [node.children count] && [[self concreteNodeForNode:node.children[0]] isTerminal];
    NSString *templateName = isPre ? @"PKSPreCallbackTemplate" : @"PKSPostCallbackTemplate";

    BOOL flag = isPre ? _preassemblerSettingBehavior : _assemblerSettingBehavior;

    switch (flag) {
        case PKParserFactoryAssemblerSettingBehaviorNone:
            fireCallback = NO;
            break;
        case PKParserFactoryAssemblerSettingBehaviorAll:
            fireCallback = YES;
            break;
        case PKParserFactoryAssemblerSettingBehaviorTerminals: {
            fireCallback = isTerminal;
        } break;
        case PKParserFactoryAssemblerSettingBehaviorSyntax: {
            fireCallback = YES;
            if (isTerminal) {
                templateName = isPre ? @"PKSPreCallbackSyntaxLeafTemplate" : @"PKSPostCallbackSyntaxLeafTemplate";
            } else {
                templateName = isPre ? @"PKSPreCallbackSyntaxInteriorTemplate" : @"PKSPostCallbackSyntaxInteriorTemplate";
            }
        } break;
        default:
            NSAssert1(0, @"unsupported assembler callback setting behavior %d", _preassemblerSettingBehavior);
            break;
    }

    NSString *result = @"";

    if (fireCallback) {
        id vars = @{METHOD_NAME: methodName};
        result = [_engine processTemplate:[self templateStringNamed:templateName] withVariables:vars];
    }

    return result;
}

- (void)visitDefinition:(PKDefinitionNode *)node {
    //NSLog(@"%s %@", __PRETTY_FUNCTION__, node);

    self.depth = 1; // 1 for the try/catch wrapper

    // setup vars
    id vars = [NSMutableDictionary dictionary];
    NSString *methodName = node.token.stringValue;

    BOOL isStartMethod = [methodName isEqualToString:@"@start"];
    if (isStartMethod) {
        methodName = @"_start";
    } else {
        [self.ruleMethodNames addObject:methodName];
    }
    vars[METHOD_NAME] = methodName;
    self.currentDefName = methodName;

    // setup child str buffer
    NSMutableString *childStr = [NSMutableString string];

    [childStr appendString:[self actionStringFrom:node.actionNode]];

    if (isStartMethod && _enableAutomaticErrorRecovery) self.depth++;

    // recurse
    for (PKBaseNode *child in node.children) {
        [child visit:self];

        // pop
        [childStr appendString:[self pop]];
    }

    if (isStartMethod && _enableAutomaticErrorRecovery) self.depth--;

    if (isStartMethod) {
        NSInteger depth = _depth + (_enableAutomaticErrorRecovery ? 1 : 0);
        id eofVars = @{DEPTH: @(depth)};
        NSString *eofCallStr = [_engine processTemplate:[self templateStringNamed:@"PKSEOFCallTemplate"] withVariables:eofVars];
        [childStr appendString:eofCallStr];

        if (_enableAutomaticErrorRecovery) {
            id resyncVars = @{DEPTH: @(_depth), CHILD_STRING: childStr};
            NSString *newChildStr = [_engine processTemplate:[self templateStringNamed:@"PKSTryAndRecoverEOFTemplate"] withVariables:resyncVars];
            [childStr setString:newChildStr];
        }
    }

    if (node.before) {
        [childStr insertString:[self actionStringFrom:node.before] atIndex:0];
    }

    if (node.after) {
        [childStr appendString:[self actionStringFrom:node.after]];
    }

    // merge
    vars[METHOD_BODY] = childStr;

    NSString *preCallbackStr = @"";
    NSString *postCallbackStr = @"";

    if (!isStartMethod) {
        preCallbackStr = [self callbackStringForNode:node methodName:methodName isPre:YES];
        postCallbackStr = [self callbackStringForNode:node methodName:methodName isPre:NO];
    }

    vars[PRE_CALLBACK] = preCallbackStr;
    vars[POST_CALLBACK] = postCallbackStr;

    NSString *templateName = nil;
    if (!isStartMethod && self.enableMemoization) {
        templateName = @"PKSMethodMemoizationTemplate";
    } else {
        templateName = @"PKSMethodTemplate";
    }

    NSString *template = [self templateStringNamed:templateName];
    NSMutableString *output = [NSMutableString stringWithString:[_engine processTemplate:template withVariables:vars]];

    // push
    [self push:output];
}

- (void)visitReference:(PKReferenceNode *)node {
    //NSLog(@"%s %@", __PRETTY_FUNCTION__, node);

    // stup vars
    id vars = [NSMutableDictionary dictionary];
    NSString *methodName = node.token.stringValue;
    vars[METHOD_NAME] = methodName;
    vars[DEPTH] = @(_depth);
    vars[DISCARD] = @(node.discard);

    // merge
    NSMutableString *output = [NSMutableString string];
    [output appendString:[self semanticPredicateForNode:node throws:YES]];

    NSString *template = [self templateStringNamed:@"PKSMethodCallTemplate"];
    [output appendString:[_engine processTemplate:template withVariables:vars]];

    [output appendString:[self actionStringFrom:node.actionNode]];

    // push
    [self push:output];
}

- (void)visitComposite:(PKCompositeNode *)node {
    //NSLog(@"%s %@", __PRETTY_FUNCTION__, node);

    NSAssert(1 == [node.token.stringValue length], @"");
    PKUniChar c = [node.token.stringValue characterAtIndex:0];
    switch (c) {
        case '*':
            [self visitRepetition:node];
            break;
        case '~':
            [self visitNegation:node];
            break;
        default:
            NSAssert2(0, @"%s must be implemented in %@", __PRETTY_FUNCTION__, [self class]);
            break;
    }
}

- (void)visitNegation:(PKCompositeNode *)node {

    // recurse
    NSAssert(1 == [node.children count], @"");
    PKBaseNode *child = node.children[0];

    NSArray *set = [self sortedLookaheadSetForNode:child];

    self.depth++;
    [child visit:self];
    self.depth--;

    // pop
    NSMutableString *childStr = [self pop];

    // setup vars
    id vars = [NSMutableDictionary dictionary];
    vars[DEPTH] = @(_depth);
    vars[METHOD_NAME] = self.currentDefName;
    vars[LOOKAHEAD_SET] = set;
    vars[LAST] = @([set count] - 1);
    vars[IF_TEST] = [self removeTabsAndNewLines:childStr];

    // TODO Predicates???

    NSMutableString *output = [NSMutableString string];
    [output appendString:[self semanticPredicateForNode:node throws:YES]];

    NSString *templateName = nil;
    if (_enableHybridDFA && [self isLL1:child]) { // ????
        templateName = @"PKSNegationPredictTemplate";
    } else {
        templateName = @"PKSNegationSpeculateTemplate";
    }

    [output appendString:[_engine processTemplate:[self templateStringNamed:templateName] withVariables:vars]];

    // action
    [output appendString:[self actionStringFrom:node.actionNode]];

    // push
    [self push:output];
}

// TODO make mutable
- (NSMutableString *)removeTabsAndNewLines:(NSMutableString *)inStr {
    [inStr replaceOccurrencesOfString:@"\n" withString:@"" options:0 range:NSMakeRange(0, [inStr length])];
    [inStr replaceOccurrencesOfString:@"    " withString:@"" options:0 range:NSMakeRange(0, [inStr length])];
    return inStr;
}

- (void)visitRepetition:(PKCompositeNode *)node {
    // setup vars
    id vars = [NSMutableDictionary dictionary];
    vars[DEPTH] = @(_depth);

    NSAssert(1 == [node.children count], @"");
    PKBaseNode *child = node.children[0];

    NSArray *set = [self sortedLookaheadSetForNode:child];

    // setup template
    vars[LOOKAHEAD_SET] = set;
    vars[LAST] = @([set count] - 1);

    // Only need to speculate if this repetition's child is non-terminal
    BOOL isLL1 = (_enableHybridDFA && [self isLL1:child]);

    // rep body is always wrapped in an while AND an IF. so increase depth twice
    NSInteger depth = isLL1 ? 1 : 2;

    // recurse first and get entire child str
    self.depth += depth;

    // visit for speculative if test
    self.isSpeculating = YES;
    [child visit:self];
    self.isSpeculating = NO;
    NSString *ifTest = [self removeTabsAndNewLines:[self pop]];

    // visit for child body
    [child visit:self];

    self.depth -= depth;

    // pop
    NSMutableString *childStr = [self pop];
    vars[CHILD_STRING] = [[childStr copy] autorelease];

    NSString *templateName = nil;
    if (isLL1) { // ????
        templateName = @"PKSRepetitionPredictTemplate";
    } else {
        vars[IF_TEST] = ifTest;
        templateName = @"PKSRepetitionSpeculateTemplate";
    }

    // repetition
    NSMutableString *output = [NSMutableString string];
    [output appendString:[self semanticPredicateForNode:node throws:YES]];

    [output appendString:[_engine processTemplate:[self templateStringNamed:templateName] withVariables:vars]];

    // action
    [output appendString:[self actionStringFrom:node.actionNode]];

    // push
    [self push:output];

}

- (void)visitCollection:(PKCollectionNode *)node {
    //NSLog(@"%s %@", __PRETTY_FUNCTION__, node);

    NSAssert(1 == [node.token.stringValue length], @"");
    PKUniChar c = [node.token.stringValue characterAtIndex:0];
    switch (c) {
        case '.':
            [self visitSequence:node];
            break;
        default:
            NSAssert2(0, @"%s must be implemented in %@", __PRETTY_FUNCTION__, [self class]);
            break;
    }
}

- (void)visitSequence:(PKCollectionNode *)node {
    //NSLog(@"%s %@", __PRETTY_FUNCTION__, node);

    // setup vars
    id vars = [NSMutableDictionary dictionary];
    vars[DEPTH] = @(_depth);

    // setup child str buffer
    NSMutableString *childStr = [NSMutableString string];
    [childStr appendString:[self semanticPredicateForNode:node throws:YES]];

    NSMutableString *partialChildStr = [NSMutableString string];
    NSUInteger partialCount = 0;

    BOOL hasTerminal = NO;

    NSMutableArray *concreteChildren = [NSMutableArray arrayWithCapacity:[node.children count]];
    for (PKBaseNode *child in node.children) {
        PKBaseNode *concreteNode = [self concreteNodeForNode:child];
        if (concreteNode.isTerminal && [concreteChildren count]) hasTerminal = YES;
        [concreteChildren addObject:concreteNode];
    }

    // recurse
    BOOL depthIncreased = NO;
    NSUInteger i = 0;
    for (PKBaseNode *child in node.children) {
        PKBaseNode *concreteNode = concreteChildren[i++];

        if (_enableAutomaticErrorRecovery && hasTerminal && partialCount == 1) {
            [childStr appendString:partialChildStr];
            [partialChildStr setString:@""];
            depthIncreased = YES;
            self.depth++;
        }

        [child visit:self];

        // pop
        NSString *terminalCallStr = [self pop];
        [partialChildStr appendString:terminalCallStr];

        if (_enableAutomaticErrorRecovery && concreteNode.isTerminal && partialCount > 0) {

            PKSTokenKindDescriptor *desc = [(PKConstantNode *)concreteNode tokenKind];
            id resyncVars = @{TOKEN_KIND: desc, DEPTH: @(_depth - 1), CHILD_STRING: partialChildStr, TERMINAL_CALL_STRING: terminalCallStr};
            NSString *tryAndResyncStr = [_engine processTemplate:[self templateStringNamed:@"PKSTryAndRecoverTemplate"] withVariables:resyncVars];

            [childStr appendString:tryAndResyncStr];

            // reset
            partialCount = 0;
            [partialChildStr setString:@""];
            if (depthIncreased) {
                self.depth--;
                depthIncreased = NO;
            }
        } else {
            NSAssert([partialChildStr length], @"");
            ++partialCount;
        }
    }

    //if (_enableAutomaticErrorRecovery && [node.children count] > 1) self.depth--;

    [childStr appendString:partialChildStr];

    [childStr appendString:[self actionStringFrom:node.actionNode]];

    // push
    [self push:childStr];

}

- (NSString *)semanticPredicateForNode:(PKBaseNode *)node throws:(BOOL)throws {
    NSString *result = @"";

    if (node.semanticPredicateNode) {
        NSString *predBody = [node.semanticPredicateNode.source stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
        NSAssert([predBody length], @"");
        BOOL isStat = [predBody rangeOfString:@";"].length > 0;

        NSString *templateName = nil;
        if (throws) {
            templateName = isStat ? @"PKSSemanticPredicateTestAndThrowStatTemplate" : @"PKSSemanticPredicateTestAndThrowExprTemplate";
        } else {
            templateName = isStat ? @"PKSSemanticPredicateTestStatTemplate" : @"PKSSemanticPredicateTestExprTemplate";
        }

        result = [_engine processTemplate:[self templateStringNamed:templateName] withVariables:@{PREDICATE_BODY: predBody, DEPTH: @(self.depth)}];
        NSAssert(result, @"");
    }

    return result;
}

- (BOOL)isEmptyNode:(PKBaseNode *)node {
    return [node.token.stringValue isEqualToString:@"Empty"];
}

- (NSMutableString *)recurseAlt:(PKAlternationNode *)node la:(NSMutableArray *)lookaheadSets {
    // setup child str buffer
    NSMutableString *result = [NSMutableString string];

    // recurse
    NSUInteger idx = 0;
    for (PKBaseNode *child in node.children) {
        if ([self isEmptyNode:child]) {
            node.hasEmptyAlternative = YES;
            ++idx;
            continue;
        }

        id vars = [NSMutableDictionary dictionary];

        NSArray *set = [self sortedArrayFromLookaheadSet:lookaheadSets[idx]];
        vars[LOOKAHEAD_SET] = set;
        vars[LAST] = @([set count] - 1);
        vars[DEPTH] = @(_depth);
        vars[NEEDS_BACKTRACK] = @(_needsBacktracking);

        // process template. cannot test `idx` here to determine `if` vs `else` due to possible Empty child borking `idx`
        NSString *templateName = [result length] ? @"PKSPredictElseIfTemplate" : @"PKSPredictIfTemplate";
        NSString *output = [_engine processTemplate:[self templateStringNamed:templateName] withVariables:vars];
        [result appendString:output];

        self.depth++;
        [child visit:self];
        self.depth--;

        // pop
        [result appendString:[self pop]];

        ++idx;
    }

    return result;
}

- (NSMutableString *)recurseAltForBracktracking:(PKAlternationNode *)node {
    // setup child str buffer
    NSMutableString *result = [NSMutableString string];

    // recurse
    NSUInteger idx = 0;
    for (PKBaseNode *child in node.children) {
        if ([self isEmptyNode:child]) {
            node.hasEmptyAlternative = YES;
            ++idx;
            continue;
        }

        // recurse first and get entire child str
        self.depth++;

        // visit for speculative if test
        self.isSpeculating = YES;
        [child visit:self];
        self.isSpeculating = NO;
        NSString *ifTest = [self removeTabsAndNewLines:[self pop]];

        // visit for child body
        [child visit:self];
        NSString *childBody = [self pop];
        self.depth--;

        // setup vars
        id vars = [NSMutableDictionary dictionary];
        vars[DEPTH] = @(_depth);
        vars[NEEDS_BACKTRACK] = @(_needsBacktracking);
        vars[CHILD_STRING] = ifTest;

        // process template. cannot test `idx` here to determine `if` vs `else` due to possible Empty child borking `idx`
        NSString *templateName = [result length] ? @"PKSSpeculateElseIfTemplate" : @"PKSSpeculateIfTemplate";
        NSString *output = [_engine processTemplate:[self templateStringNamed:templateName] withVariables:vars];

        [result appendString:output];
        [result appendString:childBody];

        ++idx;
    }

    return result;
}

- (void)visitAlternation:(PKAlternationNode *)node {
    //NSLog(@"%s %@", __PRETTY_FUNCTION__, node);

    NSMutableString *childStr = nil;

    if (_enableHybridDFA) {
        // first fetch all child lookahead sets
        NSMutableArray *lookaheadSets = [NSMutableArray arrayWithCapacity:[node.children count]];

        for (PKBaseNode *child in node.children) {
            NSSet *set = [self lookaheadSetForNode:child];
            [lookaheadSets addObject:set];
        }

        NSMutableSet *all = [NSMutableSet setWithSet:lookaheadSets[0]];
        BOOL overlap = NO;
        for (NSUInteger i = 1; i < [lookaheadSets count]; ++i) {
            NSSet *set = lookaheadSets[i];
            overlap = [set intersectsSet:all];
            if (overlap) break;
            [all unionSet:set];
        }

        if (!overlap && [all containsObject:@(TOKEN_KIND_BUILTIN_DELIMITEDSTRING)]) {
            overlap = YES; // TODO ??
        }

        //NSLog(@"%@", lookaheadSets);
        self.needsBacktracking = overlap;

        if (_needsBacktracking) {
            childStr = [self recurseAltForBracktracking:node];
        } else {
            childStr = [self recurseAlt:node la:lookaheadSets];
        }
        self.needsBacktracking = NO;

    } else {
        self.needsBacktracking = YES;
        childStr = [self recurseAltForBracktracking:node];
    }

    id vars = [NSMutableDictionary dictionary];
    vars[METHOD_NAME] = _currentDefName;
    vars[DEPTH] = @(_depth);

    NSString *elseStr = nil;
    if (node.hasEmptyAlternative) {
        elseStr = [_engine processTemplate:[self templateStringNamed:@"PKSPredictEndIfTemplate"] withVariables:vars];
    } else {
        elseStr = [_engine processTemplate:[self templateStringNamed:@"PKSPredictElseTemplate"] withVariables:vars];
    }
    [childStr appendString:elseStr];

    // push
    [self push:childStr];
}

- (void)visitOptional:(PKOptionalNode *)node {
    //NSLog(@"%s %@", __PRETTY_FUNCTION__, node);

    // recurse
    NSAssert(1 == [node.children count], @"");
    PKBaseNode *child = node.children[0];

    NSArray *set = [self sortedLookaheadSetForNode:child];

    BOOL isLL1 = _enableHybridDFA && [self isLL1:child];

    // recurse for speculation
    self.depth++;
    self.isSpeculating = YES;
    [child visit:self];
    self.isSpeculating = NO;

    NSMutableString *ifTest = [self removeTabsAndNewLines:[self pop]];

    // recurse for realz
    [child visit:self];
    self.depth--;

    // pop
    NSMutableString *childStr = [self pop];

    // setup vars
    id vars = [NSMutableDictionary dictionary];
    vars[DEPTH] = @(_depth);
    vars[LOOKAHEAD_SET] = set;
    vars[LAST] = @([set count] - 1);
    vars[CHILD_STRING] = childStr;
    vars[IF_TEST] = ifTest;

    NSMutableString *output = [NSMutableString string];
    [output appendString:[self semanticPredicateForNode:node throws:YES]];

    NSString *templateName = nil;
    if (isLL1) { // ????
        templateName = @"PKSOptionalPredictTemplate";
    } else {
        templateName = @"PKSOptionalSpeculateTemplate";
    }

    [output appendString:[_engine processTemplate:[self templateStringNamed:templateName] withVariables:vars]];

    // action
    [output appendString:[self actionStringFrom:node.actionNode]];

    // push
    [self push:output];
}

// if inNode is a #ref or $def, resolve to actual concrete node.
- (PKBaseNode *)concreteNodeForNode:(PKBaseNode *)inNode {
    PKBaseNode *node = inNode;
    while ([node isKindOfClass:[PKReferenceNode class]] || [node isKindOfClass:[PKDefinitionNode class]]) {
        while ([node isKindOfClass:[PKReferenceNode class]]) {
            node = self.symbolTable[node.token.stringValue];
        }

        if ([node isKindOfClass:[PKDefinitionNode class]]) {
            NSAssert(1 == [node.children count], @"");
            node = node.children[0];
        }
    }
    return node;
}

- (BOOL)isLL1:(PKBaseNode *)inNode {
    BOOL result = YES;

    PKBaseNode *node = [self concreteNodeForNode:inNode];

    if ([node isKindOfClass:[PKAlternationNode class]]) {
        for (PKBaseNode *child in node.children) {
            if (![self isLL1:child]) {
                result = NO;
                break;
            }
        }
    } else {
        result = node.isTerminal;
    }

    return result;
}

- (void)visitMultiple:(PKMultipleNode *)node {
    //NSLog(@"%s %@", __PRETTY_FUNCTION__, node);

    // recurse
    NSAssert(1 == [node.children count], @"");
    PKBaseNode *child = node.children[0];

    NSArray *set = [self sortedLookaheadSetForNode:child];

    BOOL isLL1 = _enableHybridDFA && [self isLL1:child];

    // recurse for speculation
    self.depth++;
    self.isSpeculating = YES;
    [child visit:self];
    self.isSpeculating = NO;

    NSMutableString *ifTest = [self removeTabsAndNewLines:[self pop]];

    // recurse for realz
    [child visit:self];
    self.depth--;

    // pop
    NSMutableString *childStr = [self pop];

    // setup vars
    id vars = [NSMutableDictionary dictionary];
    vars[DEPTH] = @(_depth);
    vars[LOOKAHEAD_SET] = set;
    vars[LAST] = @([set count] - 1);
    vars[CHILD_STRING] = childStr;
    vars[IF_TEST] = ifTest;

    NSMutableString *output = [NSMutableString string];
    [output appendString:[self semanticPredicateForNode:node throws:YES]];

    NSString *templateName = nil;
    if (isLL1) { // ????
        templateName = @"PKSMultiplePredictTemplate";
    } else {
        templateName = @"PKSMultipleSpeculateTemplate";
    }

    [output appendString:[_engine processTemplate:[self templateStringNamed:templateName] withVariables:vars]];

    // action
    [output appendString:[self actionStringFrom:node.actionNode]];

    // push
    [self push:output];
}

- (void)visitConstant:(PKConstantNode *)node {
    //NSLog(@"%s %@", __PRETTY_FUNCTION__, node);

    // stup vars
    id vars = [NSMutableDictionary dictionary];
    NSString *methodName = node.token.stringValue;
    vars[METHOD_NAME] = methodName;
    vars[DEPTH] = @(_depth);
    vars[DISCARD] = @(node.discard);

    // merge
    NSMutableString *output = [NSMutableString string];
    [output appendString:[self semanticPredicateForNode:node throws:YES]];

    NSString *template = [self templateStringNamed:@"PKSConstantMethodCallTemplate"];
    [output appendString:[_engine processTemplate:template withVariables:vars]];

    [output appendString:[self actionStringFrom:node.actionNode]];

    // push
    [self push:output];
}

- (void)visitLiteral:(PKLiteralNode *)node {
    //NSLog(@"%s %@", __PRETTY_FUNCTION__, node);

    // stup vars
    id vars = [NSMutableDictionary dictionary];
    vars[TOKEN_KIND] = node.tokenKind;
    vars[DEPTH] = @(_depth);
    vars[DISCARD] = @(node.discard);

    // merge
    NSMutableString *output = [NSMutableString string];
    [output appendString:[self semanticPredicateForNode:node throws:YES]];

    NSString *template = [self templateStringNamed:@"PKSMatchCallTemplate"];
    [output appendString:[_engine processTemplate:template withVariables:vars]];

    [output appendString:[self actionStringFrom:node.actionNode]];

    // push
    [self push:output];
}

- (void)visitDelimited:(PKDelimitedNode *)node {
    //NSLog(@"%s %@", __PRETTY_FUNCTION__, node);

    // stup vars
    id vars = [NSMutableDictionary dictionary];
    vars[TOKEN_KIND] = node.tokenKind;
    vars[DEPTH] = @(_depth);
    vars[DISCARD] = @(node.discard);
    vars[PREFIX] = node.startMarker;
    vars[SUFFIX] = node.endMarker;
    vars[METHOD_NAME] = self.currentDefName;

    // merge
    NSMutableString *output = [NSMutableString string];
    [output appendString:[self semanticPredicateForNode:node throws:YES]];

    NSString *template = [self templateStringNamed:@"PKSMatchDelimitedStringTemplate"];
    [output appendString:[_engine processTemplate:template withVariables:vars]];

    [output appendString:[self actionStringFrom:node.actionNode]];

    // push
    [self push:output];
}

- (void)visitPattern:(PKPatternNode *)node {
    //NSLog(@"%s %@", __PRETTY_FUNCTION__, node);

    // stup vars
    id vars = [NSMutableDictionary dictionary];
    //vars[TOKEN_KIND] = node.tokenKind;
    vars[DEPTH] = @(_depth);
    vars[DISCARD] = @(node.discard);
    vars[PATTERN] = [NSRegularExpression escapedPatternForString:node.string];
    vars[METHOD_NAME] = self.currentDefName;

    // merge
    NSMutableString *output = [NSMutableString string];
    [output appendString:[self semanticPredicateForNode:node throws:YES]];

    NSString *template = [self templateStringNamed:@"PKSMatchPatternTemplate"];
    [output appendString:[_engine processTemplate:template withVariables:vars]];

    [output appendString:[self actionStringFrom:node.actionNode]];

    // push
    [self push:output];
}

- (void)visitAction:(PKActionNode *)node {
    //NSLog(@"%s %@", __PRETTY_FUNCTION__, node);

    NSAssert2(0, @"%s must be implemented in %@", __PRETTY_FUNCTION__, [self class]);
}

#pragma mark -
#pragma mark MGTemplateEngineDelegate

- (void)templateEngine:(MGTemplateEngine *)engine blockStarted:(NSDictionary *)blockInfo {

}

- (void)templateEngine:(MGTemplateEngine *)engine blockEnded:(NSDictionary *)blockInfo {

}

- (void)templateEngineFinishedProcessingTemplate:(MGTemplateEngine *)engine {

}

- (void)templateEngine:(MGTemplateEngine *)engine encounteredError:(NSError *)error isContinuing:(BOOL)continuing {
    NSLog(@"%@", error);
}

@end


package mss

import "github.com/omniscale/magnacarto/color"

var attributeTypes map[string]isValid

type isValid func(interface{}) bool

func isNumber(val interface{}) bool {
	_, ok := val.(float64)
	return ok
}

func isNumbers(val interface{}) bool {
	vals, ok := val.([]Value)
	if !ok {
		return false
	}
	for _, v := range vals {
		if !isNumber(v) {
			return false
		}
	}
	return true
}

func isString(val interface{}) bool {
	_, ok := val.(string)
	return ok
}

func isStrings(val interface{}) bool {
	vals, ok := val.([]Value)
	if !ok {
		return false
	}
	for _, v := range vals {
		if !isString(v) {
			return false
		}
	}
	return true
}

func isStringOrStrings(val interface{}) bool {
	return isString(val) || isStrings(val)
}

func isFieldOr(other isValid) isValid {
	return func(val interface{}) bool {
		if s, ok := val.(string); ok {
			if len(s) > 2 && s[0] == '[' && s[len(s)-1] == ']' {
				return true
			}
			return false
		}
		return other(val)
	}
}

func isColor(val interface{}) bool {
	_, ok := val.(color.Color)
	return ok
}

func isBool(val interface{}) bool {
	_, ok := val.(bool)
	return ok
}

func isKeyword(keywords ...string) isValid {
	return func(val interface{}) bool {
		k, ok := val.(string)
		if !ok {
			return false
		}
		for _, expected := range keywords {
			if k == expected {
				return true
			}
		}
		return false
	}
}

func isKeywordOr(other isValid, keywords ...string) isValid {
	return func(val interface{}) bool {
		if other(val) {
			return true
		}
		return isKeyword(keywords...)(val)
	}
}

func isStops(val interface{}) bool {
	vals, ok := val.([]Value)
	if !ok {
		return false
	}
	for _, v := range vals {
		if _, ok := v.(Stop); !ok {
			return false
		}
	}
	return true
}

func isCompOp(val interface{}) bool {
	return isKeyword(
		"clear",
		"src",
		"dst",
		"src-over",
		"dst-over",
		"src-in",
		"dst-in",
		"src-out",
		"dst-out",
		"src-atop",
		"dst-atop",
		"xor",
		"plus",
		"minus",
		"multiply",
		"divide",
		"screen",
		"overlay",
		"darken",
		"lighten",
		"color-dodge",
		"color-burn",
		"hard-light",
		"soft-light",
		"difference",
		"exclusion",
		"contrast",
		"invert",
		"invert-rgb",
		"grain-merge",
		"grain-extract",
		"hue",
		"saturation",
		"color",
		"value",
	)(val)
}

func isScaling(val interface{}) bool {
	return isKeyword(
		"near",
		"fast",
		"bilinear",
		"bicubic",
		"spline16",
		"spline36",
		"hanning",
		"hamming",
		"hermite",
		"kaiser",
		"quadric",
		"catrom",
		"gaussian",
		"bessel",
		"mitchell",
		"sinc",
		"lanczos",
		"blackman",
	)(val)
}

func isSimplifyAlgorithm(val interface{}) bool {
	return isKeyword(
		"radial-distance",
		"zhao-saalfeld",
		"visvalingam-whyatt",
	)(val)
}

func isRasterizer(val interface{}) bool {
	return isKeyword(
		"full",
		"fast",
	)(val)
}

func isVerticalAlignment(val interface{}) bool {
	return isKeyword(
		"top",
		"middle",
		"bottom",
		"auto",
	)(val)
}

func isJustifyAlignment(val interface{}) bool {
	return isKeyword(
		"left",
		"center",
		"right",
		"auto",
	)(val)
}

func init() {
	attributeTypes = map[string]isValid{
		"background-color": isColor,

		"building-fill":         isColor,
		"building-fill-opacity": isNumber,
		"building-height":       isNumber,

		"dot-fill":    isColor,
		"dot-opacity": isNumber,
		"dot-width":   isNumber,
		"dot-height":  isNumber,
		"dot-comp-op": isCompOp,

		"line-cap":                isKeyword("round", "butt", "square"),
		"line-clip":               isBool,
		"line-color":              isColor,
		"line-dasharray":          isNumbers,
		"line-dash-offset":        isNumbers,
		"line-gamma":              isNumber,
		"line-gamma-method":       isKeyword("power", "linear", "none", "threshold", "multiply"),
		"line-join":               isKeyword("miter", "miter-revert", "round", "bevel"),
		"line-miterlimit":         isNumber,
		"line-offset":             isNumber,
		"line-opacity":            isNumber,
		"line-rasterizer":         isRasterizer,
		"line-simplify":           isNumber,
		"line-simplify-algorithm": isSimplifyAlgorithm,
		"line-smooth":             isNumber,
		"line-width":              isNumber,
		"line-comp-op":            isCompOp,
		"line-geometry-transform": isString,

		"line-pattern-file":               isString,
		"line-pattern-clip":               isBool,
		"line-pattern-opacity":            isNumber,
		"line-pattern-simplify":           isNumber,
		"line-pattern-simplify-algorithm": isSimplifyAlgorithm,
		"line-pattern-smooth":             isNumber,
		"line-pattern-offset":             isNumber,
		"line-pattern-geometry-transform": isString,
		"line-pattern-comp-op":            isCompOp,

		"marker-allow-overlap":      isBool,
		"marker-file":               isString,
		"marker-fill":               isColor,
		"marker-fill-opacity":       isNumber,
		"marker-height":             isNumber,
		"marker-line-color":         isColor,
		"marker-line-width":         isNumber,
		"marker-line-opacity":       isNumber,
		"marker-opacity":            isNumber,
		"marker-placement":          isKeyword("point", "interior", "line", "vertex-first", "vertex-last"),
		"marker-spacing":            isNumber,
		"marker-transform":          isString,
		"marker-type":               isKeyword("arrow", "ellipse"),
		"marker-width":              isNumber,
		"marker-multi-policy":       isKeyword("each", "whole", "largest"),
		"marker-avoid-edges":        isBool,
		"marker-ignore-placement":   isBool,
		"marker-max-error":          isNumber,
		"marker-clip":               isBool,
		"marker-simplify":           isNumber,
		"marker-simplify-algorithm": isSimplifyAlgorithm,
		"marker-smooth":             isNumber,
		"marker-geometry-transform": isString,
		"marker-offset":             isNumber,
		"marker-comp-op":            isCompOp,
		"marker-direction":          isKeyword("auto", "auto-down", "left", "right", "left-only", "right-only", "up", "down"),

		"point-file":             isString,
		"point-allow-overlap":    isBool,
		"point-opacity":          isNumber,
		"point-transform":        isString,
		"point-ignore-placement": isBool,
		"point-placement":        isKeyword("centroid", "interior"),
		"point-comp-op":          isCompOp,

		"polygon-fill":               isColor,
		"polygon-gamma":              isNumber,
		"polygon-gamma-method":       isKeyword("power", "linear", "none", "threshold", "multiply"),
		"polygon-opacity":            isNumber,
		"polygon-clip":               isBool,
		"polygon-simplify":           isNumber,
		"polygon-simplify-algorithm": isSimplifyAlgorithm,
		"polygon-smooth":             isNumber,
		"polygon-geometry-transform": isString,
		"polygon-comp-op":            isCompOp,

		"polygon-pattern-alignment":          isKeyword("global", "local"),
		"polygon-pattern-file":               isString,
		"polygon-pattern-gamma":              isNumber,
		"polygon-pattern-opacity":            isNumber,
		"polygon-pattern-clip":               isBool,
		"polygon-pattern-simplify":           isNumber,
		"polygon-pattern-simplify-algorithm": isSimplifyAlgorithm,
		"polygon-pattern-smooth":             isNumber,
		"polygon-pattern-geometry-transform": isString,
		"polygon-pattern-comp-op":            isCompOp,

		"shield-allow-overlap":            isBool,
		"shield-avoid-edges":              isBool,
		"shield-character-spacing":        isNumber,
		"shield-clip":                     isBool,
		"shield-dx":                       isNumber,
		"shield-dy":                       isNumber,
		"shield-face-name":                isStringOrStrings,
		"shield-file":                     isString,
		"shield-fill":                     isColor,
		"shield-halo-fill":                isColor,
		"shield-halo-radius":              isNumber,
		"shield-halo-rasterizer":          isRasterizer,
		"shield-halo-transform":           isString,
		"shield-halo-comp-op":             isCompOp,
		"shield-halo-opacity":             isNumber,
		"shield-line-spacing":             isNumber,
		"shield-min-distance":             isNumber,
		"shield-min-padding":              isNumber,
		"shield-name":                     isString,
		"shield-opacity":                  isNumber,
		"shield-placement":                isKeyword("line", "point", "vertex", "interior"),
		"shield-placement-type":           isKeyword("dummy", "simple", "list"),
		"shield-placements":               isString,
		"shield-transform":                isString,
		"shield-simplify":                 isNumber,
		"shield-simplify-algorithm":       isSimplifyAlgorithm,
		"shield-smooth":                   isNumber,
		"shield-comp-op":                  isCompOp,
		"shield-size":                     isNumber,
		"shield-spacing":                  isNumber,
		"shield-text-dx":                  isNumber,
		"shield-text-dy":                  isNumber,
		"shield-text-opacity":             isNumber,
		"shield-text-transform":           isKeyword("none", "uppercase", "lowercase", "capitalize", "reverse"),
		"shield-wrap-before":              isBool,
		"shield-wrap-character":           isString,
		"shield-wrap-width":               isNumber,
		"shield-unlock-image":             isBool,
		"shield-margin":                   isNumber,
		"shield-repeat-distance":          isNumber,
		"shield-label-position-tolerance": isNumber,
		"shield-horizontal-alignment":     isKeyword("left", "middle", "right", "auto"),
		"shield-vertical-alignment":       isVerticalAlignment,
		"shield-justify-alignment":        isJustifyAlignment,

		"text-allow-overlap":            isBool,
		"text-avoid-edges":              isBool,
		"text-character-spacing":        isNumber,
		"text-clip":                     isBool,
		"text-dx":                       isNumber,
		"text-dy":                       isNumber,
		"text-face-name":                isStringOrStrings,
		"text-font-feature-settings":    isString,
		"text-fill":                     isColor,
		"text-halo-fill":                isColor,
		"text-halo-radius":              isNumber,
		"text-halo-opacity":             isNumber,
		"text-halo-rasterizer":          isRasterizer,
		"text-halo-transform":           isString,
		"text-halo-comp-op":             isCompOp,
		"text-line-spacing":             isNumber,
		"text-min-distance":             isNumber,
		"text-min-padding":              isNumber,
		"text-name":                     isString,
		"text-opacity":                  isNumber,
		"text-orientation":              isFieldOr(isNumber),
		"text-placement":                isKeyword("line", "point", "vertex", "interior"),
		"text-placement-type":           isKeyword("dummy", "simple", "list"),
		"text-placements":               isString,
		"text-size":                     isNumber,
		"text-spacing":                  isNumber,
		"text-transform":                isKeyword("none", "uppercase", "lowercase", "capitalize", "reverse"),
		"text-wrap-before":              isBool,
		"text-wrap-character":           isString,
		"text-wrap-width":               isNumber,
		"text-repeat-wrap-characater":   isBool,
		"text-ratio":                    isNumber,
		"text-label-position-tolerance": isNumber,
		"text-max-char-angle-delta":     isNumber,
		"text-vertical-alignment":       isVerticalAlignment,
		"text-horizontal-alignment":     isKeyword("left", "middle", "right", "auto", "adjust"),
		"text-justify-alignment":        isJustifyAlignment,
		"text-margin":                   isNumber,
		"text-repeat-distance":          isNumber,
		"text-min-path-length":          isKeywordOr(isNumber, "auto"),
		"text-rotate-displacement":      isBool,
		"text-upgright":                 isKeyword("auto", "auto-down", "left", "right", "left-only", "right-only"),
		"text-simplify":                 isNumber,
		"text-simplify-algorithm":       isSimplifyAlgorithm,
		"text-smooth":                   isNumber,
		"text-comp-op":                  isCompOp,
		"text-largest-bbox-only":        isBool,

		"raster-opacity":                 isNumber,
		"raster-scaling":                 isScaling,
		"raster-colorizer-default-mode":  isKeyword("discrete", "linear", "exact"),
		"raster-colorizer-default-color": isColor,
		"raster-colorizer-stops":         isStops,
		"raster-comp-op":                 isCompOp,
		"raster-filter-factor":           isNumber,
		"raster-mesh-size":               isNumber,
		"raster-colorizer-epsilon":       isNumber,
	}
}

// validProperty returns whether the property and the value is valid.
func validProperty(property string, value interface{}) (bool, bool) {
	checkFunc, ok := attributeTypes[property]
	if !ok {
		return false, false
	}
	return true, checkFunc(value)
}


<!DOCTYPE html>

	<meta charset="UTF-8">
	<title>Multiple Sorting - jQuery EasyUI Demo</title>
	<link rel="stylesheet" type="text/css" href="../../themes/default/easyui.css">
	<link rel="stylesheet" type="text/css" href="../../themes/icon.css">
	<link rel="stylesheet" type="text/css" href="../demo.css">

Multiple Sorting

Set 'multiSort' property to true to enable multiple column sorting.

				Item ID
				Product
				List Price
				Unit Cost
				Attribute
				Status


#ifndef OPENMM_MONTECARLOMEMBRANEBAROSTATIMPL_H_
#define OPENMM_MONTECARLOMEMBRANEBAROSTATIMPL_H_

/* -------------------------------------------------------------------------- *
 *                                   OpenMM                                   *
 * -------------------------------------------------------------------------- *
 * This is part of the OpenMM molecular simulation toolkit originating from   *
 * Simbios, the NIH National Center for Physics-Based Simulation of           *
 * Biological Structures at Stanford, funded under the NIH Roadmap for        *
 * Medical Research, grant U54 GM072970. See https://simtk.org.               *
 *                                                                            *
 * Portions copyright (c) 2010-2019 Stanford University and the Authors.      *
 * Authors: Peter Eastman                                                     *
 * Contributors:                                                              *
 *                                                                            *
 * Permission is hereby granted, free of charge, to any person obtaining a    *
 * copy of this software and associated documentation files (the "Software"), *
 * to deal in the Software without restriction, including without limitation  *
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,   *
 * and/or sell copies of the Software, and to permit persons to whom the      *
 * Software is furnished to do so, subject to the following conditions:       *
 *                                                                            *
 * The above copyright notice and this permission notice shall be included in *
 * all copies or substantial portions of the Software.                        *
 *                                                                            *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   *
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    *
 * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,    *
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR      *
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE  *
 * USE OR OTHER DEALINGS IN THE SOFTWARE.                                     *
 * -------------------------------------------------------------------------- */

#include "ForceImpl.h"
#include "openmm/MonteCarloMembraneBarostat.h"
#include "openmm/Kernel.h"
#include <string>

namespace OpenMM {

/**
 * This is the internal implementation of MonteCarloMembraneBarostat.
 */

class MonteCarloMembraneBarostatImpl : public ForceImpl {
public:
    MonteCarloMembraneBarostatImpl(const MonteCarloMembraneBarostat& owner);
    void initialize(ContextImpl& context);
    const MonteCarloMembraneBarostat& getOwner() const {
        return owner;
    }
    void updateContextState(ContextImpl& context);
    double calcForcesAndEnergy(ContextImpl& context, bool includeForces, bool includeEnergy, int groups) {
        // This force doesn't apply forces to particles.
        return 0.0;
    }
    std::map<std::string, double> getDefaultParameters();
    std::vector<std::string> getKernelNames();
private:
    const MonteCarloMembraneBarostat& owner;
    int step, numAttempted[3], numAccepted[3];
    double volumeScale[3];
    Kernel kernel;
};

} // namespace OpenMM

#endif /*OPENMM_MONTECARLOMEMBRANEBAROSTATIMPL_H_*/