/* Copyright 2023 Manuel Jinger
 * Copyright 2023 Dual Tachyon
 * https://github.com/DualTachyon
 *
 * 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.
 */

#include "bsp/dp32g030/gpio.h"
#include "driver/gpio.h"
#include "driver/keyboard.h"
#include "driver/systick.h"
#include "driver/i2c.h"
#include "misc.h"

KEY_Code_t gKeyReading0     = KEY_INVALID;
KEY_Code_t gKeyReading1     = KEY_INVALID;
uint16_t   gDebounceCounter = 0;
bool       gWasFKeyPressed  = false;

static const struct {

	// Using a 16 bit pre-calculated shift and invert is cheaper
	// than using 8 bit and doing shift and invert in code.
	uint16_t set_to_zero_mask;

	// We are very fortunate.
	// The key and pin defines fit together in a single u8, making this very efficient
	struct {
		KEY_Code_t key : 5;
		uint8_t    pin : 3; // Pin 6 is highest
	} pins[4];

} keyboard[] = {

	{	// Zero row
		// Set to zero to handle special case of nothing pulled down
		.set_to_zero_mask = 0xffff,
		.pins = {
			{ .key = KEY_SIDE1,   .pin = GPIOA_PIN_KEYBOARD_0},
			{ .key = KEY_SIDE2,   .pin = GPIOA_PIN_KEYBOARD_1},

			// Duplicate to fill the array with valid values
			{ .key = KEY_INVALID, .pin = GPIOA_PIN_KEYBOARD_1},
			{ .key = KEY_INVALID, .pin = GPIOA_PIN_KEYBOARD_1}
		}
	},
	{	// First row
		.set_to_zero_mask = ~(1u << GPIOA_PIN_KEYBOARD_4) & 0xffff,
		.pins = {
			{ .key = KEY_MENU,  .pin = GPIOA_PIN_KEYBOARD_0},
			{ .key = KEY_1,     .pin = GPIOA_PIN_KEYBOARD_1},
			{ .key = KEY_4,     .pin = GPIOA_PIN_KEYBOARD_2},
			{ .key = KEY_7,     .pin = GPIOA_PIN_KEYBOARD_3}
		}
	},
	{	// Second row
		.set_to_zero_mask = ~(1u << GPIOA_PIN_KEYBOARD_5) & 0xffff,
		.pins = {
			{ .key = KEY_UP,    .pin = GPIOA_PIN_KEYBOARD_0},
			{ .key = KEY_2 ,    .pin = GPIOA_PIN_KEYBOARD_1},
			{ .key = KEY_5 ,    .pin = GPIOA_PIN_KEYBOARD_2},
			{ .key = KEY_8 ,    .pin = GPIOA_PIN_KEYBOARD_3}
		}
	},
	{	// Third row
		.set_to_zero_mask = ~(1u << GPIOA_PIN_KEYBOARD_6) & 0xffff,
		.pins = {
			{ .key = KEY_DOWN,  .pin = GPIOA_PIN_KEYBOARD_0},
			{ .key = KEY_3   ,  .pin = GPIOA_PIN_KEYBOARD_1},
			{ .key = KEY_6   ,  .pin = GPIOA_PIN_KEYBOARD_2},
			{ .key = KEY_9   ,  .pin = GPIOA_PIN_KEYBOARD_3}
		}
	},
	{	// Fourth row
		.set_to_zero_mask = ~(1u << GPIOA_PIN_KEYBOARD_7) & 0xffff,
		.pins = {
			{ .key = KEY_EXIT,  .pin = GPIOA_PIN_KEYBOARD_0},
			{ .key = KEY_STAR,  .pin = GPIOA_PIN_KEYBOARD_1},
			{ .key = KEY_0   ,  .pin = GPIOA_PIN_KEYBOARD_2},
			{ .key = KEY_F   ,  .pin = GPIOA_PIN_KEYBOARD_3}
		}
	}
};

KEY_Code_t KEYBOARD_Poll(void)
{
	KEY_Code_t Key = KEY_INVALID;

//	if (!GPIO_CheckBit(&GPIOC->DATA, GPIOC_PIN_PTT))
//		return KEY_PTT;

	// *****************

	for (unsigned int j = 0; j < ARRAY_SIZE(keyboard); j++)
	{
		uint16_t reg;
		unsigned int i;
		unsigned int k;

		// Set all high
		GPIOA->DATA |=  1u << GPIOA_PIN_KEYBOARD_4 |
						1u << GPIOA_PIN_KEYBOARD_5 |
						1u << GPIOA_PIN_KEYBOARD_6 |
						1u << GPIOA_PIN_KEYBOARD_7;

		// Clear the pin we are selecting
		GPIOA->DATA &= keyboard[j].set_to_zero_mask;

		// Read all 4 GPIO pins at once .. with de-noise, max of 8 sample loops
		for (i = 0, k = 0, reg = 0; i < 3 && k < 8; i++, k++)
		{
			uint16_t reg2;

			SYSTICK_DelayUs(1);

			reg2 = GPIOA->DATA;
			if (reg != reg2)
			{	// noise
				reg = reg2;
				i   = 0;
			}
		}
		if (i < 3)
			break;	// noise is too bad

		for (unsigned int i = 0; i < ARRAY_SIZE(keyboard[j].pins); i++)
		{
			const uint16_t mask = 1u << keyboard[j].pins[i].pin;
			if (!(reg & mask))
			{
				Key = keyboard[j].pins[i].key;
				break;
			}
		}

		if (Key != KEY_INVALID)
			break;
	}

	// Create I2C stop condition since we might have toggled I2C pins
	// This leaves GPIOA_PIN_KEYBOARD_4 and GPIOA_PIN_KEYBOARD_5 high
	I2C_Stop();

	// Reset VOICE pins
	GPIO_ClearBit(&GPIOA->DATA, GPIOA_PIN_KEYBOARD_6);
	GPIO_SetBit(  &GPIOA->DATA, GPIOA_PIN_KEYBOARD_7);

	return Key;
}