using BizHawk.Common;
using System;
using System.Collections.Generic;
using System.Linq;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
///
/// The 48k keyboard device
///
public class StandardKeyboard : IKeyboard
{
public SpectrumBase _machine { get; set; }
private byte[] LineStatus;
private string[] _keyboardMatrix;
private int[] _keyLine;
private bool _isIssue2Keyboard;
private string[] _nonMatrixKeys;
public bool IsIssue2Keyboard
{
get { return _isIssue2Keyboard; }
set { _isIssue2Keyboard = value; }
}
public int[] KeyLine
{
get { return _keyLine; }
set { _keyLine = value; }
}
public string[] KeyboardMatrix
{
get { return _keyboardMatrix; }
set { _keyboardMatrix = value; }
}
public string[] NonMatrixKeys
{
get { return _nonMatrixKeys; }
set { _nonMatrixKeys = value; }
}
public StandardKeyboard(SpectrumBase machine)
{
_machine = machine;
KeyboardMatrix = new string[]
{
// 0xfefe - 0 - 4
"Key Caps Shift", "Key Z", "Key X", "Key C", "Key V",
// 0xfdfe - 5 - 9
"Key A", "Key S", "Key D", "Key F", "Key G",
// 0xfbfe - 10 - 14
"Key Q", "Key W", "Key E", "Key R", "Key T",
// 0xf7fe - 15 - 19
"Key 1", "Key 2", "Key 3", "Key 4", "Key 5",
// 0xeffe - 20 - 24
"Key 0", "Key 9", "Key 8", "Key 7", "Key 6",
// 0xdffe - 25 - 29
"Key P", "Key O", "Key I", "Key U", "Key Y",
// 0xbffe - 30 - 34
"Key Return", "Key L", "Key K", "Key J", "Key H",
// 0x7ffe - 35 - 39
"Key Space", "Key Symbol Shift", "Key M", "Key N", "Key B"
};
var nonMatrix = new List();
foreach (var key in _machine.Spectrum.ZXSpectrumControllerDefinition.BoolButtons)
{
if (!KeyboardMatrix.Any(s => s == key))
nonMatrix.Add(key);
}
NonMatrixKeys = nonMatrix.ToArray();
LineStatus = new byte[8];
_keyLine = new int[] { 255, 255, 255, 255, 255, 255, 255, 255 };
IsIssue2Keyboard = true;
}
public void SetKeyStatus(string key, bool isPressed)
{
int k = GetByteFromKeyMatrix(key);
if (k != 255)
{
var lineIndex = k / 5;
var lineMask = 1 << k % 5;
_keyLine[lineIndex] = isPressed
? (byte)(_keyLine[lineIndex] & ~lineMask)
: (byte)(_keyLine[lineIndex] | lineMask);
}
// Combination keys that are not in the keyboard matrix
// but are available on the Spectrum+, 128k +2 & +3
// (GetByteFromKeyMatrix() should return 255)
// Processed after the matrix keys - only presses handled (unpressed get done above)
if (k == 255)
{
if (isPressed)
{
switch (key)
{
// Delete key (simulates Caps Shift + 0)
case "Key Delete":
_keyLine[0] = _keyLine[0] & ~(0x1);
_keyLine[4] = _keyLine[4] & ~(0x1);
break;
// Cursor left (simulates Caps Shift + 5)
case "Key Left Cursor":
_keyLine[0] = _keyLine[0] & ~(0x1);
_keyLine[3] = _keyLine[3] & ~(0x10);
break;
// Cursor right (simulates Caps Shift + 8)
case "Key Right Cursor":
_keyLine[0] = _keyLine[0] & ~(0x1);
_keyLine[4] = _keyLine[4] & ~(0x04);
break;
// Cursor up (simulates Caps Shift + 7)
case "Key Up Cursor":
_keyLine[0] = _keyLine[0] & ~(0x1);
_keyLine[4] = _keyLine[4] & ~(0x08);
break;
// Cursor down (simulates Caps Shift + 6)
case "Key Down Cursor":
_keyLine[0] = _keyLine[0] & ~(0x1);
_keyLine[4] = _keyLine[4] & ~(0x10);
break;
}
}
}
}
public bool GetKeyStatus(string key)
{
byte keyByte = GetByteFromKeyMatrix(key);
var lineIndex = keyByte / 5;
var lineMask = 1 << keyByte % 5;
return (_keyLine[lineIndex] & lineMask) == 0;
}
public void ResetLineStatus()
{
lock (this)
{
for (int i = 0; i < KeyLine.Length; i++)
KeyLine[i] = 255;
}
}
public byte GetLineStatus(byte lines)
{
lock(this)
{
byte status = 0;
lines = (byte)~lines;
var lineIndex = 0;
while (lines > 0)
{
if ((lines & 0x01) != 0)
status |= (byte)_keyLine[lineIndex];
lineIndex++;
lines >>= 1;
}
var result = (byte)status;
return result;
}
}
public byte ReadKeyboardByte(ushort addr)
{
return GetLineStatus((byte)(addr >> 8));
}
public byte GetByteFromKeyMatrix(string key)
{
int index = Array.IndexOf(KeyboardMatrix, key);
return (byte)index;
}
#region IPortIODevice
///
/// Device responds to an IN instruction
///
///
///
///
public bool ReadPort(ushort port, ref int result)
{
/*
The high byte indicates which half-row of keys is being polled
A zero on one of these lines selects a particular half-row of five keys:
IN: Reads keys (bit 0 to bit 4 inclusive)
0xfefe SHIFT, Z, X, C, V 0xeffe 0, 9, 8, 7, 6
0xfdfe A, S, D, F, G 0xdffe P, O, I, U, Y
0xfbfe Q, W, E, R, T 0xbffe ENTER, L, K, J, H
0xf7fe 1, 2, 3, 4, 5 0x7ffe SPACE, SYM SHFT, M, N, B
A zero in one of the five lowest bits means that the corresponding key is pressed. If more than one address line
is made low, the result is the logical AND of all single inputs, so a zero in a bit means that at least one of the
appropriate keys is pressed. For example, only if each of the five lowest bits of the result from reading from Port 00FE
(for instance by XOR A/IN A,(FE)) is one, no key is pressed
*/
if ((port & 0x0001) != 0)
return false;
if ((port & 0x8000) == 0)
{
result &= KeyLine[7];
}
if ((port & 0x4000) == 0)
{
result &= KeyLine[6];
}
if ((port & 0x2000) == 0)
{
result &= KeyLine[5];
}
if ((port & 0x1000) == 0)
{
result &= KeyLine[4];
}
if ((port & 0x800) == 0)
{
result &= KeyLine[3];
}
if ((port & 0x400) == 0)
{
result &= KeyLine[2];
}
if ((port & 0x200) == 0)
{
result &= KeyLine[1];
}
if ((port & 0x100) == 0)
{
result &= KeyLine[0];
}
// mask out lower 4 bits
result = result & 0x1f;
// set bit 5 & 7 to 1
result = result | 0xa0;
return true;
}
///
/// Device responds to an OUT instruction
///
///
///
///
public bool WritePort(ushort port, int result)
{
// not implemented
return false;
}
#endregion
public void SyncState(Serializer ser)
{
ser.BeginSection("Keyboard");
ser.Sync("LineStatus", ref LineStatus, false);
ser.Sync("_keyLine", ref _keyLine, false);
ser.EndSection();
}
}
}