/**
* This file is a part of the Cairo-Dock project
*
* Copyright : (C) see the 'copyright' file.
* E-mail    : see the 'copyright' file.
*
* 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 3
* 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, see <http://www.gnu.org/licenses/>.
*/

#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <glib/gi18n.h>
#include <alsa/asoundlib.h>

#include "applet-struct.h"
#include "applet-draw.h"
#include "applet-mixer.h"


static int	 mixer_level = 0;
static struct snd_mixer_selem_regopt mixer_options;


static int
mixer_event (snd_mixer_t *mixer, unsigned int mask, snd_mixer_elem_t *elem)
{
	cd_debug ("");
	return 0;
}

void mixer_init (gchar *cCardID)  // this function is taken from AlsaMixer.
{
	snd_ctl_card_info_t *hw_info = NULL;  // ne pas liberer.
	snd_ctl_t *ctl_handle = NULL;
	int err;
	snd_ctl_card_info_alloca (&hw_info);
	
	if ((err = snd_ctl_open (&ctl_handle, cCardID, 0)) < 0)
	{
		myData.cErrorMessage = g_strdup_printf (D_("I couldn't open card '%s'"), cCardID);
		return ;
	}
	if ((err = snd_ctl_card_info (ctl_handle, hw_info)) < 0)
	{
		myData.cErrorMessage = g_strdup_printf (D_("Card '%s' opened but I couldn't get any info"), cCardID);
		return ;
	}
	snd_ctl_close (ctl_handle);
	
	// open mixer device
	if ((err = snd_mixer_open (&myData.mixer_handle, 0)) < 0)
	{
		myData.cErrorMessage = g_strdup (D_("I couldn't open the mixer"));
		return ;
	}
	if (mixer_level == 0 && (err = snd_mixer_attach (myData.mixer_handle, cCardID)) < 0)
	{
		snd_mixer_free (myData.mixer_handle);
		myData.mixer_handle = NULL;
		myData.cErrorMessage = g_strdup (D_("I couldn't attach the mixer to the card"));
		return ;
	}
	if ((err = snd_mixer_selem_register (myData.mixer_handle, mixer_level > 0 ? &mixer_options : NULL, NULL)) < 0)
	{
		snd_mixer_free (myData.mixer_handle);
		myData.mixer_handle = NULL;
		myData.cErrorMessage = g_strdup (D_("I couldn't register options"));
		return ;
	}
	///snd_mixer_set_callback (myData.mixer_handle, mixer_event);
	if ((err = snd_mixer_load (myData.mixer_handle)) < 0)
	{
		snd_mixer_free (myData.mixer_handle);
		myData.mixer_handle = NULL;
		myData.cErrorMessage = g_strdup (D_("I couldn't load the mixer"));
		return ;
	}
	
	myData.mixer_card_name = g_strdup (snd_ctl_card_info_get_name(hw_info));
	myData.mixer_device_name = g_strdup (snd_ctl_card_info_get_mixername(hw_info));
	cd_debug ("myData.mixer_card_name : %s ; myData.mixer_device_name : %s", myData.mixer_card_name, myData.mixer_device_name);
}

void mixer_stop (void)
{
	if (myData.mixer_handle != NULL)
	{
		snd_mixer_detach (myData.mixer_handle, myConfig.card_id);
		snd_mixer_close (myData.mixer_handle);
		myData.mixer_handle = NULL;
		myData.pControledElement = NULL;
		myData.pControledElement2 = NULL;
	}
}


GList *mixer_get_elements_list (void)
{
	snd_mixer_elem_t *elem;
	if (myData.mixer_handle == NULL)
		return NULL;
	cd_message ("");
	
	GList *pList = NULL;
	for (elem = snd_mixer_first_elem(myData.mixer_handle); elem; elem = snd_mixer_elem_next(elem))
	{
		if (snd_mixer_selem_is_active (elem) && snd_mixer_selem_has_playback_volume (elem))
			pList = g_list_prepend (pList, (gpointer)snd_mixer_selem_get_name (elem));  // la liste ne contiendra que des const, on ne supprimera pas ses elements lors du g_list_free.
	}
	return pList;
}


static snd_mixer_elem_t *_mixer_get_element_by_name (const gchar *cName)
{
	if (myData.mixer_handle == NULL)
		return NULL;
	g_return_val_if_fail (cName != NULL, NULL);
	
	int i = 0;
	snd_mixer_elem_t *elem;
	for (elem = snd_mixer_first_elem(myData.mixer_handle); elem; elem = snd_mixer_elem_next(elem))
	{
		if (strcmp (cName, snd_mixer_selem_get_name (elem)) == 0)
			return elem;
	}
	
	cd_debug ("no channel matches '%s', we take the first available channel by default", cName);
	return snd_mixer_first_elem(myData.mixer_handle);
	/**myData.cErrorMessage = g_strdup_printf (D_("I couldn't find any audio channel named '%s'\nYou should try to open the configuration panel of the applet,\n and select the proper audio channel you want to control."), cName);
	return NULL;*/
}

void mixer_get_controlled_element (void)
{
	myData.pControledElement = _mixer_get_element_by_name (myConfig.cMixerElementName);
	if (myData.pControledElement != NULL)
	{
		myData.bHasMuteSwitch = snd_mixer_selem_has_playback_switch (myData.pControledElement);
		
		snd_mixer_selem_get_playback_volume_range (myData.pControledElement, &myData.iVolumeMin, &myData.iVolumeMax);
		cd_debug ("volume range : %d - %d", myData.iVolumeMin, myData.iVolumeMax);
		
		snd_mixer_elem_set_callback (myData.pControledElement, mixer_element_update_with_event);
	}
	if (myConfig.cMixerElementName2 != NULL)
	{
		myData.pControledElement2 = _mixer_get_element_by_name (myConfig.cMixerElementName2);
	}
}

int mixer_get_mean_volume (void)
{
	g_return_val_if_fail (myData.pControledElement != NULL, 0);
	long iVolumeLeft=0, iVolumeRight=0;
	gboolean bHasLeft = snd_mixer_selem_has_playback_channel (myData.pControledElement, SND_MIXER_SCHN_FRONT_LEFT);
	gboolean bHasRight = snd_mixer_selem_has_playback_channel (myData.pControledElement, SND_MIXER_SCHN_FRONT_RIGHT);
	g_return_val_if_fail (bHasLeft || bHasRight, 0);
	
	if (bHasLeft)
		snd_mixer_selem_get_playback_volume (myData.pControledElement, SND_MIXER_SCHN_FRONT_LEFT, &iVolumeLeft);
	if (bHasRight)
		snd_mixer_selem_get_playback_volume (myData.pControledElement, SND_MIXER_SCHN_FRONT_RIGHT, &iVolumeRight);
	cd_debug ("volume : %d;%d", iVolumeLeft, iVolumeRight);
	
	int iMeanVolume = (iVolumeLeft + iVolumeRight) / (bHasLeft + bHasRight);
	
	cd_debug ("myData.iVolumeMin : %d ; myData.iVolumeMax : %d ; iMeanVolume : %d", myData.iVolumeMin, myData.iVolumeMax, iMeanVolume);
	return (100. * (iMeanVolume - myData.iVolumeMin) / (myData.iVolumeMax - myData.iVolumeMin));
}

void mixer_set_volume (int iNewVolume)
{
	g_return_if_fail (myData.pControledElement != NULL);
	cd_debug ("%s (%d)", __func__, iNewVolume);
	int iVolume = ceil (myData.iVolumeMin + (myData.iVolumeMax - myData.iVolumeMin) * iNewVolume / 100.);
	snd_mixer_selem_set_playback_volume_all (myData.pControledElement, iVolume);
	if (myData.pControledElement2 != NULL)
		snd_mixer_selem_set_playback_volume_all (myData.pControledElement2, iVolume);
	myData.iCurrentVolume = iNewVolume;
	mixer_element_update_with_event (myData.pControledElement, 0);  // on ne recoit pas d'evenements pour nos actions.
}

gboolean mixer_is_mute (void)
{
	cd_debug ("");
	g_return_val_if_fail (myData.pControledElement != NULL, FALSE);
	if (snd_mixer_selem_has_playback_switch (myData.pControledElement))
	{
		int iSwitchLeft, iSwitchRight;
		snd_mixer_selem_get_playback_switch (myData.pControledElement, SND_MIXER_SCHN_FRONT_LEFT, &iSwitchLeft);
		snd_mixer_selem_get_playback_switch (myData.pControledElement, SND_MIXER_SCHN_FRONT_RIGHT, &iSwitchRight);
		cd_debug ("%d;%d", iSwitchLeft, iSwitchRight);
		return (iSwitchLeft == 0 && iSwitchRight == 0);
	}
	else
		return FALSE;
}

void mixer_switch_mute (void)
{
	g_return_if_fail (myData.pControledElement != NULL);
	gboolean bIsMute = mixer_is_mute ();
	snd_mixer_selem_set_playback_switch_all (myData.pControledElement, bIsMute);
	if (myData.pControledElement2 != NULL)
		snd_mixer_selem_set_playback_switch_all (myData.pControledElement2, bIsMute);
	myData.bIsMute = ! bIsMute;
	mixer_element_update_with_event (myData.pControledElement, 0);  // on ne recoit pas d'evenements pour nos actions.
}



static void on_change_volume (GtkRange *range, gpointer data)
{
	CD_APPLET_ENTER;
	int iNewVolume = (int) gtk_range_get_value (GTK_RANGE (range));
	cd_debug ("%s (%d)", __func__, iNewVolume);
	mixer_set_volume (iNewVolume);
	CD_APPLET_LEAVE();
}
GtkWidget *mixer_build_widget (gboolean bHorizontal)
{
	g_return_val_if_fail (myData.pControledElement != NULL, NULL);
	GtkWidget *pScale = (bHorizontal ? gtk_hscale_new_with_range (0., 100., .5*myConfig.iScrollVariation) : gtk_vscale_new_with_range (0., 100., .5*myConfig.iScrollVariation));
	if (! bHorizontal)
		gtk_range_set_inverted (GTK_RANGE (pScale), TRUE);  // de bas en haut.
	
	myData.iCurrentVolume = mixer_get_mean_volume ();
	gtk_range_set_value (GTK_RANGE (pScale), myData.iCurrentVolume);
	
	g_signal_connect (G_OBJECT (pScale),
		"value-changed",
		G_CALLBACK (on_change_volume),
		NULL);
	
	return pScale;
}


void mixer_set_volume_with_no_callback (GtkWidget *pScale, int iVolume)
{
	g_signal_handlers_block_matched (GTK_WIDGET(pScale),
		G_SIGNAL_MATCH_FUNC,
		0, 0, NULL, (void*)on_change_volume, NULL);
	gtk_range_set_value (GTK_RANGE (pScale), (double) iVolume);
	g_signal_handlers_unblock_matched (GTK_WIDGET(pScale),
		G_SIGNAL_MATCH_FUNC,
		0, 0, NULL, (void*)on_change_volume, NULL);
}


static gboolean on_button_press_dialog (GtkWidget *widget,
	GdkEventButton *pButton,
	CairoDialog *pDialog)
{
	CD_APPLET_ENTER;
	cairo_dock_dialog_unreference (pDialog);
	myData.pDialog = NULL;
	CD_APPLET_LEAVE(FALSE);
	//return FALSE;
}
static gboolean _on_key_press_dialog (int iClickedButton, GtkWidget *pInteractiveWidget, gpointer *data, CairoDialog *pDialog)
{
	myData.pDialog = NULL;  // le dialogue est dereference donc tout ce qu'on a a faire c'est prendre en compte ce fait !
}
void mixer_show_hide_dialog (void)
{
	if (myDesklet)
		return ;
	if (myData.pDialog == NULL)
	{
		const gchar *cMessage;
		GtkWidget *pScale = NULL;
		if (myData.cErrorMessage != NULL)
			cMessage = myData.cErrorMessage;
		else
		{
			cMessage = D_("Set up volume:");
			pScale = mixer_build_widget (TRUE);
		}
		
		CairoDialogAttribute attr;
		memset (&attr, 0, sizeof (CairoDialogAttribute));
		attr.cText = cMessage;
		attr.pInteractiveWidget = pScale;
		attr.pActionFunc = (CairoDockActionOnAnswerFunc) _on_key_press_dialog;
		myData.pDialog = cairo_dock_build_dialog (&attr, myIcon, myContainer);
		g_signal_connect (G_OBJECT (myData.pDialog->container.pWidget),
			"button-press-event",
			G_CALLBACK (on_button_press_dialog),
			myData.pDialog);
	}
	else
	{
		cairo_dock_dialog_unreference (myData.pDialog);
		myData.pDialog = NULL;
	}
}

gboolean mixer_check_events (gpointer data)
{
	CD_APPLET_ENTER;
	snd_mixer_handle_events (myData.mixer_handle);  // ne renvoie pas d'evenements pour nos actions !
	CD_APPLET_LEAVE(TRUE);
	//return TRUE;
}
