text: // license:BSD-3-Clause
  // copyright-holders:Olivier Galibert
  
  #include "emu.h"
  #include "wpc_out.h"
  
  DEFINE_DEVICE_TYPE(WPC_OUT, wpc_out_device, "wpc_out", "Williams Pinball Controller Output Control")
  
  wpc_out_device::wpc_out_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
  	device_t(mconfig, WPC_OUT, tag, owner, clock)
  {
  	names = nullptr;
  }
  
  wpc_out_device::~wpc_out_device()
  {
  }
  
  void wpc_out_device::set_names(const char *const *_names)
  {
  	names = _names;
  }
  
  void wpc_out_device::set_handler(handler_t cb)
  {
  	handler_cb = cb;
  }
  
  void wpc_out_device::set_gi_count(int _count)
  {
  	gi_count = _count;
  }
  
  void wpc_out_device::gi_update()
  {
  	attotime now = machine().time();
  	attotime delta = now - previous_gi_update;
  	uint32_t delta_us = delta.as_ticks(1e6);
  	for(int i=0; i<gi_count; i++)
  		if(gi & (1 <<i))
  			gi_time[i] += delta_us;
  	previous_gi_update = now;
  }
  
  void wpc_out_device::send_output(int sid, int state)
  {
  	if(!handler_cb.isnull() && handler_cb(sid, state))
  		return;
  
  	char buffer[32];
  	const char *name;
  	if(names && names[sid-1] && strcmp(names[sid-1], "s:"))
  		name = names[sid-1];
  	else {
  		sprintf(buffer, "u:output %02d", sid);
  		name = buffer;
  	}
  	machine().output().set_value(name, state);
  
  	if(sid == 41)
  		machine().bookkeeping().coin_counter_w(0, state);
  }
  
  void wpc_out_device::out_w(offs_t offset, uint8_t data)
  {
  	first_after_led = false;
  	uint8_t diff = state[offset] ^ data;
  	state[offset] = data;
  	if(diff)
  		for(int i=0; i<8; i++)
  			if(diff & (1 << i)) {
  				int id = (offset << 3) | i;
  				int sid;
  				if(id <= 3)
  					sid = id + 25;
  				else if(id <= 7)
  					sid = id + 33;
  				else if(id <= 15)
  					sid = id-7;
  				else if(id <= 23)
  					sid = id+1;
  				else if(id <= 31)
  					sid = id-15;
  				else if(id <= 39)
  					sid = id-3;
  				else
  					sid = id+2;
  
  				send_output(sid, (data & (1<<i)) != 0);
  			}
  }
  
  void wpc_out_device::out4_w(uint8_t data)
  {
  	// This is gross, probably wrong, but also the best I could find.
  	// Test case is No Good Gofers (ngg_13).
  	out_w(first_after_led ? 5 : 4, data);
  }
  
  void wpc_out_device::gi_w(uint8_t data)
  {
  	gi_update();
  	if((gi^data) & 0x80)
  		send_output(41, data & 0x80 ? 1 : 0);
  	gi = data;
  }
  
  void wpc_out_device::led_w(uint8_t data)
  {
  	first_after_led = true;
  	machine().output().set_value("L:cpu led", data & 0x80 ? 1 : 0);
  }
  
  void wpc_out_device::device_start()
  {
  	save_item(NAME(state));
  	save_item(NAME(gi));
  	save_item(NAME(first_after_led));
  	save_item(NAME(previous_gi_update));
  	save_item(NAME(gi_time));
  
  	timer = timer_alloc(0);
  }
  
  void wpc_out_device::device_reset()
  {
  	memset(state, 0x00, 6);
  	first_after_led = false;
  	gi = 0x00;
  	previous_gi_update = attotime::zero;
  	memset(gi_time, 0, sizeof(gi_time));
  	timer->adjust(attotime::from_hz(10), 0, attotime::from_hz(10));
  }
  
  void wpc_out_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
  {
  	gi_update();
  	for(int i=0; i<gi_count; i++) {
  		//      fprintf(stderr, "gi[%d] = %d\n", i, gi_time[i]);
  		gi_time[i] = 0;
  	}
  }
  