/*
 * Copyright (c) 2001-2003 Shiman Associates Inc. All Rights Reserved.
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "mas/mas.h"
#include "mas/mas_core.h"
#include <gtk/gtk.h>
#include "ruler_hack.h"


/* hacks to get a pleasant window geometry. Figure out a way without
   hard-coding sizes!! */
#define HACK_BAL_WIDTH 65
#define HACK_WIN_HEIGHT 300

/* balance slider snap-to-center area */
#define SNAP_RADIUS 15

#define M_IMAGE "/usr/local/mas/share/pixmaps/M_25px_icon.png"



static struct
{
    gboolean show_rulers;
    gboolean show_values;
} gui_preferences;



/* a channel means a mixer channel here... think "main mix", "mic", "dac"
 * This is a glib style linked list for all available channels */
static GList *channels;


/* the nodes of the linked list of mixer channels. */
typedef struct
{
    mas_device_t anx_device; /* perhaps we'll get cases with more than one.. */
    uint8 id;
    char *name;
    int n_vols; /* "channels": usually 1 (mono) or 2 (stereo) */
    int16 *vols;

    GtkObject *volume_adj;
    GtkObject *balance_adj;
    GtkWidget *balance;
    GtkWidget *ruler;
} channel_info;




/* interface with MAS to get/set volume levels */

int32 get_volume( mas_device_t anx, uint8 channel, int *n_vols, int16 *vols[]);
int32 set_mono_volume( mas_device_t anx, uint8 channel, int16 vol );
int32 set_stereo_volume( mas_device_t anx, uint8 channel,
                         int16 left, int16 right );


/* query MAS for mixer channels and set up the "channels" linked list */ 
int32 get_anx_channels( mas_device_t anx );


/* GUI functions */

/* callbacks */
gint cb_delete_event(GtkWidget *w, GdkEvent *e, gpointer o);
gint cb_slider_moved( GtkWidget *w, gpointer o );
gint cb_balance_slider_moved( GtkWidget *w, gpointer o );
gint cb_pref_button_clicked( GtkWidget *w, gpointer o );

/* creates+adds GUI elements for each mixer channel, sets channel_info->adj */
void setup_channels( GtkWidget *hbox );

/* creates window and calls setup_channels */
void do_window( void );




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


int32
get_volume( mas_device_t anx, uint8 channel, int *n_vols, int16 *vols[] )
{
    struct mas_package pkg;
    struct mas_package nugget;
    char pbuf[1024];
    int i;
    
    masc_setup_package( &pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );
    masc_pushk_uint8( &pkg, "channel", channel );
    masc_finalize_package( &pkg );
    
    mas_get( anx, "gain_linear", &pkg, &nugget );
    masc_strike_package( &pkg );
    
    *n_vols = nugget.members;
    *vols = masc_rtalloc( *n_vols * sizeof(int16) );

    
    switch( nugget.members )
    {
    case 1:
        masc_pullk_int16( &nugget, "mono", *vols );
        break;
        
    case 2:
        masc_pullk_int16( &nugget, "left", *vols );
        masc_pullk_int16( &nugget, "right", *vols+1 );

        break;
        
    default:
        for (i=0; i<nugget.members; i++)
        {
            masc_pull_int16( &nugget, *vols+i );
        }
    } 

    
    masc_strike_package( &nugget );

    return 0;
}


int32
set_mono_volume( mas_device_t anx, uint8 channel, int16 vol )
{
    struct mas_package pkg;
    char pbuf[1024];
    
    masc_setup_package( &pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );
    
    masc_pushk_uint8( &pkg, "channel", channel );
    masc_pushk_int16( &pkg, "mono", vol );
        
    masc_finalize_package( &pkg );
    
    mas_set( anx, "gain_linear", &pkg );
    masc_strike_package( &pkg );
    
    return 0;
}

int32
set_stereo_volume( mas_device_t anx, uint8 channel, int16 left, int16 right )
{
    struct mas_package pkg;
    char pbuf[1024];
    
    masc_setup_package( &pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );
    
    masc_pushk_uint8( &pkg, "channel", channel );
    masc_pushk_int16( &pkg, "left", left );
    masc_pushk_int16( &pkg, "right", right );
    
    masc_finalize_package( &pkg );
    
    mas_set( anx, "gain_linear", &pkg );
    masc_strike_package( &pkg );
    
    return 0;
}


int32
get_anx_channels( mas_device_t anx )
{
    struct mas_package nugget;
    char *val;
    channel_info *ci;    
    int i;
    
    mas_get( anx, "channels", NULL, &nugget );

    channels = g_list_alloc();
    
    for (i=0; i<nugget.members; i++)
    {
        /* we know the members are strings */

        masc_pull_string( &nugget, &val, TRUE );
        
        ci = g_malloc( sizeof(channel_info) );

        ci->anx_device = anx;
        ci->id = i;
        ci->name = val;

        get_volume( anx, i, &(ci->n_vols), &(ci->vols) );
        
        channels = g_list_append( channels, (gpointer)ci );
    }
    
    masc_strike_package( &nugget );

    return 0;
}


gint
cb_delete_event(GtkWidget *w, GdkEvent *e, gpointer o)
{
    gtk_widget_hide_all( GTK_WIDGET(o) ); 
    
    gtk_main_quit();
    return TRUE;
}


gint
cb_slider_moved( GtkWidget *w, gpointer o )
{
    int16 v;
    float bal;
    
    channel_info *ci;

    ci = (channel_info*)o;
    
    v   = 100 - (int16) GTK_ADJUSTMENT(ci->volume_adj)->value;    


    if( ci->n_vols==2 )
    {
        bal = GTK_ADJUSTMENT(ci->balance_adj)->value / 50;
    
        if( bal==0 )
        {
            set_stereo_volume( ci->anx_device, ci->id, v, v );
            return TRUE;
        }
        
        if( bal<0 )
        {
            bal = v * (1+bal);
            set_stereo_volume( ci->anx_device, ci->id, v, (int16) bal );
        }
        else
        {
            bal = v * (1-bal);
            set_stereo_volume( ci->anx_device, ci->id, (int16) bal, v );
        }
    }
    else
    {
        set_mono_volume( ci->anx_device, ci->id, v );
    }
        
    return TRUE;
}


gint
cb_balance_slider_moved( GtkWidget *w, gpointer o )
{
    float r;
    int16 v;
    channel_info *ci;
    
    ci = (channel_info*)o;
    
    v = (int16) GTK_ADJUSTMENT(ci->balance_adj)->value;    

    if( (v>-SNAP_RADIUS)&&(v<SNAP_RADIUS) )
        gtk_adjustment_set_value( GTK_ADJUSTMENT(ci->balance_adj), 0 );

    r=v/50.0;

    v = (int16) GTK_ADJUSTMENT(ci->volume_adj)->value;    
    v = 100 - v;

    if( r==0 )
    {
        set_stereo_volume( ci->anx_device, ci->id, v, v );
        return TRUE;
    }
    
    
    
    if( r<0 )
    {
        r = v * (1+r);
        set_stereo_volume( ci->anx_device, ci->id, v, (int16) r );
    }
    else
    {
        r = v * (1-r);
        set_stereo_volume( ci->anx_device, ci->id, (int16) r, v );
    }
    
    
    return TRUE;    
}


gint
cb_pref_button_clicked( GtkWidget *w, gpointer o )
{
    channel_info *ci;
    GList *current;

    gui_preferences.show_rulers = 1 - gui_preferences.show_rulers;
    
    current = channels;
    while( (current = g_list_next(current)) )
    {
        ci = (channel_info*)current->data;
        if( gui_preferences.show_rulers )
        {
            gtk_widget_show( ci->ruler );
            if( ci->n_vols==2 )
                gtk_widget_show( ci->balance );
        }
        else
        {
            gtk_widget_hide( ci->ruler );
            if( ci->n_vols==2 )
                gtk_widget_hide( ci->balance );
        }
        
    }
    
    
    return TRUE;
    
}


void setup_channels( GtkWidget *hbox )
{
    GtkWidget *vbox;
    GtkWidget *ruler_slider_box, *hlpbox;
    GtkWidget *label;
    GtkWidget *scale;
    GtkWidget *balance;
    GtkWidget *ruler;
    
    channel_info *ci;
    GList *current;

    current = channels;
    while( (current = g_list_next(current)) )
    {
        ci = (channel_info*)current->data;
        
        vbox = gtk_vbox_new( FALSE, 10 );
        
        ci->volume_adj = gtk_adjustment_new( (gdouble)( 100-ci->vols[0] ),
                                             0.0, 100.0, 1.0, 10.0, 0.0 );

        ci->balance_adj = gtk_adjustment_new( 0.0,
                                             -50.0, 50.0, 1.0, 10.0, 0.0 );


        label = gtk_label_new( ci->name );
        gtk_label_set_justify( GTK_LABEL(label), GTK_JUSTIFY_RIGHT );
        
        scale = gtk_vscale_new( GTK_ADJUSTMENT(ci->volume_adj) );
        gtk_scale_set_draw_value( GTK_SCALE(scale), FALSE );
        
        
        gtk_signal_connect( GTK_OBJECT(ci->volume_adj), "value-changed",
                            GTK_SIGNAL_FUNC(cb_slider_moved), (gpointer) ci );

        

        ruler_slider_box = gtk_hbox_new( FALSE, 0 );

        /* hlpbox is a cosmetic hack: we want no space between the ruler
           and the slider, even when the label is wider than
           ruler_slider_box */
        hlpbox  = gtk_hbox_new( TRUE, 0 );
        
        ruler = gtk_ppmscale_new( 100, 3 );
        ci->ruler = ruler;
        
        gtk_box_pack_start( GTK_BOX(ruler_slider_box), ruler, TRUE, TRUE, 0 );
        gtk_box_pack_start( GTK_BOX(ruler_slider_box), scale, TRUE,FALSE, 0 );

        gtk_box_pack_start( GTK_BOX(hlpbox), ruler_slider_box, TRUE, FALSE,0 );
        
        gtk_box_pack_start( GTK_BOX(vbox), label, FALSE, FALSE, 0 );
        gtk_box_pack_start( GTK_BOX(vbox), hlpbox, TRUE, TRUE, 0 );


        if( ci->n_vols==2 )
        {
            balance = gtk_hscale_new( GTK_ADJUSTMENT(ci->balance_adj) );
            ci->balance = balance;

            /* HACK! we shouldn't set any size explicitly */
            gtk_widget_set_size_request( balance, HACK_BAL_WIDTH, -1 );
            
            gtk_scale_set_draw_value( GTK_SCALE(balance), FALSE );
            gtk_signal_connect( GTK_OBJECT(ci->balance_adj), "value-changed",
                                GTK_SIGNAL_FUNC(cb_balance_slider_moved),
                                (gpointer) ci );
            
            gtk_box_pack_start( GTK_BOX(vbox), balance, FALSE, FALSE, 0 );
        }
        
        gtk_box_pack_start( GTK_BOX(hbox), vbox, FALSE, FALSE, 0 );
    }
}


void do_window( void )
{
    GtkWidget *hbox;
    GtkWidget *vbox;
    GtkWidget *hbox2;
    GtkWidget *pref_button;    
    GtkWidget *main_window;
    GtkWidget *image;
    char *title;
    
    gtk_init( NULL, NULL ); /* &argc, &argv */

    main_window = gtk_window_new( GTK_WINDOW_TOPLEVEL );

    title = masc_construct_title( "MAS Hardware Mixer");
    if( title )
    {
        gtk_window_set_title ( GTK_WINDOW (main_window), title );
        masc_rtfree( title );
    }
    else
    {
        gtk_window_set_title ( GTK_WINDOW (main_window), "MAS Hardware Mixer");
    }
    
    
    gtk_window_set_policy( GTK_WINDOW(main_window), TRUE, TRUE, TRUE );  

    gtk_window_set_default_size( GTK_WINDOW(main_window), -1,
                                 HACK_WIN_HEIGHT );
    

    gtk_signal_connect( GTK_OBJECT(main_window), "delete_event", 
                        GTK_SIGNAL_FUNC(cb_delete_event),
                        (gpointer)main_window );
    
    vbox  = gtk_vbox_new ( FALSE, 10 );
    hbox  = gtk_hbox_new ( TRUE, 10 );
    hbox2 = gtk_hbox_new ( FALSE, 10 );

    gtk_box_pack_start( GTK_BOX(vbox),  hbox, TRUE, TRUE, 0 );

    
    image = gtk_image_new_from_file( M_IMAGE );
    gtk_box_pack_start( GTK_BOX(hbox2), image, FALSE, FALSE, 0 );
    
    pref_button = gtk_button_new_with_label( " preferences " );
    gtk_signal_connect( GTK_OBJECT(pref_button), "clicked",
                        GTK_SIGNAL_FUNC(cb_pref_button_clicked),
                        (gpointer) 0 );

    
    gtk_box_pack_end( GTK_BOX(hbox2), pref_button, FALSE, FALSE, 0 );
    gtk_container_set_border_width( GTK_CONTAINER(hbox2), 5 );
 
    gtk_box_pack_start( GTK_BOX(vbox),  hbox2, FALSE, FALSE, 0 );
    
    gtk_container_add( GTK_CONTAINER(main_window), vbox );

    setup_channels( hbox );
    
    gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);

    gtk_widget_show_all(main_window);

    return;
}



    

int
main(int argc, char* argv[])
{
    int32 err;
    mas_device_t  device;
    char name[256];


    gui_preferences.show_rulers = gui_preferences.show_values = 1;
    
    /* initiate contact with MAS */
    err = mas_init();
    if (err < 0)
    {
	printf("\nconnection with server failed.\n");
	exit(1);
    }
    
    err = mas_asm_get_device_by_name( "anx", &device );
    if ( err < 0 )
    {
        masc_logerror( err, "can't retrieve device handle for '%s'", name);
        exit(1);
    }

    get_anx_channels( device );

    do_window();
    
    gtk_main();
        
    exit(0);
}
