#include <Arduino.h>
#include <MIDI_Controller.h>

#define NOTE_ON_CMD 0x90
#define NOTE_OFF_CMD 0x80
#define NOTE_VELOCITY 127

#define s * 1000

/* Pins */
/* GPIO (I2C) */
const uint8_t gpio_int{0};
const uint8_t sda{2};
const uint8_t scl{3};
/* Multiplexer */
#define MUX_ADDRSIZE 5
const uint8_t mux_sig{A10};
const uint8_t mux_addr[MUX_ADDRSIZE]{18, 19, 20, 21, 15};
/* ShiftRegister (OUT) */
#define SHIFT_OUTPUTSIZE 16
const uint8_t shift_data{14};
const uint8_t shift_latch{16};
const uint8_t shift_clock{1};
/* keyboard (IN) */
#define KEYBOARD_INSIZE 6
const uint8_t keyboard_in[KEYBOARD_INSIZE]{4, 5, 6, 7, 8, 9};

/* Components */
/* GPIO (I2C) */
/* Multiplexer */
AnalogMultiplex multiplexer(mux_sig, mux_addr);
/* ShiftRegister (OUT) */
ShiftRegisterOut shiftRegister(shift_data, shift_clock, shift_latch, MSBFIRST, SHIFT_OUTPUTSIZE);
/* keyboard (IN) */

/* Midi links */
// USBDebugMIDI_Interface midiInterface(115200);

Analog potentiometers[] = {
	{multiplexer.pin(0), MIDI_CC::Channel_Volume, 1},
	{multiplexer.pin(1), MIDI_CC::Channel_Volume, 2},
	{multiplexer.pin(2), MIDI_CC::Channel_Volume, 3},
	{multiplexer.pin(3), MIDI_CC::Channel_Volume, 4},
	{multiplexer.pin(4), MIDI_CC::Channel_Volume, 5},
	{multiplexer.pin(5), MIDI_CC::Channel_Volume, 6},
	{multiplexer.pin(6), MIDI_CC::Channel_Volume, 7},
	{multiplexer.pin(7), MIDI_CC::Channel_Volume, 8},
	{multiplexer.pin(8), MIDI_CC::Channel_Volume, 9},
	{multiplexer.pin(9), MIDI_CC::Channel_Volume, 10}
};

byte matrix[16][6][2] = {
/*00*/	{{0,  0}, {0,  0}, {0,  0}, {0,  0}, {0,  0}, {0,  0}},
/*01*/	{{1,  0}, {1,  1}, {1,  2}, {1,  3}, {1,  4}, {1,  5}},
/*02*/	{{1,  6}, {1,  7}, {1,  8}, {1,  9}, {1, 10}, {1, 11}},
/*03*/	{{1, 18}, {1, 19}, {1, 20}, {1, 21}, {1, 22}, {1, 23}},
/*04*/	{{1, 47}, {0,  0}, {0,  0}, {0,  0}, {0,  0}, {0,  0}},
/*05*/	{{1, 41}, {1, 42}, {1, 43}, {1, 44}, {1, 45}, {1, 46}},
/*06*/	{{1, 36}, {1, 37}, {1, 38}, {1, 39}, {1, 40}, {1, 40}},
/*07*/	{{1, 30}, {1, 31}, {1, 32}, {1, 33}, {1, 34}, {1, 35}},
/*08*/	{{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
/*09*/	{{1, 24}, {1, 25}, {1, 26}, {1, 27}, {1, 28}, {1, 29}},
/*10*/	{{1, 12}, {1, 13}, {1, 14}, {1, 15}, {1, 16}, {1, 17}},
/*11*/	{{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
/*12*/	{{2, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
/*13*/	{{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
/*14*/	{{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
/*15*/	{{2, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
};

bool ref[6*16];

unsigned char shift = 0;

void setup() {
	// Serial.begin(31250);
	// while (Serial.available() == 0){
	// 	delay(.5 s);
	// }

	// Serial.print("Serial OK");

	for (size_t i = 0; i < KEYBOARD_INSIZE; ++i) {
		ExtIO::pinMode(keyboard_in[i], INPUT);
	}

	for (size_t i = 0; i < 6*16; ++i) {
		ref[i] = false;
	}
	// ExtIO::pinMode(shift_data, OUTPUT);
	// ExtIO::pinMode(shift_latch, OUTPUT);
	// ExtIO::pinMode(shift_clock, OUTPUT);
	shiftRegister.begin();
}

void scanPin(int pin) {
	ExtIO::digitalWrite(shift_latch, LOW);
	if (pin < 8) {
		byte bitsToSend = 0b00000001 << pin;
		ExtIO::shiftOut(shift_data, shift_clock, MSBFIRST, 0b00000000);
		ExtIO::shiftOut(shift_data, shift_clock, MSBFIRST, bitsToSend);
	} else {
		byte bitsToSend = 0b00000001 << (pin - 8);
		ExtIO::shiftOut(shift_data, shift_clock, MSBFIRST, bitsToSend);
		ExtIO::shiftOut(shift_data, shift_clock, MSBFIRST, 0b00000000);
	}
	ExtIO::digitalWrite(shift_latch, HIGH);
}


void init_shift() {
	ExtIO::digitalWrite(shift_latch, LOW);
	ExtIO::shiftOut(shift_data, shift_clock, MSBFIRST, 0b00000000);
	ExtIO::shiftOut(shift_data, shift_clock, MSBFIRST, 0b00000001);
	ExtIO::digitalWrite(shift_latch, HIGH);
}

void next_shift() {
	ExtIO::digitalWrite(shift_data, LOW);
	ExtIO::digitalWrite(shift_latch, LOW);
	ExtIO::digitalWrite(shift_clock, HIGH);
	ExtIO::digitalWrite(shift_clock, LOW);
	ExtIO::digitalWrite(shift_latch, HIGH);
}

void noteOn(byte note);

void noteOff(byte note);

void loop() {
	init_shift();
	for (size_t i = 0; i < 16; ++i) {
		// shiftRegister.digitalWrite(i, HIGH);
		uint8_t rowValue[KEYBOARD_INSIZE];
		bool pressed = false;
		for (size_t j = 0; j < 6; ++j) {
			if ((rowValue[i] = ExtIO::digitalRead(keyboard_in[j])) == HIGH) {
				if (!ref[j * 16 + i]) {
					byte type = matrix[i][j][0];
					byte key = matrix[i][j][1];
					if (type == 1) {
						noteOn(key);
					} else if (type == 2) {
						if (key == 0 && shift >= 12) {
							shift -= 12;
						} else if (key == 1 && shift <= 0xFF - 47 - 12) {
							shift += 12;
						}
					}
					ref[j * 16 + i] = true;
				}
			} else {
				if (ref[j * 16 + i]) {
					if (matrix[i][j][0] == 1) {
						noteOff(matrix[i][j][1]);
					}
					ref[j * 16 + i] = false;
				}
			}
		}
		next_shift();
		// shiftRegister.digitalWrite(i, LOW);
		// delay(3 s);
	}

	// for (uint8_t i = 0; i < 8; ++i) {
	// 	byte bitsToSend = 0b00000001 << i;
	// 	ExtIO::digitalWrite(shift_latch, LOW);
	// 	// ExtIO::shiftOut(shift_data, shift_clock, MSBFIRST, bitsToSend);
	// 	// ExtIO::shiftOut(shift_data, shift_clock, MSBFIRST, 0b00000000);
	// 	ExtIO::shiftOut(shift_data, shift_clock, MSBFIRST, 0b11111111);
	// 	ExtIO::shiftOut(shift_data, shift_clock, MSBFIRST, 0b11111111);
	// 	ExtIO::digitalWrite(shift_latch, HIGH);
	// 	Serial.print(" ");
	// 	Serial.print(bitsToSend, BIN);
	// 	Serial.println(" HIGH");
	// 	delay(4 s);
	// 	//bitWrite(bitsToSend, i, LOW);
	// 	ExtIO::digitalWrite(shift_latch, LOW);
	// 	ExtIO::shiftOut(shift_data, shift_clock, MSBFIRST, 0b00000000);
	// 	ExtIO::shiftOut(shift_data, shift_clock, MSBFIRST, 0b00000000);
	// 	ExtIO::digitalWrite(shift_latch, HIGH);
	// 	Serial.print(i);
	// 	Serial.println(" LOW");
	// 	delay(4 s);
	// }

	// analogWrite(9, val[0] * 256 / 1024);



	MIDI_Controller.refresh();
	// lastLoop = millis() - start;
	// loopAverage = (loopAverage + lastLoop) / 2;
	//delay(.5 s);
}


void noteOn(byte note) {
	MIDI_Controller.MIDI()->send(NOTE_ON_CMD, 0, note + shift, NOTE_VELOCITY);
	// Serial.write(NOTE_ON_CMD);
	// Serial.write(note + shift);
	// Serial.write(NOTE_VELOCITY);
}

void noteOff(byte note) {
	MIDI_Controller.MIDI()->send(NOTE_OFF_CMD, 0, note + shift, NOTE_VELOCITY);
	// Serial.write(NOTE_OFF_CMD);
	// Serial.write(note + shift);
	// Serial.write(NOTE_VELOCITY);
}
