//
//  Keyboard.cpp
//  Clock Signal
//
//  Created by Thomas Harte on 13/02/2021.
//  Copyright © 2021 Thomas Harte. All rights reserved.
//

#include "Keyboard.hpp"

using namespace Apple::ADB;

Keyboard::Keyboard(Bus &bus) : ReactiveDevice(bus, 2) {}

void Keyboard::perform_command(const Command &command) {
	switch(command.type) {
		case Command::Type::Reset:
			modifiers_ = 0xffff;
			[[fallthrough]];
		case Command::Type::Flush: {
			std::lock_guard lock_guard(keys_mutex_);
			pending_events_.clear();
		} break;

		case Command::Type::Talk:
			switch(command.reg) {
				case 0: {
					// Post up to two key events, or nothing if there are
					// no events pending.
					std::lock_guard lock_guard(keys_mutex_);

					if(!pending_events_.empty()) {
						if(pending_events_.size() > 1) {
							post_response({pending_events_[0], pending_events_[1]});
							pending_events_.erase(pending_events_.begin(), pending_events_.begin()+2);
						} else {
							// Two bytes are required; provide a key up of the fictional
							// key zero as the second.
							// That's arbitrary; verify with real machines.
							post_response({pending_events_[0], 0x80});
							pending_events_.clear();
						}
					}
				} break;

				case 2: {
					std::lock_guard lock_guard(keys_mutex_);
					post_response({uint8_t(modifiers_ >> 8), uint8_t(modifiers_)});
				} break;

				default: break;
			}
		break;

		case Command::Type::Listen:
			// If a listen is incoming for register 2, prepare to capture LED statuses.
			if(command.reg == 2) {
				receive_bytes(2);
			}
		break;


		default: break;
	}
}

void Keyboard::did_receive_data(const Command &, const std::vector<uint8_t> &data) {
	// This must be a register 2 listen; update the LED statuses.
	// TODO: and possibly display these.
	modifiers_ = (modifiers_ & 0xfff8) | (data[1] & 7);
}


bool Keyboard::set_key_pressed(Key key, bool is_pressed) {
	// ADB keyboard events: low 7 bits are a key code; bit 7 is either 0 for pressed or 1 for released.
	std::lock_guard lock_guard(keys_mutex_);
	pending_events_.push_back(uint8_t(key) | (is_pressed ? 0x00 : 0x80));
	pressed_keys_[size_t(key)] = is_pressed;

	// Track modifier state also.

	/*
		In all cases below: 0 = pressed/on; 1 = released/off.

		b15:	None (reserved)
		b14:	Delete
		b13:	Caps lock
		b12:	Reset
		b11:	Control
		b10:	Shift
		b9:		Option
		b8:		Command

		-- From here onwards, available only on the extended keyboard.

		b7:		Num lock/clear
		b6:		Scroll lock
		b5–3:	None (reserved)
		b2:		Scroll Lock LED
		b1:		Caps Lock LED
		b0:		Num Lock LED
	*/

#define SetModifierBit(x)		modifiers_ = (modifiers_ & ~x) | (is_pressed ? 0 : x);
#define ToggleModifierBit(x)	if(is_pressed) modifiers_ ^= x;
	switch(key) {
		default: break;
		case Key::Delete:	SetModifierBit(0x4000);		break;
		case Key::CapsLock:	ToggleModifierBit(0x2000);	break;
		case Key::Power:	SetModifierBit(0x1000);		break;

		case Key::LeftControl:
		case Key::RightControl:
			SetModifierBit(0x0800);
		break;

		case Key::LeftShift:
		case Key::RightShift:
			SetModifierBit(0x0400);
		break;

		case Key::LeftOption:
		case Key::RightOption:
			SetModifierBit(0x0200);
		break;

		case Key::Command:
			SetModifierBit(0x0100);
		break;

		case Key::KeypadClear:	ToggleModifierBit(0x0080);	break;
		case Key::Help:			ToggleModifierBit(0x0040);	break;
	}
#undef SetModifierBit

	// Ensure service occurs.
	post_service_request();
	return true;
}

void Keyboard::clear_all_keys() {
	// For all keys currently marked as down, enqueue key-up actions.
	std::lock_guard lock_guard(keys_mutex_);
	for(size_t key = 0; key < pressed_keys_.size(); key++) {
		if(pressed_keys_[key]) {
			pending_events_.push_back(0x80 | uint8_t(key));
			pressed_keys_[key] = false;
		}
	}
	if(!pending_events_.empty()) post_service_request();

	// Mark all modifiers as released.
	modifiers_ |= 0xfff8;
}

// MARK: - KeyboardMapper

uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const {
	using Key = Inputs::Keyboard::Key;
	using ADBKey = Apple::ADB::Key;
	switch(key) {
		default: return MachineTypes::MappedKeyboardMachine::KeyNotMapped;

#define Bind(x, y)		case Key::x: return uint16_t(ADBKey::y)
#define BindDirect(x)	Bind(x, x)

		BindDirect(BackTick);
		BindDirect(k1);	BindDirect(k2);	BindDirect(k3);	BindDirect(k4);	BindDirect(k5);
		BindDirect(k6);	BindDirect(k7);	BindDirect(k8);	BindDirect(k9);	BindDirect(k0);

		BindDirect(Help);
		BindDirect(Home);
		BindDirect(PageUp);
		BindDirect(Delete);
		BindDirect(End);
		BindDirect(PageDown);

		BindDirect(Escape);
		BindDirect(Hyphen);
		BindDirect(Equals);
		BindDirect(Backspace);
		BindDirect(Tab);

		BindDirect(F1);		BindDirect(F2);		BindDirect(F3);		BindDirect(F4);
		BindDirect(F5);		BindDirect(F6);		BindDirect(F7);		BindDirect(F8);
		BindDirect(F9);		BindDirect(F10);	BindDirect(F11);	BindDirect(F12);

		BindDirect(Q);	BindDirect(W);	BindDirect(E);	BindDirect(R);
		BindDirect(T);	BindDirect(Y);	BindDirect(U);	BindDirect(I);
		BindDirect(O);	BindDirect(P);	BindDirect(A);	BindDirect(S);
		BindDirect(D);	BindDirect(F);	BindDirect(G);	BindDirect(H);
		BindDirect(J);	BindDirect(K);	BindDirect(L);	BindDirect(Z);
		BindDirect(X);	BindDirect(C);	BindDirect(V);	BindDirect(B);
		BindDirect(N);	BindDirect(M);

		BindDirect(OpenSquareBracket);
		BindDirect(CloseSquareBracket);
		BindDirect(Semicolon);
		BindDirect(Quote);
		BindDirect(Comma);
		BindDirect(FullStop);
		BindDirect(ForwardSlash);

		BindDirect(CapsLock);
		BindDirect(LeftShift);		BindDirect(RightShift);
		BindDirect(LeftControl);	BindDirect(RightControl);
		BindDirect(LeftOption);		BindDirect(RightOption);
		Bind(LeftMeta, Command);	Bind(RightMeta, Command);

		BindDirect(Space);
		BindDirect(Backslash);
		Bind(Enter, Return);

		BindDirect(Left);	BindDirect(Right);
		BindDirect(Up);		BindDirect(Down);

		Bind(KeypadDelete, KeypadClear);
		BindDirect(KeypadEquals);
		BindDirect(KeypadSlash);
		BindDirect(KeypadAsterisk);
		BindDirect(KeypadMinus);
		BindDirect(KeypadPlus);
		BindDirect(KeypadEnter);
		BindDirect(KeypadDecimalPoint);

		BindDirect(Keypad9);
		BindDirect(Keypad8);
		BindDirect(Keypad7);
		BindDirect(Keypad6);
		BindDirect(Keypad5);
		BindDirect(Keypad4);
		BindDirect(Keypad3);
		BindDirect(Keypad2);
		BindDirect(Keypad1);
		BindDirect(Keypad0);

		// Leaving unmapped:
		//	Power, F13, F14, F15

#undef BindDirect
#undef Bind
	}
}
