/*
 * 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"

#define HACK_WIN_HEIGHT 300
#define SPIEL 24
#define QUERY_MIX_EPSILON 5

#define M_IMAGE "/usr/local/mas/share/pixmaps/M_25px_icon.png"
/* -40, hard coded in the mix device */
#define DB_CUTOFF 40  
#define FACT 10.0

mas_device_t mix;
GtkWidget *hbox;
GtkWidget *main_window;

int query_mix_device( gpointer o );



struct channel_info
{
    struct channel_info *next;
   
    /* uint8 id; */
    int32 portnum;
    char *name;
    int16 db;
    int16 vol;
    uint8 snum;
    int8 updated;

    GtkObject *volume_adj;
    GtkWidget *cont;
};


/* a channel means a mixer channel here... */
struct channel_info *channels;



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


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;
    struct mas_package package;
    char pbuf[128];
    
    struct channel_info *ci;
    
    ci = (struct channel_info*)o;
    v  = (int16) GTK_ADJUSTMENT(ci->volume_adj)->value;
    ci->db = v;
    
    masc_setup_package( &package, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );
    masc_push_int32( &package, ci->portnum );
    masc_push_int16( &package, -ci->db );
    masc_finalize_package( &package );
    
    mas_set( mix, "gain_db", &package );
    masc_strike_package( &package );
    
    return TRUE;
}

void
add_slider( int32 portnum, char *c_name, int16 db, int8 snum )
{
    GtkWidget *vbox;
    GtkWidget *ruler_slider_box, *hlpbox;
    GtkWidget *label;
    GtkWidget *scale;
/*     GtkWidget *balance; */
    GtkWidget *ruler;
    struct channel_info *ci, *current;

    current = channels;
    while( current->next ){ current=current->next; }
    
    ci = (struct channel_info*)masc_rtalloc( sizeof(struct channel_info) );
    current->next = ci;
    ci->next = 0;
    
    ci->portnum = portnum;
    ci->name = c_name;
    ci->snum = snum;
    ci->db = db;
    ci->updated = 1;
    
    vbox = gtk_vbox_new( FALSE, 10 );
    ci->cont = vbox;
    
    ci->volume_adj = gtk_adjustment_new( (gdouble)( FACT*ci->db ),
                                         0.0, FACT*DB_CUTOFF, 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( 10*DB_CUTOFF, 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), hlpbox, TRUE, TRUE, 0 );
    gtk_box_pack_start( GTK_BOX(vbox), label, FALSE, FALSE, 0 );
    
    gtk_box_pack_start( GTK_BOX(hbox), vbox, FALSE, FALSE, 0 );

    gtk_widget_show( ruler );
    gtk_widget_show( scale );
    gtk_widget_show( label );
    gtk_widget_show( ruler_slider_box );
    gtk_widget_show( hlpbox );
    gtk_widget_show( vbox );
    
/*     gtk_widget_queue_resize( GTK_WIDGET(main_window) ); */
    /* gtk_container_resize_children( GTK_CONTAINER(hbox) ); */
    
/*     gtk_widget_hide( hbox ); */
}



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

    main_window = gtk_window_new( GTK_WINDOW_TOPLEVEL );

    title = masc_construct_title( "MAS 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 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 );

    query_mix_device( NULL );
    
    gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);

    gtk_widget_show_all(main_window);

    return;
}

int
update_slider( int32 portnum, char* c_name, int16 db )
{
    struct channel_info *current;
    
    current = channels;
    while( (current = current->next) )
    {
        if( current->portnum==portnum )
        {
            if( abs(db - GTK_ADJUSTMENT(current->volume_adj)->value) > QUERY_MIX_EPSILON )
            {
                gtk_adjustment_set_value( GTK_ADJUSTMENT(current->volume_adj), (gdouble)(-db) );
            }
            current->updated = 1;
            return 1;
        }
    }
    return 0;
}


void
remove_old_sliders( )
{
    struct channel_info *ci, *tmp;
    
    ci = channels;
    while ( (ci=ci->next) )
    {
        if ( ! ci->updated )
        {
            gtk_container_remove( GTK_CONTAINER(hbox), ci->cont );

            
            tmp = channels;
            while( tmp->next )
            {
                if( tmp->next == ci )
                {
                    tmp->next = ci->next; /* todo: got to free
                                           * ci... but careful! it's
                                           used in the while condition */
                    break;
                }
                tmp = tmp->next;
            }
        }
        else
        {
            ci->updated = 0;
        }
    }
}


/* I hate to have to do this explicitly. Hmm well, at least ask the
   widgets how large they'd like to be */
void
resize_main_window( void )
{
    GtkRequisition req;
    struct channel_info *current;
    int w;

    current = channels;
    w = 0;
    while( (current = current->next) )
    {
        gtk_widget_size_request( current->cont, &req );
        w += req.width;
    }
    
    gtk_window_resize( GTK_WINDOW(main_window), SPIEL + w, HACK_WIN_HEIGHT );
}


        
int
query_mix_device( gpointer o )
{
    struct mas_package package, sinkpack;
    static int num=0;
    int i, j = 0;

/*     gtk_widget_show( hbox ); */
    
    mas_get( mix, "sinksinfo", NULL, &package );
    for ( i=0; i<package.members; i++ )
    {
        char* c_name;
        int32 portnum;
        int16 db;
        uint8 snum;
        void *data;
        
        masc_pull_package( &package, &sinkpack, FALSE );
        masc_pullk_string( &sinkpack, "n", &c_name, FALSE );
        masc_pullk_int32( &sinkpack, "pn", &portnum );
        masc_pullk_int16( &sinkpack, "db", &db );
        masc_pullk_uint8( &sinkpack, "s", &snum );
        masc_strike_package( &sinkpack );

        for (j=0; j<strlen(c_name) && c_name[j] != '('; )
            j++;
        if ( c_name[j] == '(' )
            c_name[j] = 0;
        
        if ( !update_slider( portnum, c_name, db ) )
        {
            add_slider( portnum, c_name, db, snum );
        }
    }

    remove_old_sliders();

    if( num != package.members )
    {
        num = package.members;
        resize_main_window();
    }
    
    return TRUE;
}


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


    /* gui_preferences.show_rulers = gui_preferences.show_values = 1; */


    channels = (struct channel_info*) masc_rtalloc( sizeof(struct channel_info) );
    channels->next = 0;

    /* 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( "mix", &mix );
    if ( err < 0 )
    {
        masc_logerror( err, "can't retrieve device handle for '%s'", name);
        exit(1);
    }

/*     get_anx_channels( device ); */
    
    do_window();
    gtk_timeout_add( 1000, query_mix_device, NULL );
    
    gtk_main();
        
    exit(0);
}
