// license:BSD-3-Clause
// copyright-holders:Sandro Ronco, hap
/******************************************************************************

Tasc SmartBoard SB30 (analog)

Chessboard controller for use with Tasc R30 chesscomputer, or as PC peripheral.

The SmartBoard can detect which piece is present on a specific square, more
info on the technology used in the piece recognition system can be found in
the US patent 5,129,654

SmartBoard I is SB30 (81 LEDs, analog chesspieces)
SmartBoard II is SB20 (64 LEDs, digital chesspieces)
SmartBoard III is SB30 again, but digital

SB20 and the newer SB30 are not emulated. They're on different hardware, with
embedded CPU to reduce I/O overhead. Note, those are not compatible with old
versions of Tasc R30.

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

#include "emu.h"
#include "smartboard.h"

// SB30 chesspiece IDs: 0-31 (+1 for MAME sensorboard_device)
enum
{
	SB30_WHITE_KNIGHT1 = 1,
	SB30_WHITE_KNIGHT2,
	SB30_BLACK_KING,
	SB30_WHITE_KING,
	SB30_BLACK_QUEEN,
	SB30_WHITE_QUEEN,
	SB30_BLACK_ROOK1,
	SB30_BLACK_ROOK2,
	SB30_WHITE_ROOK1,
	SB30_WHITE_ROOK2,
	SB30_BLACK_BISHOP1,
	SB30_BLACK_BISHOP2,
	SB30_WHITE_BISHOP1,
	SB30_WHITE_BISHOP2,
	SB30_BLACK_KNIGHT1,
	SB30_BLACK_KNIGHT2,
	SB30_WHITE_PAWN1,
	SB30_WHITE_PAWN2,
	SB30_WHITE_PAWN3,
	SB30_WHITE_PAWN4,
	SB30_WHITE_PAWN5,
	SB30_WHITE_PAWN6,
	SB30_WHITE_PAWN7,
	SB30_WHITE_PAWN8,
	SB30_BLACK_PAWN1,
	SB30_BLACK_PAWN2,
	SB30_BLACK_PAWN3,
	SB30_BLACK_PAWN4,
	SB30_BLACK_PAWN5,
	SB30_BLACK_PAWN6,
	SB30_BLACK_PAWN7,
	SB30_BLACK_PAWN8
};


DEFINE_DEVICE_TYPE(TASC_SB30, tasc_sb30_device, "tasc_sb30", "Tasc SmartBoard SB30")

//-------------------------------------------------
//  tasc_sb30_device - constructor
//-------------------------------------------------

tasc_sb30_device::tasc_sb30_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
	: device_t(mconfig, TASC_SB30, tag, owner, clock)
	, m_board(*this, "board")
	, m_out_leds(*this, "sb30_led_%u.%u", 0U, 0U)
	, m_conf(*this, "CONF")
	, m_data_out(*this)
	, m_led_out(*this)
{ }


//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void tasc_sb30_device::device_start()
{
	m_led_out.resolve();
	if (m_led_out.isnull())
		m_out_leds.resolve();

	m_data_out.resolve_safe();

	// zerofill
	m_data0 = 0;
	m_data1 = 0;
	m_output = 0;
	m_scan_pending = false;
	m_pos = 0;
	std::fill_n(m_squares, std::size(m_squares), 0);

	// register for savestates
	save_item(NAME(m_data0));
	save_item(NAME(m_data1));
	save_item(NAME(m_output));
	save_item(NAME(m_scan_pending));
	save_item(NAME(m_pos));
	save_item(NAME(m_squares));
}


//-------------------------------------------------
//  input_ports - device-specific input ports
//-------------------------------------------------

static INPUT_PORTS_START( smartboard )
	PORT_START("CONF")
	PORT_CONFNAME( 0x01, 0x00, "Duplicate Piece IDs" )
	PORT_CONFSETTING(    0x00, DEF_STR( No ) )
	PORT_CONFSETTING(    0x01, DEF_STR( Yes ) )
INPUT_PORTS_END

ioport_constructor tasc_sb30_device::device_input_ports() const
{
	return INPUT_PORTS_NAME(smartboard);
}


//-------------------------------------------------
//  device_add_mconfig - add device-specific
//  machine configuration
//-------------------------------------------------

void tasc_sb30_device::device_add_mconfig(machine_config &config)
{
	SENSORBOARD(config, m_board);
	m_board->set_type(sensorboard_device::INDUCTIVE);
	m_board->set_max_id(32);
	m_board->init_cb().set(FUNC(tasc_sb30_device::init_cb));
	m_board->spawn_cb().set(FUNC(tasc_sb30_device::spawn_cb));
}


//-------------------------------------------------
//  sensorboard_device interface
//-------------------------------------------------

void tasc_sb30_device::init_cb(int state)
{
	m_board->clear_board();
	m_board->write_piece(0, 0, SB30_WHITE_ROOK1);
	m_board->write_piece(7, 0, SB30_WHITE_ROOK2);
	m_board->write_piece(1, 0, SB30_WHITE_KNIGHT1);
	m_board->write_piece(6, 0, SB30_WHITE_KNIGHT2);
	m_board->write_piece(2, 0, SB30_WHITE_BISHOP1);
	m_board->write_piece(5, 0, SB30_WHITE_BISHOP2);
	m_board->write_piece(3, 0, SB30_WHITE_QUEEN);
	m_board->write_piece(4, 0, SB30_WHITE_KING);
	m_board->write_piece(0, 7, SB30_BLACK_ROOK1);
	m_board->write_piece(7, 7, SB30_BLACK_ROOK2);
	m_board->write_piece(1, 7, SB30_BLACK_KNIGHT1);
	m_board->write_piece(6, 7, SB30_BLACK_KNIGHT2);
	m_board->write_piece(2, 7, SB30_BLACK_BISHOP1);
	m_board->write_piece(5, 7, SB30_BLACK_BISHOP2);
	m_board->write_piece(3, 7, SB30_BLACK_QUEEN);
	m_board->write_piece(4, 7, SB30_BLACK_KING);

	for (int x = 0; x < 8; x++)
	{
		m_board->write_piece(x, 1, SB30_WHITE_PAWN1 + x);
		m_board->write_piece(x, 6, SB30_BLACK_PAWN1 + x);
	}
}

bool tasc_sb30_device::piece_available(u8 id)
{
	for (int y = 0; y < 8; y++)
		for (int x = 0; x < 8; x++)
		{
			if (m_board->read_piece(x, y) == id)
				return bool(m_conf->read() & 1);
		}

	return true;
}

u8 tasc_sb30_device::spawn_cb(offs_t offset)
{
	int piece_id = 0;

	// While most software works fine as long as color and piece type can be distinguished,
	// each individual chesspiece is expected to have a unique id.
	switch (offset)
	{
		case 1+0:
			for (int p = 0; p < 8; p++)
				if (piece_available(SB30_WHITE_PAWN1 + p))
				{
					piece_id = SB30_WHITE_PAWN1 + p;
					break;
				}
			break;

		case 1+6:
			for (int p = 0; p < 8; p++)
				if (piece_available(SB30_BLACK_PAWN1 + p))
				{
					piece_id = SB30_BLACK_PAWN1 + p;
					break;
				}
			break;

		case 2+0:
			if (piece_available(SB30_WHITE_KNIGHT1))
				piece_id = SB30_WHITE_KNIGHT1;
			else if (piece_available(SB30_WHITE_KNIGHT2))
				piece_id = SB30_WHITE_KNIGHT2;
			break;

		case 2+6:
			if (piece_available(SB30_BLACK_KNIGHT1))
				piece_id = SB30_BLACK_KNIGHT1;
			else if (piece_available(SB30_BLACK_KNIGHT2))
				piece_id = SB30_BLACK_KNIGHT2;
			break;

		case 3+0:
			if (piece_available(SB30_WHITE_BISHOP1))
				piece_id = SB30_WHITE_BISHOP1;
			else if (piece_available(SB30_WHITE_BISHOP2))
				piece_id = SB30_WHITE_BISHOP2;
			break;

		case 3+6:
			if (piece_available(SB30_BLACK_BISHOP1))
				piece_id = SB30_BLACK_BISHOP1;
			else if (piece_available(SB30_BLACK_BISHOP2))
				piece_id = SB30_BLACK_BISHOP2;
			break;

		case 4+0:
			if (piece_available(SB30_WHITE_ROOK1))
				piece_id = SB30_WHITE_ROOK1;
			else if (piece_available(SB30_WHITE_ROOK2))
				piece_id = SB30_WHITE_ROOK2;
			break;

		case 4+6:
			if (piece_available(SB30_BLACK_ROOK1))
				piece_id = SB30_BLACK_ROOK1;
			else if (piece_available(SB30_BLACK_ROOK2))
				piece_id = SB30_BLACK_ROOK2;
			break;

		case 5+0:
			if (piece_available(SB30_WHITE_QUEEN))
				piece_id = SB30_WHITE_QUEEN;
			break;

		case 5+6:
			if (piece_available(SB30_BLACK_QUEEN))
				piece_id = SB30_BLACK_QUEEN;
			break;

		case 6+0:
			if (piece_available(SB30_WHITE_KING))
				piece_id = SB30_WHITE_KING;
			break;

		case 6+6:
			if (piece_available(SB30_BLACK_KING))
				piece_id = SB30_BLACK_KING;
			break;

		default:
			break;
	}

	return piece_id;
}


//-------------------------------------------------
//  I/O handlers
//-------------------------------------------------

void tasc_sb30_device::data0_w(int state)
{
	state = state ? 1 : 0;

	if (!state && m_data0)
	{
		if (m_scan_pending)
		{
			for (int i = 0; i < 64; i++)
			{
				// each piece is identified by a single bit in a 32-bit sequence
				int piece_id = m_board->read_sensor(i >> 3 ^ 7, ~i & 7);
				m_squares[i] = piece_id ? (1 << (piece_id - 1)) : 0;
			}

			m_scan_pending = false;
			m_pos = 0;
			update_output();
		}
		else
		{
			// output board led(s)
			if (m_led_out.isnull())
				m_out_leds[m_pos & 7][m_pos >> 3 & 7] = m_data1;
			else
				m_led_out(m_pos & 0x3f, m_data1);
		}
	}

	m_data0 = state;
}

void tasc_sb30_device::data1_w(int state)
{
	state = state ? 1 : 0;

	// clock position counter
	if (!state && m_data1)
	{
		m_pos++;

		if (m_data0)
			m_scan_pending = true;

		update_output();
	}

	m_data1 = state;
}

void tasc_sb30_device::update_output()
{
	m_output = BIT(m_squares[m_pos & 0x3f], m_pos >> 6 & 0x1f);
	m_data_out(m_output);
}
