#ifdef HALF_FLASH
const static int calib_sector = -1;
#else
const static int calib_sector = 255;
#endif
const static uint64_t MAGIC = 0xf00dcafe473ff02a; // bump on change
bool flash_writecalib(int which);
int flash_readcalib(void) {
#ifdef EMU
	openflash();
#endif
#if defined EMU // WASM?
#ifndef CALIB_TEST
	if (1) {
		// fake calib results
		for (int i = 0; i < 18; ++i) {
			for (int j = 0; j < 8; ++j)
				calibresults[i].pressure[j] = 8000,
				calibresults[i].pos[j] = (512 * j - 1792) * (((i % 9) == 8) ? -1 : 1);
		}
		return 3;
	}
#endif
#endif
	volatile uint64_t *flash = (volatile uint64_t*) (FLASH_ADDR_256 + calib_sector * 2048);
	int ver = 0, ok=0;
	if (flash[0] == MAGIC && flash[255] == ~MAGIC) ver = 2;
	if (ver==0) {
		DebugLog("no calibration found in flash\r\n");
		return 0;
	}
	volatile uint64_t *s = flash + 1;
	if(*s!=~(uint64_t)(0)) {
		ok|=1;
		memcpy(calibresults, (uint64_t*) s, sizeof(calibresults));
	}
	s += sizeof(calibresults) / 8;
	if (*s!=~(uint64_t)(0)) {
		ok|=2;
		memcpy(cvcalib, (int64_t*)s, sizeof(cvcalib));
	}
	s += sizeof(cvcalib) / 8;
	return ok;
}

bool flash_writecalib(int which) {
	HAL_FLASH_Unlock();
	int rv = flash_erase_page(calib_sector);
	if (rv == 0) {
		uint64_t* flash = (uint64_t*)(FLASH_ADDR_256 + calib_sector * 2048);
		HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, (uint32_t)(size_t)flash, MAGIC);
		uint64_t* d = flash + 1;
		if (which&1)
			flash_program_block(d, calibresults, sizeof(calibresults));
		d+=(sizeof(calibresults)+7)/8;
		if (which&2)
			flash_program_block(d, cvcalib, sizeof(cvcalib));
		d+=(sizeof(cvcalib)+7)/8;
		HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, (uint32_t)(size_t)(flash + 255), ~MAGIC);
	}
	HAL_FLASH_Lock();
	return 0;
}

extern s8 enable_audio;

static inline float deadzone(float f, float zone) {
	if (f<zone && f>-zone) return 0.f;
	if (f > 0.f) f -= zone; else f += zone;
	return f;// *f* (1.f / 2048.f);
}

#ifdef EMU
static inline int getgatesense(void) {
	return emugatesense;
}
static inline int getpitchsense(void) {
	return emupitchsense;
}
#else
static inline int getgatesense(void) {
	return HAL_GPIO_ReadPin(SENSE1_GPIO_Port, SENSE1_Pin) == GPIO_PIN_RESET;
}
static inline int getpitchsense(void) {
	return HAL_GPIO_ReadPin(SENSE2_GPIO_Port, SENSE2_Pin) == GPIO_PIN_RESET;
}
#endif

void cv_calib(void) {
#ifdef WASM
	return;
#endif
	enable_audio = EA_OFF;
	clear();
	int topscroll = 128;
	const char* topline = "unplug all inputs"
#ifndef NEW_LAYOUT
		" except plug gate out to gate in"
#endif
		". use left 4 columns to adjust pitch cv outputs. plug pitch lo output to pitch input when done.";
	int toplinew = strwidth(F_16, topline);
	const char* const botlines[5] = { "plug gate out->in", "Pitch lo=0v/C0", "Pitch lo=2v/C2", "Pitch hi=0v/C0", "Pitch hi=2v/C2" };
	u8 ff = finger_frame_ui;
	float adcavgs[6][2];
	for (int i = 0; i < 6; ++i)
		adcavgs[i][0] = -1.f;
	int curx = -1;
	float downpos[4], downval[4];
	float cvout[4] = {
		cvcalib[8].bias, cvcalib[8].bias + cvcalib[8].scale * 2048.f * 24.f,
		cvcalib[9].bias, cvcalib[9].bias + cvcalib[9].scale * 2048.f * 24.f,
	};
	SetOutputCVPitchLo((int)cvout[0], false);
	SetOutputCVPitchHi((int)cvout[2], false);
	u8 curlo = 0;
	u8 curhi = 2;
	bool prevprevpitchsense=true;
	bool prevpitchsense=true;
	while (1) {
		clear();
		drawstr_noright(topscroll, 0, F_16, topline);
		bool gateok = getgatesense();
#ifdef NEW_LAYOUT
		gateok = !gateok; // in new layout, theres a bleed resistor so no need for gate - in fact, we dont want it
#endif
		drawstr(0, 18, F_12_BOLD, (curx<0 && gateok) ? "pitch out>in when done" : botlines[curx+1]);
		if (curx >= 0)
			fdrawstr(-128, 24, F_8, "(%d)", (int)cvout[curx]);
		oled_flip(vrambuf);
		topscroll-=2;
		if (topscroll < -toplinew)
			topscroll = 128;
		while (finger_frame_ui == ff); // wait for new touch data
		ff = finger_frame_ui;
		int pitchsense = getpitchsense();
		if (pitchsense && !prevprevpitchsense && gateok)
			break;
		prevprevpitchsense=prevpitchsense;
		prevpitchsense=pitchsense;

		// calibrate the 0 point for the inputs
		for (int i = 0; i < 6; ++i) {
			int tot = 0;
			for (int j = 0; j < ADC_SAMPLES; ++j)
				tot += adcbuf[j * ADC_CHANS + i];
			tot /= ADC_SAMPLES;
			if (adcavgs[i][0] < 0)
				adcavgs[i][0] = adcavgs[i][1] = tot;
			adcavgs[i][0] += (tot - adcavgs[i][0]) * 0.05f;
			adcavgs[i][1] += (adcavgs[i][0] - adcavgs[i][1]) * 0.05f;
		}
		for (int x = 0; x < 4; ++x) {
			Finger* f = touch_ui_getlatest(x);
			Finger* pf = touch_ui_getprev(x);
			if (f->pressure >= 500) {
				if (pf->pressure < 500) {
					downpos[x] = f->pos;
					downval[x] = cvout[x];
					curx = x;
				}
				float delta = deadzone(f->pos - downpos[x], 64.f);
//				delta=(delta*delta)>>12;
				cvout[x] += (clampf(downval[x] + delta*0.25f, 0.f, 65535.f) - cvout[x])*0.1f;
				if (x < 2)
					curlo = x;
				else
					curhi = x;
			}
		}
		SetOutputCVPitchLo((int)cvout[curlo], false);
		SetOutputCVPitchHi((int)cvout[curhi], false);
		AdvanceCVOut();

		// update the leds, innit
		for (int fi = 0; fi < 9; ++fi) {
			for (int y = 0; y < 8; ++y) {
				int k = (fi<4)?(triangle(y*64-(int)cvout[fi]/4)/4):128;
				led_ram[fi][y] = led_gamma(((fi==curx)?255:128)-k);
			}
		}

	}
	// zero is now nicely set
	for (int i = 0; i < 6; ++i) {
		cvcalib[i].bias = adcavgs[i][1];
		cvcalib[i].scale = 0.2f / -6548.1f;
		DebugLog("adc zero point %d - %d\r\n", i, (int)adcavgs[i][1]);
	}
	cvcalib[6].bias=32000.f; // hw seems to skew towards 0 slightly...
	cvcalib[6].scale = 1.05f / -32768.f;
	cvcalib[7].bias=32000.f;
	cvcalib[7].scale= 1.05f / -32768.f;

	// output calib is now nicely set
	cvcalib[8].bias = cvout[0];
	cvcalib[8].scale = (cvout[1] - cvout[0]) * 1.f / (2048.f*24.f);
	cvcalib[9].bias = cvout[2];
	cvcalib[9].scale = (cvout[3] - cvout[2]) * 1.f / (2048.f * 24.f);
	for (int i = 0; i < 4; ++i)
		DebugLog("selected dac value %d - %d\r\n", i, (int)cvout[i]);
	DebugLog("dac pitch lo zero point %d, step*1000 %d\r\n", (int)cvcalib[8].bias, (int)(cvcalib[8].scale*1000.f));
	DebugLog("dac pitch hi zero point %d, step*1000 %d\r\n", (int)cvcalib[9].bias, (int)(cvcalib[9].scale * 1000.f));

	// use it to calibrate 

	clear();
	drawstr(0, 4, F_12, "waiting for pitch\nloopback cable");
	oled_flip(vrambuf);
	HAL_Delay(1000);
	// wait for them to plug the other end in
	while (1) {
		int tots[2] = { 0 };
		for (int hilo = 0; hilo < 2; ++hilo) {
			SetOutputCVPitchLo((int)cvout[hilo], false);
			SetOutputCVPitchHi((int)cvout[hilo + 2], false);
			HAL_Delay(50);
			int tot = 0;
			for (int j = 0; j < ADC_SAMPLES; ++j)
				tot += adcbuf[j * ADC_CHANS + ADC_PITCH];
			tot /= ADC_SAMPLES;
			tots[hilo] = tot;
		}
		if (abs(tots[0]- tots[1]) > 5000)
			break;
	}
	clear();
	drawstr(0,4,F_24_BOLD, "just a mo...");
	oled_flip(vrambuf);
	HAL_Delay(1000);
	for (int hilo = 0; hilo < 2; ++hilo) {
		SetOutputCVPitchLo((int)cvout[hilo], false);
		SetOutputCVPitchHi((int)cvout[hilo+2], false);
		HAL_Delay(50);
		int tot = 0;
		for (int iter = 0; iter < 256; ++iter) {
			HAL_Delay(2);
			for (int j = 0; j < ADC_SAMPLES; ++j)
				tot += adcbuf[j * ADC_CHANS + ADC_PITCH];
		}
		tot /= ADC_SAMPLES * 256;
		DebugLog("pitch adc for hilo=%d is %d\r\n", hilo, tot);
		if (hilo == 0)
			cvcalib[ADC_PITCH].bias = tot;
		else
			cvcalib[ADC_PITCH].scale = 2.f / (minf(-0.00001f,tot - cvcalib[ADC_PITCH].bias));
	}
	clear();
	drawstr(0, 0, F_16_BOLD, "Done!");
	drawstr(0, 16, F_12_BOLD, "Unplug pitch cable!");
	oled_flip(vrambuf);
	while (getpitchsense()) {
		HAL_Delay(1);
	}
}

void reflash(void);
extern volatile u8 gotclkin;

void led_test(void) {
	enable_audio = EA_PASSTHRU;
	for (int y = 0; y < 9; ++y) for (int x = 0; x < 8; ++x)
		led_ram[y][x] = 255;
	u16 tri = 128;
	int encoder_down_count = -1;
	while (1) {
		clear();
		if (encbtn) {
			if (encoder_down_count >= 0)
				encoder_down_count++;
		}
		else {
			if (encoder_down_count > 2) {
				HAL_Delay(20);
				return;
			}
			encoder_down_count = 0;
		}
		if (encoder_down_count > 100)
			reflash();
		fdrawstr(0, 2, F_12, "TEST %d %d %d %d %02x", adcbuf[0] / 256, adcbuf[1] / 256, adcbuf[2] / 256, adcbuf[3] / 256, gotclkin);
		fdrawstr(0, 18, F_12, "%d %d %d %d %d %d", adcbuf[4] / 256, adcbuf[5] / 256, adcbuf[6] / 256, adcbuf[7] / 256, encval >> 2, encbtn);
		oled_flip(vrambuf);
		HAL_Delay(20);
		for (int srcidx = 0; srcidx < 9; ++srcidx) {
			int a = finger_cap(srcidx * 2);
			int b = finger_cap(srcidx * 2 + 1);
			int amin = finger_mincap(srcidx * 2);
			int bmin = finger_mincap(srcidx * 2 + 1);
			int rawpressure = finger_rawpressure(a - amin, b - bmin);
			int rawpos = finger_rawpos(a, b);
			if (rawpressure > 300) {
				DebugLog("f %d - a=%4d b=%4d amin=%4d bmin=%4d pos=%4d pr=%4d   \r\n", srcidx, a, b, amin, bmin, rawpos, rawpressure);
			}
		}
		tri += 256;
		SetOutputCVTrigger((tri < 16384) ? 65535 : 0);
		SetOutputCVClk((tri < (16384 + 32768)) ? 65535 : 0);
		SetOutputCVPressure(tri);
		SetOutputCVGate((tri * 2) & 65535);
		SetOutputCVPitchLo(tri, false);
		SetOutputCVPitchHi((tri * 2) & 65535, false);
	}
}

void calib(void) {
again:
#ifdef WASM
	return;
#endif



	enable_audio=EA_OFF;
	touch_reset_calib();
	
	HAL_Delay(20);
	CalibProgress* state = GetCalibProgress(0);
	memset(state, 0, sizeof(CalibProgress) * 18);
	int prevrawpressure[18] = { 0 };
	s8 curstep[9] = { 7,7,7,7,7,7,7,7,7 };
	int done = false;
	u8 ff = finger_frame_ui;
	u8 refreshscreen = 0;
	char helptext[64] = "slowly/evenly press lit pads.\ntake care, be accurate!";
	bool blink = false;
	int errors = 0;
	while (!done) {

		if (!refreshscreen) {
			refreshscreen = 16;
			blink = !blink;
			clear();
			fdrawstr(0, 0, F_16, "Calibration%c",blink?'!':' ');
			drawstr(0, 16, F_8, helptext);
			if (errors)
				invertrectangle(0, 0, 128, 32);
			oled_flip(vrambuf);
		}
		else {
			refreshscreen--;
		}

		if (encbtn) {
			led_test();
			goto again;
		}


		while (finger_frame_ui == ff); // wait for new touch data
		ff = finger_frame_ui;
		// update the 18 calibration entries for the current step
		done = 0;
		int readymask=0;
		for (int si = 0; si < 18; ++si) {
			int a = finger_cap(si*2);
			int b = finger_cap(si*2 + 1);
			int amin = finger_mincap(si * 2);
			int bmin = finger_mincap(si * 2 + 1);
			int amax = finger_maxcap(si * 2);
			int bmax = finger_maxcap(si * 2 + 1);
			int rawpressure = finger_rawpressure(a-amin,b-bmin);
			int prevrawp = prevrawpressure[si];
			int rawpos = finger_rawpos(a,b);
			int step = curstep[si % 9];

			int pressureband = rawpressure / 20;
			if (step >=0 && step<8 && rawpressure > 1200 && rawpressure > prevrawp - pressureband/2 && rawpressure < prevrawp + pressureband) {
				// pressure is quite stable
				float w = (rawpressure - 1200.f) / 1000.f;
				float change = abs(prevrawp - rawpressure)*(1.f/250.f);
				w *= maxf(0.f,1.f - change);
				if(w>1.f) w=1.f;
				w *= w;
				const static float LEAK = 0.90f;
				state[si].weight[step] *= LEAK;
				state[si].pos[step] *= LEAK;
				state[si].pressure[step] *= LEAK;
				state[si].weight[step] += w;
				state[si].pos[step] += rawpos * w;
				state[si].pressure[step] += rawpressure * w;

				if (0) if (si<9) DebugLog("finger %d step %d pos %4d %4d pressure %5d %5d weight %3d %d      \r\n", si, step,
								(int)(state[si].pos[step] / state[si].weight[step]),
								(int)(state[si + 9].pos[step] / state[si + 9].weight[step]),
								(int)(state[si].pressure[step] / state[si].weight[step]),
								(int)(state[si + 9].pressure[step] / state[si + 9].weight[step]),
										(int)state[si].weight[step], (int)state[si+9].weight[step]
							);
			}
			int ti = si + 9;
			bool ready=si < 9 && step<8 && step>=0 && state[si].weight[step]>4.f && state[ti].weight[step]>4.f;
			if (ready) {
				if (rawpressure < 900) {
					// move on!
					calibresults[si].pressure[step] = state[si].pressure[step] / state[si].weight[step];
					calibresults[si].pos[step] = state[si].pos[step] / state[si].weight[step];
					calibresults[ti].pressure[step] = state[ti].pressure[step] / state[ti].weight[step];
					calibresults[ti].pos[step] = state[ti].pos[step] / state[ti].weight[step];
					if (step <= 4) {
						errors &= ~(1 << si);
						if (amax - amin < 1000) {
							snprintf(helptext, sizeof(helptext), "!pad %d upper not conn\ncheck soldering", si + 1);
							errors |= (1 << si);
						}
						else if (bmax - bmin < 1000) {
							snprintf(helptext, sizeof(helptext), "!pad %d lower not conn\ncheck soldering", si + 1);
							errors |= (1 << si);
						}
						else if (abs(calibresults[si].pos[step] - calibresults[si].pos[7]) < 300) {
							snprintf(helptext, sizeof(helptext), "!pad %d shorted?\ncheck soldering", si + 1);
							errors |= (1 << si);
						}
					}
					DebugLog("\n");
					curstep[si]--;
				}
				else
					readymask |= 1 << si; // flash the next finger if we want them to move on
			}
			if (step < 0)
				done++;
			prevrawpressure[si] = rawpressure;
		}
		if (done < 18)
			done = 0;
		int flash=triangle(millis());
		for (int fi = 0; fi < 9; ++fi) {
			int ready = readymask & (1 << fi);
			bool err = (errors & (1 << fi));
			for (int x = 0; x < 8; ++x) {
				int k = 0;
				if (x == curstep[fi])
					k = ready ? flash : 255 - state[fi].weight[x] * 12.f;
				if (err) 
					k = maxi(k, flash / 2);
				led_ram[fi][x] = led_gamma(k);
			}
		}
	} // calibration loop
}
