/*
    Copyright (C) 2001 Paul Davis

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

 */

#include "hardware.h"
#include "alsa_driver.h"
#include "hammerfall.h"
#include "internal.h"

/* Set this to 1 if you want this compile error:
 *   warning: `hammerfall_monitor_controls' defined but not used */
#define HAMMERFALL_MONITOR_CONTROLS 0

static void
set_control_id (snd_ctl_elem_id_t *ctl, const char *name)
{
	snd_ctl_elem_id_set_name (ctl, name);
	snd_ctl_elem_id_set_numid (ctl, 0);
	snd_ctl_elem_id_set_interface (ctl, SND_CTL_ELEM_IFACE_MIXER);
	snd_ctl_elem_id_set_device (ctl, 0);
	snd_ctl_elem_id_set_subdevice (ctl, 0);
	snd_ctl_elem_id_set_index (ctl, 0);
}

#if HAMMERFALL_MONITOR_CONTROLS
static void
hammerfall_broadcast_channel_status_change (hammerfall_t *h, int lock, int sync, channel_t lowchn, channel_t highchn)

{
	channel_t chn;
	ClockSyncStatus status = 0;

	if (lock) {
		status |= Lock;
	} else {
		status |= NoLock;
	}

	if (sync) {
		status |= Sync;
	} else {
		status |= NoSync;
	}

	for (chn = lowchn; chn < highchn; chn++)
		alsa_driver_set_clock_sync_status (h->driver, chn, status);
}

static void
hammerfall_check_sync_state (hammerfall_t *h, int val, int adat_id)

{
	int lock;
	int sync;

	/* S/PDIF channel is always locked and synced, but we only
	   need tell people once that this is TRUE.

	   XXX - maybe need to make sure that the rate matches our
	   idea of the current rate ?
	 */

	if (!h->said_that_spdif_is_fine) {
		ClockSyncStatus status;

		status = Lock | Sync;

		/* XXX broken! fix for hammerfall light ! */

		alsa_driver_set_clock_sync_status (h->driver, 24, status);
		alsa_driver_set_clock_sync_status (h->driver, 25, status);

		h->said_that_spdif_is_fine = TRUE;
	}

	lock = (val & 0x1) ? TRUE : FALSE;
	sync = (val & 0x2) ? TRUE : FALSE;

	if (h->lock_status[adat_id] != lock ||
	    h->sync_status[adat_id] != sync) {
		hammerfall_broadcast_channel_status_change (h, lock, sync, adat_id * 8, (adat_id * 8) + 8);
	}

	h->lock_status[adat_id] = lock;
	h->sync_status[adat_id] = sync;
}

static void
hammerfall_check_sync (hammerfall_t *h, snd_ctl_elem_value_t *ctl)

{
	const char *name;
	int val;
	snd_ctl_elem_id_t *ctl_id;

	jack_info ("check sync");

	snd_ctl_elem_id_alloca (&ctl_id);
	snd_ctl_elem_value_get_id (ctl, ctl_id);

	name = snd_ctl_elem_id_get_name (ctl_id);

	if (strcmp (name, "ADAT1 Sync Check") == 0) {
		val = snd_ctl_elem_value_get_enumerated (ctl, 0);
		hammerfall_check_sync_state (h, val, 0);
	} else if (strcmp (name, "ADAT2 Sync Check") == 0) {
		val = snd_ctl_elem_value_get_enumerated (ctl, 0);
		hammerfall_check_sync_state (h, val, 1);
	} else if (strcmp (name, "ADAT3 Sync Check") == 0) {
		val = snd_ctl_elem_value_get_enumerated (ctl, 0);
		hammerfall_check_sync_state (h, val, 2);
	} else {
		jack_error ("Hammerfall: unknown control \"%s\"", name);
	}
}
#endif /* HAMMERFALL_MONITOR_CONTROLS */

static int
hammerfall_set_input_monitor_mask (jack_hardware_t *hw, unsigned long mask)
{
	hammerfall_t *h = (hammerfall_t*)hw->private;
	snd_ctl_elem_value_t *ctl;
	snd_ctl_elem_id_t *ctl_id;
	int err;
	int i;

	snd_ctl_elem_value_alloca (&ctl);
	snd_ctl_elem_id_alloca (&ctl_id);
	set_control_id (ctl_id, "Channels Thru");
	snd_ctl_elem_value_set_id (ctl, ctl_id);

	for (i = 0; i < 26; i++)
		snd_ctl_elem_value_set_integer (ctl, i, (mask & (1 << i)) ? 1 : 0);

	if ((err = snd_ctl_elem_write (h->driver->ctl_handle, ctl)) != 0) {
		jack_error ("ALSA/Hammerfall: cannot set input monitoring (%s)", snd_strerror (err));
		return -1;
	}

	hw->input_monitor_mask = mask;

	return 0;
}

static int
hammerfall_change_sample_clock (jack_hardware_t *hw, SampleClockMode mode)
{
	hammerfall_t *h = (hammerfall_t*)hw->private;
	snd_ctl_elem_value_t *ctl;
	snd_ctl_elem_id_t *ctl_id;
	int err;

	snd_ctl_elem_value_alloca (&ctl);
	snd_ctl_elem_id_alloca (&ctl_id);
	set_control_id (ctl_id, "Sync Mode");
	snd_ctl_elem_value_set_id (ctl, ctl_id);

	switch (mode) {
	case AutoSync:
		snd_ctl_elem_value_set_enumerated (ctl, 0, 0);
		break;
	case ClockMaster:
		snd_ctl_elem_value_set_enumerated (ctl, 0, 1);
		break;
	case WordClock:
		snd_ctl_elem_value_set_enumerated (ctl, 0, 2);
		break;
	}

	if ((err = snd_ctl_elem_write (h->driver->ctl_handle, ctl)) < 0) {
		jack_error ("ALSA-Hammerfall: cannot set clock mode");
	}

	return 0;
}

static void
hammerfall_release (jack_hardware_t *hw)

{
	hammerfall_t *h = (hammerfall_t*)hw->private;
	void *status;

	if (h == 0) {
		return;
	}

	pthread_cancel (h->monitor_thread);
	pthread_join (h->monitor_thread, &status);

	free (h);
}

#if HAMMERFALL_MONITOR_CONTROLS
static void *
hammerfall_monitor_controls (void *arg)
{
	jack_hardware_t *hw = (jack_hardware_t*)arg;
	hammerfall_t *h = (hammerfall_t*)hw->private;
	snd_ctl_elem_id_t *switch_id[3];
	snd_ctl_elem_value_t *sw[3];

	pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

	snd_ctl_elem_id_malloc (&switch_id[0]);
	snd_ctl_elem_id_malloc (&switch_id[1]);
	snd_ctl_elem_id_malloc (&switch_id[2]);

	snd_ctl_elem_value_malloc (&sw[0]);
	snd_ctl_elem_value_malloc (&sw[1]);
	snd_ctl_elem_value_malloc (&sw[2]);

	set_control_id (switch_id[0], "ADAT1 Sync Check");
	set_control_id (switch_id[1], "ADAT2 Sync Check");
	set_control_id (switch_id[2], "ADAT3 Sync Check");

	snd_ctl_elem_value_set_id (sw[0], switch_id[0]);
	snd_ctl_elem_value_set_id (sw[1], switch_id[1]);
	snd_ctl_elem_value_set_id (sw[2], switch_id[2]);

	while (1) {
		if (snd_ctl_elem_read (h->driver->ctl_handle, sw[0])) {
			jack_error ("cannot read control switch 0 ...");
		}
		hammerfall_check_sync (h, sw[0]);

		if (snd_ctl_elem_read (h->driver->ctl_handle, sw[1])) {
			jack_error ("cannot read control switch 0 ...");
		}
		hammerfall_check_sync (h, sw[1]);

		if (snd_ctl_elem_read (h->driver->ctl_handle, sw[2])) {
			jack_error ("cannot read control switch 0 ...");
		}
		hammerfall_check_sync (h, sw[2]);

		if (nanosleep (&h->monitor_interval, 0)) {
			break;
		}
	}

	pthread_exit (0);
}
#endif /* HAMMERFALL_MONITOR_CONTROLS */

jack_hardware_t *
jack_alsa_hammerfall_hw_new (alsa_driver_t *driver)

{
	jack_hardware_t *hw;
	hammerfall_t *h;

	hw = (jack_hardware_t*)malloc (sizeof(jack_hardware_t));

	hw->capabilities = Cap_HardwareMonitoring | Cap_AutoSync | Cap_WordClock | Cap_ClockMaster | Cap_ClockLockReporting;
	hw->input_monitor_mask = 0;
	hw->private = 0;

	hw->set_input_monitor_mask = hammerfall_set_input_monitor_mask;
	hw->change_sample_clock = hammerfall_change_sample_clock;
	hw->release = hammerfall_release;

	h = (hammerfall_t*)malloc (sizeof(hammerfall_t));

	h->lock_status[0] = FALSE;
	h->sync_status[0] = FALSE;
	h->lock_status[1] = FALSE;
	h->sync_status[1] = FALSE;
	h->lock_status[2] = FALSE;
	h->sync_status[2] = FALSE;
	h->said_that_spdif_is_fine = FALSE;
	h->driver = driver;

	h->monitor_interval.tv_sec = 1;
	h->monitor_interval.tv_nsec = 0;

	hw->private = h;

#if 0
	if (pthread_create (&h->monitor_thread, 0, hammerfall_monitor_controls, hw)) {
		jack_error ("ALSA/Hammerfall: cannot create sync monitor thread");
	}
#endif

	return hw;
}
