// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************

    timer.cpp

    Timer devices.

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

#include "emu.h"
#include "machine/timer.h"


/***************************************************************************
//  DEBUGGING
***************************************************************************/

#define VERBOSE 0

#define LOG(x)  do { if (VERBOSE) logerror x; } while (0)




//**************************************************************************
//  LIVE TIMER DEVICE
//**************************************************************************

DEFINE_DEVICE_TYPE(TIMER, timer_device, "timer", "Timer")

//-------------------------------------------------
//  timer_device - constructor
//-------------------------------------------------

timer_device::timer_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
	: device_t(mconfig, TIMER, tag, owner, clock),
		m_type(TIMER_TYPE_GENERIC),
		m_callback(*this),
		m_start_delay(attotime::zero),
		m_period(attotime::zero),
		m_param(0),
		m_screen(*this, finder_base::DUMMY_TAG),
		m_first_vpos(0),
		m_increment(0),
		m_timer(nullptr),
		m_first_time(true)
{
}


//-------------------------------------------------
//  device_validity_check - validate the device
//  configuration
//-------------------------------------------------

void timer_device::device_validity_check(validity_checker &valid) const
{
	// type based configuration
	switch (m_type)
	{
		case TIMER_TYPE_GENERIC:
			if (m_screen.finder_tag() != finder_base::DUMMY_TAG || m_first_vpos != 0 || m_start_delay != attotime::zero)
				osd_printf_warning("Generic timer specified parameters for a scanline timer\n");
			if (m_period != attotime::zero || m_start_delay != attotime::zero)
				osd_printf_warning("Generic timer specified parameters for a periodic timer\n");
			break;

		case TIMER_TYPE_PERIODIC:
			if (m_screen.finder_tag() != finder_base::DUMMY_TAG || m_first_vpos != 0)
				osd_printf_warning("Periodic timer specified parameters for a scanline timer\n");
			if (m_period <= attotime::zero)
				osd_printf_error("Periodic timer specified invalid period\n");
			break;

		case TIMER_TYPE_SCANLINE:
			if (m_period != attotime::zero || m_start_delay != attotime::zero)
				osd_printf_warning("Scanline timer specified parameters for a periodic timer\n");
			if (m_param != 0)
				osd_printf_warning("Scanline timer specified parameter which is ignored\n");
			if (m_screen.finder_tag() == finder_base::DUMMY_TAG)
				osd_printf_error("Scanline timer has no screen specified\n");
			else if (!m_screen)
				osd_printf_error("Scanline timer specifies nonexistent screen %s\n", m_screen.finder_tag());
//          if (m_first_vpos < 0)
//              osd_printf_error("Scanline timer specified invalid initial position\n");
//          if (m_increment < 0)
//              osd_printf_error("Scanline timer specified invalid increment\n");
			break;

		default:
			osd_printf_error("Invalid type specified\n");
			break;
	}
}


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

void timer_device::device_start()
{
	if (m_type == TIMER_TYPE_SCANLINE)
		m_timer = timer_alloc(FUNC(timer_device::scanline_tick), this);
	else
		m_timer = timer_alloc(FUNC(timer_device::generic_tick), this);

	m_callback.resolve();

	// register for save states
	save_item(NAME(m_first_time));
}


//-------------------------------------------------
//  device_reset - reset the device
//-------------------------------------------------

void timer_device::device_reset()
{
	// type based configuration
	switch (m_type)
	{
		case TIMER_TYPE_GENERIC:
		case TIMER_TYPE_PERIODIC:
		{
			// convert the period into attotime
			attotime period;
			if (m_period > attotime::zero)
			{
				period = m_period;

				// convert the start_delay into attotime
				attotime start_delay = attotime::zero;
				if (m_start_delay > attotime::zero)
					start_delay = m_start_delay;

				// allocate and start the backing timer
				m_timer->adjust(start_delay, m_param, period);
			}
			break;
		}

		case TIMER_TYPE_SCANLINE:
			if (!m_screen)
				fatalerror("timer '%s': unable to find screen '%s'\n", tag(), m_screen.finder_tag());

			// set the timer to fire immediately
			m_first_time = true;
			m_timer->adjust(attotime::zero, m_param);
			break;
	}
}


TIMER_CALLBACK_MEMBER(timer_device::generic_tick)
{
	if (!m_callback.isnull())
		(m_callback)(*this, param);
}

TIMER_CALLBACK_MEMBER(timer_device::scanline_tick)
{
	// by default, we fire at the first position
	int next_vpos = m_first_vpos;

	// the first time through we just go with the default position
	if (!m_first_time)
	{
		// call the real callback
		int vpos = m_screen->vpos();
		if (!m_callback.isnull())
			(m_callback)(*this, vpos);

		// advance by the increment only if we will still be within the screen bounds
		if (m_increment != 0 && (vpos + m_increment) < m_screen->height())
			next_vpos = vpos + m_increment;
	}
	m_first_time = false;

	// adjust the timer
	m_timer->adjust(m_screen->time_until_pos(next_vpos));
}
