/*
 * Copyright (c) 2006 Alvaro Lopes <alvieboy@alvie.com>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"
#include "cellmodem.h"
#include "modem_driver.h"
#include "preferences.h"
#include "pin_helper.h"

/* Prototypes */

static gboolean cellmodem_t_send_at_command( cellmodem_t *monitor,
					     modem_reply_callback_t callback,
					     const gchar *command );
static gboolean get_network_info( cellmodem_t *monitor );

static void cellmodem_t_identify_network( cellmodem_t *monitor );
static gboolean cellmodem_t_close_modem(cellmodem_t *monitor /*, gboolean failure */);
static gboolean reschedule_open( cellmodem_t *monitor /*, gboolean failure */);
static gboolean cellmodem_t_at_command_timeout( cellmodem_t *monitor );
static void cellmodem_t_cancel_pending_at_command_timeout( cellmodem_t *monitor );
static void cellmodem_t_get_quality( cellmodem_t *monitor );
static void cellmodem_t_get_pin_status( cellmodem_t *monitor );
static void cellmodem_t_send_pin( cellmodem_t *monitor, const gchar *pin );
static void cellmodem_t_set_tooltip_info( cellmodem_t *monitor );
static void cellmodem_t_get_ohcip_status( cellmodem_t *monitor );
static gboolean cellmodem_button_event(GtkEventBox *box, GdkEventButton *button, cellmodem_t *monitor);
static gboolean cellmodem_t_open_modem(cellmodem_t *monitor);
static void cellmodem_t_switch_status( cellmodem_t *monitor, modem_status_t status );



/* End Proto */

/**
 * @brief [cellmodem_t method] Set display modem error
 *
 * @param monitor The monitor object
 *
 * @return TRUE if c starts with 'ERROR', FALSE otherwise
 */


static void cellmodem_t_set_error( cellmodem_t *monitor )
{
    led_t_set_color( monitor->status_led, LED_RED );
    led_t_set_flashing( monitor->status_led, TRUE );
    led_t_set_color( monitor->network_led, LED_OFF );
    led_t_set_flashing( monitor->network_led, FALSE );
}

static void cellmodem_t_set_ok( cellmodem_t *monitor )
{
    led_t_set_color( monitor->status_led, LED_GREEN );
    led_t_set_flashing( monitor->status_led, FALSE );
}

static gboolean cellmodem_t_in_error_status( cellmodem_t *monitor )
{
    return monitor->modem_status == MODEM_ERROR;
}

static void cellmodem_t_switch_to_error( cellmodem_t *monitor,
					const char *fmt, ... )
{
    va_list ap;
    va_start( ap, fmt );

    if ( monitor->lasterror != NULL ) {
	g_free( monitor->lasterror );
    }

    vasprintf( &monitor->lasterror, fmt, ap );

    va_end( ap );

    cellmodem_t_switch_status( monitor, MODEM_ERROR );
    cellmodem_t_set_tooltip_info( monitor );
}

static void cellmodem_t_switch_to_error_and_restart( cellmodem_t *monitor,
						    const char *fmt, ... )
{
    va_list ap;
    va_start( ap, fmt );

    if ( monitor->lasterror != NULL ) {
	g_free( monitor->lasterror );
    }

    vasprintf( &monitor->lasterror, fmt, ap );

    va_end( ap );

    cellmodem_t_switch_status( monitor, MODEM_ERROR );
    cellmodem_t_set_tooltip_info( monitor );
    reschedule_open( monitor );
}


static void cellmodem_t_switch_status( cellmodem_t *monitor,
				      modem_status_t status )
{
    DEBUG("Switching modem status from %d to %d",
          monitor->modem_status, status );

    switch ( status ) {
    case MODEM_ERROR:
	cellmodem_t_set_error( monitor );
	break;
    case MODEM_WAIT_CSQ_RESPONSE:
    case MODEM_WAIT_CPIN_RESPONSE:
    case MODEM_WAIT_COPS_RESPONSE:
    case MODEM_WAIT_PINE_RESPONSE:
    case MODEM_CLOSED:
	cellmodem_t_set_ok( monitor );
	break;
    case MODEM_WAIT_CREG_RESPONSE:
    case MODEM_WAIT_REGISTRATION:
    case MODEM_WAIT_OHCIP_RESPONSE:
        break;
    }

    monitor->modem_status = status;
}

static void cellmodem_t_switch_network_status( cellmodem_t *monitor,
					      registration_type_t status )
{
    switch (status) {
    case REGISTRATION_UNKNOWN:
	led_t_set_color( monitor->network_led, LED_YELLOW );
	led_t_set_flashing( monitor->network_led, FALSE );
	break;
    case REGISTRATION_NOT_REGISTERED:
	led_t_set_color( monitor->network_led, LED_RED );
	led_t_set_flashing( monitor->network_led, TRUE );
	break;
    case REGISTRATION_REGISTERING:
	led_t_set_color( monitor->network_led, LED_GREEN );
	led_t_set_flashing( monitor->network_led, TRUE );
	break;
    case REGISTRATION_NEEDS_PIN:
	led_t_set_color( monitor->network_led, LED_YELLOW );
	led_t_set_flashing( monitor->network_led, TRUE );
	break;
    case REGISTRATION_HSDPA:
	led_t_set_color( monitor->network_led, LED_BLUE );
	led_t_set_flashing( monitor->network_led, FALSE );
        break;
    default:
	led_t_set_color( monitor->network_led, LED_GREEN );
	led_t_set_flashing( monitor->network_led, FALSE );
    }

    monitor->registration_status = status;

    cellmodem_t_set_tooltip_info( monitor );
}

/**
 * @brief Check if buffer is an error reply from modem
 *
 * @param c The buffer to check
 *
 * @return TRUE if c starts with 'ERROR', FALSE otherwise
 */

static gboolean
is_AT_error_reply( const gchar *c )
{
    if ( strcmp(c,"ERROR") == 0 )
	return TRUE;

    /* This is sometimes issued when
     the card is still initializing */
    /*
     Got response: '+CME ERROR: SIM interface not started yet' command 'AT+CPIN?'
     */
    if ( strncmp(c,"+CME ERROR",10) == 0 )
	return TRUE;
    if ( strcmp(c,"NO CARRIER") == 0 )
	return TRUE;
    if ( strcmp(c,"BUSY") == 0 )
	return TRUE;
    if ( strcmp(c,"NO DIALTONE") == 0 )
	return TRUE;

    return FALSE;
}

/**
 * @brief Check if buffer is a success reply from modem
 *
 * @param c The buffer to check
 *
 * @return TRUE if c starts with 'OK', FALSE otherwise
 */

static gboolean
is_AT_success_reply( const gchar *c )
{
    return strcmp(c,"OK") == 0;
}
/**
 * @brief Remove leading characters from string
 *
 * This will remove all leading chars in string that match the parameter.
 *
 * @param buffer The string where to remove the characters
 * @param c The character
 *
 * @return A new pointer to the first valid char
 */

static char *
remove_leading_chars( char *buffer, const char *c )
{
    size_t position = 0;

    while ( buffer[position] > 0 ) {
	if ( strchr( c, buffer[position] ) > 0 ) {
            position++;
	} else {
            break;
	}
    }
    return buffer + position;
}
/**
 * @brief Remove trailing characters from string
 *
 * This will remove all trailing chars in string that match the parameter.
 *
 * @param buffer The string where to remove the characters
 * @param c The characters to remove
 *
 * @return Nothing
 */

static void
remove_trailing_chars( char *buffer, const char *c )
{
    size_t position = strlen(buffer);
    do {
	if ( strchr( c, buffer[position] ) > 0 ) {
	    buffer[ position ] = '\0';
            position--;
	} else {
            break;
	}
    } while ( position > 0 );
}

/**
 * @brief Check if string is empty or contains only spaces
 *
 * @param buffer The string where to remove the characters
 *
 * @return TRUE if only space chars found, FALSE otherwise
 */

static gboolean
is_only_spaces( const char *buffer )
{
    int i;

    if ( strlen( buffer )==0 )
	return TRUE;
    
    for (i=0; i<strlen(buffer); i++) {
	if (buffer[i]!=' ')
	    return FALSE;
    }
    return TRUE;
}

/**
 * @brief [cellmodem_t method] Reset the internal RX buffer 
 * @param monitor The cellmodem object
 * @return Nothing
 */

static void cellmodem_t_reset_buffer( cellmodem_t *monitor )
{
    monitor->line_buffer_size = 0;
}


/**
 * @brief Set the plugin size
 * DOCUMENT ME PLEASE
 */

static gboolean
cellmodem_set_size(XfcePanelPlugin *plugin, int size, cellmodem_t *monitor)
{
    DEBUG("Set size request");
    if (xfce_panel_plugin_get_orientation (plugin) == GTK_ORIENTATION_HORIZONTAL)
    {
	gtk_widget_set_size_request(GTK_WIDGET( monitor->qualpbar ),
				    BORDER, size - 4);
    }
    else
    {
	gtk_widget_set_size_request(GTK_WIDGET( monitor->qualpbar ),
				    size - 4, BORDER);
    }
    DEBUG("Done set size request");
    return TRUE;
}


/**
 * @brief [cellmodem_t method] Setup the main widgets for the panel plugin
 * @param monitor The cellmodem object
 * @param destroy If true, destroy the already instanciated objects [will be deprecated]
 */

static void
cellmodem_t_setup_widgets( cellmodem_t *monitor, gboolean destroy )
{
    monitor->status_led =
	led_t_new();
    monitor->network_led =
	led_t_new();

    led_t_set_color( monitor->status_led, LED_OFF );

    led_t_set_color( monitor->network_led, LED_OFF );


    /*   led_t_set_flashing( monitor->testled, TRUE );*/

    GtkOrientation orientation =
	xfce_panel_plugin_get_orientation(monitor->plugin);

    monitor->tooltips = gtk_tooltips_new();
    g_object_ref(monitor->tooltips);
    gtk_object_sink(GTK_OBJECT(monitor->tooltips));

    monitor->eventbox = gtk_event_box_new();
    monitor->qualpbar = gtk_progress_bar_new();

    if (orientation == GTK_ORIENTATION_HORIZONTAL)
    {
	gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(monitor->qualpbar),
					 GTK_PROGRESS_BOTTOM_TO_TOP);

	monitor->gbox = gtk_hbox_new(FALSE, 0);
	monitor->box=gtk_hbox_new(FALSE, 0);
    } else {
	gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(monitor->qualpbar),
					 GTK_PROGRESS_LEFT_TO_RIGHT);

	monitor->gbox = gtk_vbox_new(FALSE, 0);
	monitor->box=gtk_vbox_new(FALSE, 0);
    }


    gtk_container_set_border_width(GTK_CONTAINER(monitor->box), BORDER/2);

    gtk_box_pack_start(GTK_BOX(monitor->box),
		       GTK_WIDGET(monitor->qualpbar),
		       FALSE, FALSE, 0);


    /* LEDS */
    GtkWidget *led_box;

    if (orientation == GTK_ORIENTATION_HORIZONTAL)
    {
        led_box = gtk_vbox_new(FALSE,0);
    } else {
	led_box = gtk_hbox_new(FALSE,0);
    }
    

    gtk_box_pack_start(GTK_BOX(led_box),
		       GTK_WIDGET(monitor->status_led->image),
		       FALSE, FALSE, 0);
    gtk_widget_show( monitor->status_led->image );

    gtk_box_pack_start(GTK_BOX(led_box),
		       GTK_WIDGET(monitor->network_led->image),
		       FALSE, FALSE, 0);
    gtk_widget_show( monitor->network_led->image );

    gtk_box_pack_start(GTK_BOX(monitor->box),
                       led_box, FALSE,FALSE,0);

    gtk_box_set_spacing(GTK_BOX(led_box), BORDER/4);


    gtk_widget_show( led_box );

    gtk_box_pack_start(GTK_BOX(monitor->gbox),
		       GTK_WIDGET(monitor->box), FALSE, FALSE, 0);


    gtk_widget_show( monitor->box );
    gtk_widget_show( monitor->gbox );

    gtk_widget_show( monitor->qualpbar );

    gtk_container_add(GTK_CONTAINER(monitor->eventbox),
		      GTK_WIDGET(monitor->gbox));

    gtk_widget_show(monitor->eventbox);
    gtk_widget_set_size_request(monitor->eventbox, -1, -1);

    gtk_signal_connect( GTK_OBJECT(monitor->eventbox), "button-press-event", G_CALLBACK (cellmodem_button_event), monitor );

}
/**
 * @brief [cellmodem_t CTOR] Create a new cellmodem object
 * Creates the new cellmodem object, setting up some default values
 *
 * @param plugin The relevant XfcePanelPlugin
 * @return The newly created cellmodem object
 */

static cellmodem_t *
cellmodem_t_new(XfcePanelPlugin *plugin)
{
    cellmodem_t *monitor;

    monitor = g_new0(cellmodem_t, 1);

    monitor->plugin = plugin;
    monitor->lasterror = NULL;
    monitor->lastcmd = g_new( gchar, 128 );

    monitor->options.modem_device = NULL;
    monitor->options.critical_threshold = 20;
    monitor->options.low_threshold = 40;
    monitor->options.max_quality = MAX_QUAL;


    /* Setup colors */

    gdk_color_parse("#ff0000", &(monitor->red_color));
    gdk_color_parse("#ffff00", &(monitor->yellow_color));
    gdk_color_parse("#00ff00", &(monitor->green_color));

    cellmodem_t_setup_widgets( monitor, FALSE );

    cellmodem_t_switch_to_error( monitor, _("Initializing plugin now") );

    cellmodem_t_switch_network_status(  monitor, REGISTRATION_UNKNOWN );

    return monitor;
}

/**
 * @brief [cellmodem_t member] Update tooltips according to status
 *
 * @param monitor The cellmodem object
 */

static void
cellmodem_t_set_tooltip_info( cellmodem_t *monitor )
{
    gchar buffer[512];

    gint qp = monitor->quality*100;
    gint q = monitor->signal_strength;

    if ( cellmodem_t_in_error_status ( monitor ) )
    {
	g_snprintf( buffer, 512, _("Error detected:\n%s"), monitor->lasterror );
    }
    else {
	switch( monitor->registration_status )
	{
	case REGISTRATION_NOT_REGISTERED:
	    g_snprintf(buffer, 512, _("Not registered"));
	    break;
	case REGISTRATION_NEEDS_PIN:
	    g_snprintf(buffer, 512, _("Modem needs PIN"));
	    break;
	case REGISTRATION_REGISTERING:
	    g_snprintf(buffer, 512, _("Registering"));
	    break;
	case REGISTRATION_GPRS:
	    g_snprintf(buffer, 512, _("Registered [GPRS] to %s\nQuality: %d (%d%%)"), monitor->network, q,qp );
            break;
	case REGISTRATION_UMTS:
	    g_snprintf(buffer, 512, _("Registered [UMTS] to %s\nQuality: %d (%d%%)"), monitor->network,q,qp );
            break;
	case REGISTRATION_HSDPA:
	    g_snprintf(buffer, 512, _("Registered [HSDPA] to %s\nQuality: %d (%d%%)"), monitor->network,q,qp );
	    break;
	case REGISTRATION_UNKNOWN:
	    g_snprintf(buffer, 512, _("Registered to %s\nQuality: %d (%d%%)"), monitor->network,q,qp);
            break;
	}
    }

    gtk_tooltips_set_tip (monitor->tooltips, monitor->eventbox, buffer, NULL);
}


/**
 * @brief [cellmodem_t member] Set the quality bar color
 *
 * @param monitor The cellmodem object
 * @param color The required GdkColor * color
 */


static void
cellmodem_t_set_bar_color(cellmodem_t *monitor, GdkColor *color )
{
    gtk_widget_modify_bg( monitor->qualpbar, GTK_STATE_PRELIGHT, color );
}

/**
 * @brief [cellmodem_t member] Update the progress bar
 *
 * @param monitor The cellmodem object
 */


static void
cellmodem_t_update_progressbar( cellmodem_t *monitor )
{
    gint qual = (gint)(100.0*monitor->quality);

    if ( monitor->registration_status == REGISTRATION_NOT_REGISTERED ) {
	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(monitor->qualpbar), 0.0 );
	cellmodem_t_set_bar_color( monitor, NULL );
    }
    else {

	if( qual <= monitor->options.critical_threshold ) {
	    cellmodem_t_set_bar_color( monitor, &(monitor->red_color) );
	}
	else if( qual <= monitor->options.low_threshold ) {
	    cellmodem_t_set_bar_color( monitor, &(monitor->yellow_color) );
	}
	else {
	    cellmodem_t_set_bar_color( monitor, &(monitor->green_color) );
	}
    }

    gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(monitor->qualpbar), monitor->quality );

}
/**
 * @brief [cellmodem_t member] Update the monitors
 *
 * This will update the progressbar and the tooltips
 *
 * @param monitor The cellmodem object
 */

static void
cellmodem_t_update_monitors( cellmodem_t *monitor )
{
    cellmodem_t_set_tooltip_info( monitor );
    cellmodem_t_update_progressbar( monitor );
}

/**
 * @brief [cellmodem_t DTOR] Destroy the cellmodem object
 *
 * @param monitor The cellmodem object
 *
 * @return Nothing
 */

static void
cellmodem_t_delete(cellmodem_t *monitor)
{
    DEBUG("Destroying cellmodem object");
    cellmodem_t_close_modem( monitor /*, TRUE*/ );

    g_object_unref (monitor->tooltips);

    g_free( monitor->lastcmd );

    g_free(monitor);
}

/**
 * @brief [cellmodem_t member] Close the modem channel
 *
 * @param monitor The cellmodem object
 *
 * @return TRUE if modem sucessfuly closed, FALSE if not opened or error
 */

static gboolean
cellmodem_t_close_modem(cellmodem_t *monitor  /*, gboolean failure */)
{
    if (NULL == monitor->modem_instance)
	return FALSE;

    if (NULL == monitor->driver)
	return FALSE;

    monitor->driver->close(monitor->modem_instance);

    return TRUE;
}

static void pin_callback( const gchar *pin, void *pvt )
{
    cellmodem_t *monitor = (cellmodem_t *)pvt;

    cellmodem_t_send_pin( monitor, pin );
}

static void
cellmodem_t_request_pin( cellmodem_t *monitor )
{
    /*
     if ( monitor->options.ask_for_pin )
	pin_helper_launch( GTK_WIDGET(monitor->plugin), pin_callback, monitor );
	else {
        */
    cellmodem_t_switch_status( monitor, MODEM_CLOSED );
    cellmodem_t_switch_network_status( monitor, REGISTRATION_NEEDS_PIN );

    /* End here */

    cellmodem_t_close_modem( monitor );

}


/**
 * @brief [cellmodem_t pseudo-member] Callback for registration request
 *
 * @param success TRUE if modem replied OK, FALSE otherwise
 * @param response GString with modem response
 * @param pvt Pointer to cellmodem object
 *
 * pvt will be deferenced to cellmodem object upon calling. No typechecking is done.
 *
 *
 * @return Nothing
 */

static void
cellmodem_t_registration_callback(gboolean success, GString *response, void *pvt)
{
    cellmodem_t *monitor = (cellmodem_t*)pvt;

    if ( !success || response==NULL || response->str==NULL ) {
	/* Error */
	cellmodem_t_switch_to_error_and_restart( monitor, _("Error in modem reply to CREG") );
	return;
    }

    DEBUG("Got registration callback: %s", response->str);
    if ( strncmp( response->str ,"+CREG: ", 7 )==0 ) {

	gchar *start = response->str + 7;
	if ( strtok( start, ",") == NULL) {
	    cellmodem_t_switch_to_error_and_restart( monitor, _("Invalid reply to +CREG") );
	    return;
	}
	gchar *delim = strtok ( NULL, "," );

	int i = atoi( delim );

	DEBUG("Registration reply: %d", i);

	switch (i) {
	case 1:

	    /* We only should switch status here after +COPS response */

	    cellmodem_t_switch_network_status( monitor, REGISTRATION_UNKNOWN );
	    break;
	case 2:
	    cellmodem_t_switch_network_status( monitor, REGISTRATION_REGISTERING );
	    break;
	default:
	    cellmodem_t_switch_network_status( monitor, REGISTRATION_NOT_REGISTERED );
            break;
	}
    } else {
	DEBUG("Modem error in reply");
	cellmodem_t_switch_to_error_and_restart( monitor, _("Invalid CREG reply from modem") );
        return;
    }


    if ( monitor->registration_status != REGISTRATION_NOT_REGISTERED  &&
         monitor->registration_status != REGISTRATION_REGISTERING ) {

	cellmodem_t_identify_network( monitor );

    } else {
	/* Check for PIN ????? */
	cellmodem_t_get_pin_status( monitor );
	/*else
	 reschedule_open( monitor, FALSE );
	 */
    }
}
/**
 * @brief [cellmodem_t pseudo-member] Callback for network id request
 *
 * @param success TRUE if modem replied OK, FALSE otherwise
 * @param response GString with modem response
 * @param pvt Pointer to cellmodem object
 *
 * pvt will be deferenced to cellmodem object upon calling. No typechecking is done.
 *
 * @return Nothing
 */

static void
cellmodem_t_identify_callback(gboolean success, GString *response, void *pvt)
{
    cellmodem_t *monitor = (cellmodem_t*)pvt;

    if ( !success || response==NULL || response->str==NULL ) {
	/* Error */
        DEBUG("Modem error detected (%p)", pvt);
	cellmodem_t_switch_to_error_and_restart( monitor, _("Error in modem reply to COPS") );
        return;
    }

    /* +COPS: 0,0,"Provider",2 */
    if ( strncmp(response->str,"+COPS: ", 7 )==0 ) {
	gchar *start = response->str + 7;
	gchar *tok[64];
	int i=0;

	tok[i++] = strtok( start, ",");
	while ( ( tok[i++]=strtok(NULL,",")) ) {};

	DEBUG("Network: '%s'\n", tok[2]);
	DEBUG("Network Type: '%s'\n", tok[3]);

	if ( monitor->network )
	    g_free( monitor->network );

	if (tok[2][0]=='"') {
	    monitor->network = g_strndup( tok[2]+1, strlen(tok[2])-2 );
	} else {
	    monitor->network = g_strdup( tok[2] );
	}

	if ( monitor->registration_status != REGISTRATION_NOT_REGISTERED )
	{
	    int type = atoi(tok[3]);

	    switch (type) {
	    case 0:
		cellmodem_t_switch_network_status( monitor, REGISTRATION_GPRS );
		break;
	    case 2:
		/* Check for HSDPA/UMTS */
		cellmodem_t_get_ohcip_status( monitor );
                return;
	    default:
		cellmodem_t_switch_network_status( monitor, REGISTRATION_UNKNOWN );
	    }
	}
    } else {
	cellmodem_t_switch_network_status( monitor, REGISTRATION_NOT_REGISTERED );

        cellmodem_t_switch_to_error_and_restart( monitor, _("Invalid COPS reply from modem") );
        return;
    }

    cellmodem_t_get_quality( monitor );
}

/**
 * @brief [cellmodem_t pseudo-member] Callback for PIN status request
 *
 * @param success TRUE if modem replied OK, FALSE otherwise
 * @param response GString with modem response
 * @param pvt Pointer to cellmodem object
 *
 * pvt will be deferenced to cellmodem object upon calling. No typechecking is done.
 *
 * @return Nothing
 */

static void
cellmodem_t_pin_callback(gboolean success, GString *response, void *pvt)
{
    cellmodem_t *monitor = (cellmodem_t*)pvt;
    gboolean failure = FALSE;

    if ( !success || response==NULL || response->str==NULL ) {
	/* Error */
        DEBUG("Modem error detected (%p)", pvt);
	cellmodem_t_switch_to_error_and_restart( monitor, _("Error in modem reply to CPIN") );
        return;
    }

    /* +CPIN: SIM PIN */
    /* +CPIN: READY */

    if ( strncmp(response->str,"+CPIN: ", 7 )==0 ) {
	gchar *start = response->str + 7;
	DEBUG("CPIN: '%s'", start);
	if ( strncmp( start, "SIM PIN", 7) == 0) {
	    /* If we can ask for pin, we do */

	    cellmodem_t_request_pin( monitor );
	    return;
	}
	if ( strncmp( start, "READY", 2) == 0 )
	{
	    /* If we got here, we are registering. Do nothing, just
	     reschedule open */
	} else {
            failure = TRUE;
	}

        /* Errors found ?? . */
    } else {
	cellmodem_t_switch_to_error_and_restart( monitor, _("Invalid CPIN reply from modem") );

        reschedule_open( monitor );
    }
    if ( failure )
	cellmodem_t_switch_to_error_and_restart( monitor, _("Unsupportted CPIN reply from modem") );
    else
	reschedule_open( monitor );
}

/**
 * @brief [cellmodem_t pseudo-member] Callback for OHCIP status request
 *
 * @param success TRUE. We ignore OHCIP errors, not all modem support it
 * @param response GString with modem response
 * @param pvt Pointer to cellmodem object
 *
 * pvt will be deferenced to cellmodem object upon calling. No typechecking is done.
 *
 * @return Nothing
 */

static void
cellmodem_t_ohcip_callback(gboolean success, GString *response, void *pvt)
{
    cellmodem_t *monitor = (cellmodem_t*)pvt;
    gboolean failure = FALSE;

    if ( !success || response==NULL || response->str==NULL ) {
	/* Error  - ignore */
	cellmodem_t_switch_network_status( monitor, REGISTRATION_UMTS );
        goto ohcip_out;
    }

    /* +CPIN: SIM PIN */
    /* +CPIN: READY */

    if ( strncmp(response->str,"_OHCIP: ", 8 )==0 ) {
	gchar *start = response->str + 8;
	DEBUG("OHCIP: '%s'", start);
	if ( strncmp( start, "1", 1) == 0) {

            cellmodem_t_switch_network_status( monitor, REGISTRATION_HSDPA );
	} else {
	    cellmodem_t_switch_network_status( monitor, REGISTRATION_UMTS );
	}

        /* Errors found ?? . */
    } else {
	cellmodem_t_switch_to_error_and_restart( monitor, _("Invalid OHCIP reply from modem") );

        reschedule_open( monitor );
    }
    if ( failure )
	cellmodem_t_switch_to_error_and_restart( monitor, _("Unsupportted OHCIP reply from modem") );
ohcip_out:
    cellmodem_t_get_quality( monitor );
}


static void
cellmodem_t_setpin_callback(gboolean success, GString *response, void *pvt)
{
    cellmodem_t *monitor = (cellmodem_t*)pvt;

    if ( !success ) {
	/* Error */
	cellmodem_t_switch_to_error_and_restart( monitor, _("Cannot send PIN to modem") );
        return;
    }

    /* TODO: We should recheck if PIN was accepted */



    /* Just try to reopen. */

    cellmodem_t_switch_status( monitor, MODEM_CLOSED );

    reschedule_open( monitor /*, FALSE*/ );
}


/**
 * @brief [cellmodem_t pseudo-member] Callback for quality request
 *
 * @param success TRUE if modem replied OK, FALSE otherwise
 * @param response GString with modem response
 * @param pvt Pointer to cellmodem object
 *
 * pvt will be deferenced to cellmodem object upon calling. No typechecking is done.
 *
 * @return Nothing
 */

static void
cellmodem_t_quality_callback(gboolean success, GString *response, void *pvt)
{
    cellmodem_t *monitor = (cellmodem_t*)pvt;

    if ( !success || response==NULL || response->str==NULL ) {
	/* Error */
        DEBUG("Modem error detected (%p)", pvt);
	cellmodem_t_switch_to_error_and_restart( monitor, _("Error in modem reply to CSQ") );
        return;
    }

    if ( strncmp( response->str ,"+CSQ: ", 6 )==0 ) {
	gchar *start = response->str + 6;
	if ( strtok( start, ",") == NULL) {
	    return;/* FALSE;*/
	}
	strtok ( NULL, "," );
	DEBUG("Qual: '%s'\n", start );

	int i = atoi( start );
	monitor->signal_strength = i;
	monitor->quality = (double)i/ (double)monitor->options.max_quality;
	if (monitor->quality>1.0)
	    monitor->quality=1.0;

        cellmodem_t_update_monitors( monitor );
    } else {
        cellmodem_t_switch_to_error_and_restart( monitor, _("Invalid CSQ reply from modem") );
        return;
    }
    DEBUG("Got quality, restarting");
    /* cellmodem_t_close_modem( monitor, FALSE ); - reschedule will close */
    cellmodem_t_switch_status( monitor, MODEM_CLOSED );
    reschedule_open( monitor /*, FALSE*/ );
}

/**
 * @brief [cellmodem_t member] Request registration status from modem
 *
 * @param monitor The cellmodem object
 *
 * This is asynchronous. Pseudo-method "cellmodem_t_registration_callback" will
 * be called when modem replies.
 *
 * @return Nothing
 */

static void
cellmodem_t_is_registered( cellmodem_t *monitor )
{
    cellmodem_t_send_at_command(monitor, &cellmodem_t_registration_callback, "+CREG?" );
    cellmodem_t_switch_status( monitor, MODEM_WAIT_CREG_RESPONSE );
}

/**
 * @brief [cellmodem_t member] Request network id from modem
 *
 * @param monitor The cellmodem object
 *
 * This is asynchronous. Pseudo-method "cellmodem_t_identify_callback" will
 * be called when modem replies.
 *
 * This should only be called when we are sure we are registered to some
 * network, by using "cellmodem_t_is_registered".
 *
 * @return Nothing
 */

static void
cellmodem_t_identify_network( cellmodem_t *monitor )
{
    cellmodem_t_send_at_command(monitor, &cellmodem_t_identify_callback, "+COPS?" );
    cellmodem_t_switch_status( monitor, MODEM_WAIT_COPS_RESPONSE );

}

/**
 * @brief [cellmodem_t member] Request signal quality from modem
 *
 * @param monitor The cellmodem object
 *
 * This is asynchronous. Pseudo-method "cellmodem_t_quality_callback" will
 * be called when modem replies.
 *
 * This should only be called when we are sure we are registered to some
 * network.
 *
 * @return Nothing
 */

static void
cellmodem_t_get_quality( cellmodem_t *monitor )
{
    cellmodem_t_send_at_command(monitor, &cellmodem_t_quality_callback, "+CSQ" );
    cellmodem_t_switch_status( monitor, MODEM_WAIT_CSQ_RESPONSE );
}

/**
 * @brief [cellmodem_t member] Request PIN status from modem
 *
 * @param monitor The cellmodem object
 *
 * This is asynchronous. Pseudo-method "cellmodem_t_pin_callback" will
 * be called when modem replies.
 *
 * This should only be called when we are sure we are NOT registered to some
 * network.
 *
 * @return Nothing
 */

static void
cellmodem_t_get_pin_status( cellmodem_t *monitor )
{
    cellmodem_t_send_at_command(monitor, &cellmodem_t_pin_callback, "+CPIN?" );
    cellmodem_t_switch_status( monitor, MODEM_WAIT_CPIN_RESPONSE );

}

/**
 * @brief [cellmodem_t member] Request HSDPA Call-In-Progress status from modem
 *
 * @param monitor The cellmodem object
 *
 * This is asynchronous. Pseudo-method "cellmodem_t_ohcip_callback" will
 * be called when modem replies.
 *
 * @return Nothing
 */

static void
cellmodem_t_get_ohcip_status( cellmodem_t *monitor )
{
    cellmodem_t_send_at_command(monitor, &cellmodem_t_ohcip_callback, "_OHCIP?" );
    cellmodem_t_switch_status( monitor, MODEM_WAIT_OHCIP_RESPONSE );
}


static void
cellmodem_t_send_pin( cellmodem_t *monitor, const gchar *pin )
{
    GString *g = g_string_new("+CPIN=");
    g_string_append( g, pin );

    if (cellmodem_t_open_modem( monitor ) == TRUE) {

	cellmodem_t_send_at_command(monitor, &cellmodem_t_setpin_callback, g->str );
	g_string_free( g, TRUE );

	cellmodem_t_switch_status( monitor, MODEM_WAIT_PINE_RESPONSE );
    } else {
	cellmodem_t_switch_to_error_and_restart( monitor, _("Cannot open modem") );
    }
}


/**
 * @brief [cellmodem_t member] Handle modem response
 *
 * This method will dispatch the appropriate pseudo-member callback. The reply
 * MUST be a single line replied from the modem. Call this function for each
 * line you receive from modem.
 *
 * @param monitor The cellmodem object
 * @param buffer The reply from modem
 *
 * @return TRUE [TBD when FALSE]
 */

static gboolean
cellmodem_t_modem_handle_response( cellmodem_t *monitor, char *buffer )
{
    char *bstart;
    DEBUG("In handle response");

    if ( monitor->resp.reply_callback == NULL ) {
        DEBUG("No callback defined!!!!");
        return TRUE; /* No registered listener. */
    }
    if ( monitor->resp.reply_buffer == NULL )
    {
	monitor->resp.reply_buffer = g_string_new( NULL ); /* Initialize to NULL */
    }

    /* Remove trailing \r and \n */

    bstart = remove_leading_chars(buffer,"\n\r");
    remove_trailing_chars(bstart,"\r\n");

    /* See if it is echo */

    if ( strcmp( monitor->lastcmd , bstart ) == 0 ) {
	DEBUG("Echo detected, skipping");
	return TRUE;
    }

    /* Ignore empty lines */

    if ( is_only_spaces( bstart ) ) {
        DEBUG("Only spaces found in reply, skipping");
	return TRUE;
    }

    DEBUG("Got response: '%s' command '%s'\n", bstart, monitor->lastcmd);

    cellmodem_t_cancel_pending_at_command_timeout( monitor );

    if ( is_AT_error_reply( bstart ) ) {

	monitor->resp.reply_callback( FALSE,
				     monitor->resp.reply_buffer,
				     monitor->resp.reply_pvt );
	DEBUG("Free reply data");

        g_string_free( monitor->resp.reply_buffer, TRUE );
        monitor->resp.reply_buffer = NULL;

        DEBUG("Done handling error");
	return TRUE;
    }

    if ( is_AT_success_reply( bstart ) ) {
        DEBUG("Got AT OK reply");
	monitor->resp.reply_callback( TRUE,
		       monitor->resp.reply_buffer,
		       monitor->resp.reply_pvt );

	g_string_free( monitor->resp.reply_buffer, TRUE );
        monitor->resp.reply_buffer = NULL;

	return TRUE;
    }

    g_string_append( monitor->resp.reply_buffer, bstart );
    g_string_append( monitor->resp.reply_buffer, "\n" );

    DEBUG("Finished handling");
    return TRUE;
}


/**
 * @brief [cellmodem_t pseudo-member] Handle modem response direcly from the driver
 *
 * The driver will call this pseudo-method when data arrives at the
 * modem channel.
 *
 * @param chan The modem channel
 * @param c The IO condition ( G_IO_IN )
 * @param data The cellmodem object
 *
 * 'data' will be deferenced to cellmodem object. No typechecking is done.
 *
 * The reply from modem are handled by the appropriate method set up when
 * "cellmodem_t_send_at_command" was called.
 *
 * @return TRUE if correctly handled, FALSE if not.
 *
 */


static gboolean
cellmodem_t_modem_callback(GIOChannel *chan, GIOCondition c, gpointer data )
{
    cellmodem_t *monitor = (cellmodem_t*)data;
    /* Data ready */
    DEBUG("Data from modem ready, IO cond %d, data %p", c, data);
    gsize length;
    GError *error = NULL;
    GIOStatus status;
    gboolean r;

    if ( chan == NULL )
    {
	DEBUG("NULL channel!!!");
        return FALSE;
    }

    DEBUG("Line buffer size before reading: %d", monitor->line_buffer_size);


    status = g_io_channel_read_chars(
				     chan,
				     monitor->line_buffer + (monitor->line_buffer_size),
				     8192-(monitor->line_buffer_size),
				     &length,
				     &error
				    );
    if ( status == G_IO_STATUS_AGAIN ) {
        usleep(10000);
	return TRUE;
    }

    if (status != G_IO_STATUS_NORMAL ) {
	DEBUG("Error reading from modem");
        cellmodem_t_switch_to_error( monitor, _("Error reading from modem") );
	cellmodem_t_close_modem( monitor /*, TRUE*/ );
	return FALSE;
    }


    monitor->line_buffer_size+=length;
    monitor->line_buffer[ monitor->line_buffer_size ] = 0;

    DEBUG("Read %d bytes from modem: '%s'", length, monitor->line_buffer);
    DEBUG("Buffer size now %d", monitor->line_buffer_size );
    char *eol;
    do {
	eol = memchr( monitor->line_buffer, '\n', monitor->line_buffer_size);
	DEBUG("EOL: from %p -> %p",monitor->line_buffer, eol);
	if ( eol!=NULL ) {
            DEBUG("Handling command from modem");
	    size_t dist = eol-monitor->line_buffer;
	    gchar * line = g_strndup( monitor->line_buffer, dist+1 );
	    line[dist+1]=0;
	    memmove(monitor->line_buffer, eol+1, (monitor->line_buffer_size)-dist-1 );
	    monitor->line_buffer_size-=dist+1;
	    DEBUG("Calling handle response");
	    r = cellmodem_t_modem_handle_response( monitor, line );
	    g_free( line );
	    if (!r)
		return r;
	} else {
	    DEBUG("No newline in modem reply, waiting");
	}
    } while (eol);
    return TRUE;
}

/**
 * @brief [cellmodem_t member] Send an AT (ATtention) command to modem
 *
 * @param monitor The cellmodem object
 * @param callback The callback which is to be called when modem replies.
 * @param command The AT command (without the 'AT' prefix and without CR/LF)
 *
 * This will call the modem driver to send the command to the modem. The pvt
 * field will be set to the cellmodem object.
 *
 * @return TRUE if correctly sent, FALSE if not.
 *
 */

static gboolean
cellmodem_t_send_at_command( cellmodem_t *monitor,
			    modem_reply_callback_t callback,
			    const gchar *command  )
{

    monitor->resp.reply_callback = callback;
    monitor->resp.reply_pvt = monitor;

    DEBUG("Callback set to %p", callback );

    snprintf(monitor->lastcmd, 128, "AT%s", command );

    /* TODO - check if we already have a timeout */

    if ( monitor->at_timeout_id > 0 )
        g_source_remove( monitor->at_timeout_id );

    monitor->at_timeout_id =
	g_timeout_add( 1000,
		      (GSourceFunc) cellmodem_t_at_command_timeout,
		      monitor );

    if ( monitor->driver->writeln(
				  monitor->modem_instance,
				  monitor->lastcmd
				 ) == FALSE )
    {
	DEBUG("ERROR Command: '%s'\n", monitor->lastcmd );
        cellmodem_t_switch_to_error( monitor, _("Error writing to modem") );
        cellmodem_t_close_modem( monitor /*, TRUE*/ );
        return FALSE;
    }

    DEBUG("Sent command to modem: '%s'\n", monitor->lastcmd);
    
    return TRUE;
}


static gboolean
cellmodem_t_at_command_timeout( cellmodem_t *monitor )
{
    monitor->at_timeout_id = -1;
    DEBUG("Modem failed to reply");
    cellmodem_t_switch_to_error_and_restart( monitor, _("Modem did not reply to command") );
    return FALSE;
}

static void
cellmodem_t_cancel_pending_at_command_timeout( cellmodem_t *monitor )
{
    if (monitor->at_timeout_id > 0) {
        g_source_remove ( monitor->at_timeout_id );
    }
    monitor->at_timeout_id = -1;
}


static gboolean
cellmodem_t_open_modem(cellmodem_t *monitor)
{
    if ( NULL == monitor->driver ) {
	cellmodem_t_switch_to_error( monitor, _("No driver defined") );
	return FALSE;
    }

    if ( NULL == monitor->modem_instance ) {
        cellmodem_t_switch_to_error( monitor, _("No modem instance") );
	return FALSE;
    }

    if ( monitor->driver->open( monitor->modem_instance,
			        &monitor->options ) == FALSE )
    {
	return FALSE;
    }
    /* cellmodem_t_switch_status( monitor, MODEM_IDLE ); */
    cellmodem_t_reset_buffer( monitor );

    monitor->driver->set_reader( monitor->modem_instance,
				 cellmodem_t_modem_callback,
				 monitor);

    return TRUE;
}


static gboolean
reschedule_open( cellmodem_t *monitor /*, gboolean failure*/ )
{
    DEBUG("RESCHEDULING OPEN");
    cellmodem_t_close_modem( monitor /*, failure*/ );

    if ( monitor->info_timeout_id > 0 )
    {
        g_source_remove( monitor->info_timeout_id );
    }

    monitor->info_timeout_id = g_timeout_add( OPEN_TIMEOUT,
					     (GSourceFunc) get_network_info ,
					     monitor );

    return FALSE; /* Cancel this timer */
}


/**
 * This will be called from a timer (but at startup)
 */

static gboolean
get_network_info( cellmodem_t *monitor )
{
    DEBUG("Getting network info");
    if ( cellmodem_t_open_modem( monitor ) != FALSE ) {

	cellmodem_t_is_registered( monitor );
	return FALSE; /* Dont restart timer. */
    }
    DEBUG("Cannot open modem");

    cellmodem_t_switch_to_error_and_restart( monitor, _("Cannot open modem") );

    return FALSE;
}


/*
 Configuration stuff
 */

static void
cellmodem_read_config(XfcePanelPlugin *plugin, cellmodem_t *monitor)
{
    char *file;
    const char *text;
    XfceRc *rc;

    if (!(file = xfce_panel_plugin_lookup_rc_file (plugin))) {
	DEBUG("No config file found\n");
	return;
    }

    rc = xfce_rc_simple_open (file, TRUE);
    g_free (file);

    if (!rc) {
	DEBUG("Cannot open config file\n");

	return;
    }

    monitor->options.display_tooltip_info =
	xfce_rc_read_bool_entry (rc, "display_tooltip_info", FALSE);


    if ( NULL != monitor->options.modem_device )
	    g_free( monitor->options.modem_device );

    text =  xfce_rc_read_entry (rc, "modem_device", NULL);
    if (NULL!=text && *text) {
	monitor->options.modem_device = g_strdup (text);
    } else {

	monitor->options.modem_device = NULL;
    }

    if ( NULL != monitor->options.modem_driver )
	g_free( monitor->options.modem_driver );

    text =  xfce_rc_read_entry (rc, "modem_driver", NULL);
    if (NULL!=text && *text) {
	monitor->options.modem_driver = g_strdup (text);
    } else {
	monitor->options.modem_driver = NULL;
    }


    monitor->options.low_threshold = xfce_rc_read_int_entry( rc, "low_threshold", 40 );
    monitor->options.critical_threshold = xfce_rc_read_int_entry( rc, "critical_threshold", 20 );
    monitor->options.max_quality = xfce_rc_read_int_entry( rc, "max_quality", MAX_QUAL );


    xfce_rc_close (rc);

}


void
cellmodem_write_config(XfcePanelPlugin *plugin, cellmodem_t *monitor)
{
    XfceRc *rc;
    char *file;

    DEBUG("Saving configuration");

    if (!(file = xfce_panel_plugin_save_location (plugin, TRUE))) {
        DEBUG("Cannot save configuration!");
	return;
    }
    
    rc = xfce_rc_simple_open (file, FALSE);
    g_free (file);


    if (!rc) {
        DEBUG("Cannot open configuration file to save!");
	return;
    }
    
    xfce_rc_write_bool_entry (rc, "display_tooltip_info",
			      monitor->options.display_tooltip_info);

    xfce_rc_write_entry (rc, "modem_device",
			 monitor->options.modem_device );
    xfce_rc_write_entry (rc, "modem_driver",
			 monitor->options.modem_driver );

    xfce_rc_write_int_entry( rc, "low_threshold", monitor->options.low_threshold );
    xfce_rc_write_int_entry( rc, "critical_threshold", monitor->options.critical_threshold );
    xfce_rc_write_int_entry( rc, "max_quality", monitor->options.max_quality );

    DEBUG("Done saving configuration");
    xfce_rc_close (rc);
}


void
cellmodem_t_initialize_modem( cellmodem_t *monitor )
{
    DEBUG(" ---- Initializing modem ---- ");

    cellmodem_t_switch_to_error( monitor, _("Modem initializing") );

    /*
     Cancel any pending timers
     */

    if (monitor->at_timeout_id > 0) {
        g_source_remove( monitor->at_timeout_id );
    }
    if ( monitor->info_timeout_id > 0 )
    {
        g_source_remove( monitor->info_timeout_id );
    }

    if ( monitor->options.modem_driver == NULL ) {
        DEBUG("No driver defined, skipping");
	return;
    }

    /* TODO: Skip if we already have this driver loaded */

    if ( monitor->driver != NULL && monitor->modem_instance != NULL ) {
        DEBUG("Closing already created driver/instance");
	monitor->driver->close( monitor->modem_instance );
        DEBUG("Destroying the modem instance");
	monitor->driver->destroy( monitor->modem_instance );
        monitor->modem_instance = NULL;
    }

    monitor->driver = NULL;
    DEBUG("Locating driver");
    monitor->driver = find_driver_by_name( monitor->options.modem_driver );

    if (monitor->driver==NULL) {
	DEBUG("Cannot find that driver (%s)!!!", monitor->options.modem_driver);
	cellmodem_t_switch_to_error( monitor, _("Invalid driver specified") );
	return;
    }

    DEBUG("Modem driver at %p", monitor->driver);
    monitor->modem_instance =
	monitor->driver->create();

    DEBUG("Modem instance created");

    cellmodem_t_switch_status( monitor, MODEM_WAIT_CREG_RESPONSE );

    DEBUG("Calling get network info");

    get_network_info(monitor);

}

static gboolean
cellmodem_button_event(GtkEventBox *box, GdkEventButton *button, cellmodem_t *monitor)
{
    if (button->type != GDK_BUTTON_PRESS || button->button != 1) {
        return FALSE;
    }

    if ( monitor->registration_status == REGISTRATION_NEEDS_PIN )
    {
	pin_helper_launch( GTK_WIDGET(monitor->plugin), pin_callback, monitor );
    }
    return TRUE;
}


#ifdef DEBUG_ENABLED

int init_logging()
{
    openlog("cellmodem", LOG_PID, LOG_USER );

    return 0;
}

char logbuf[8192];

#endif
/* create the plugin */

static void
cellmodem_t_construct (XfcePanelPlugin *plugin)
{
    cellmodem_t *monitor;

    xfce_textdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR, "UTF-8");

#ifdef DEBUG_ENABLED
    init_logging();
#endif

    DEBUG("Creating applet\n");
    monitor = cellmodem_t_new (plugin);

    DEBUG("Reading config\n");
    cellmodem_read_config (plugin, monitor);
    cellmodem_t_initialize_modem( monitor );

    cellmodem_t_update_monitors( monitor );

    g_signal_connect (plugin, "free-data", G_CALLBACK (cellmodem_t_delete), monitor);
    g_signal_connect (plugin, "save", G_CALLBACK (cellmodem_write_config), monitor);
    g_signal_connect (plugin, "size-changed", G_CALLBACK (cellmodem_set_size), monitor);

    xfce_panel_plugin_menu_show_configure (plugin);

    g_signal_connect (plugin, "configure-plugin", G_CALLBACK (cellmodem_create_options), monitor);

    gtk_container_add(GTK_CONTAINER(plugin), monitor->eventbox);

}

XFCE_PANEL_PLUGIN_REGISTER_EXTERNAL (cellmodem_t_construct);
