/*
 * 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 "modem_driver_generic.h"
#include "cellmodem.h"
#include <errno.h> /* Maybe non-portable */
#include <termios.h>


static gboolean generic_open( modem_instance_t instance, cellmodem_options_t *options  )
{
    struct generic_modem_t *modem = (struct generic_modem_t*)instance;
    GError *error = NULL;
    struct termios tio;

    DEBUG("Opening modem");
    if ( options->modem_device == NULL ) {
        DEBUG("Modem device is null, returning");
	return FALSE;
    }

    int fd = open( options->modem_device, O_RDWR|O_EXCL);

    if (fd<0) {
        DEBUG("Cannot open modem!!!");
	return FALSE;
    }

    tcgetattr( fd, &tio );

    cfmakeraw( &tio );

    tio.c_cflag &= ~(CBAUD | CSIZE | CSTOPB | PARENB);
    tio.c_cflag |= ( B115200 | CS8 | CREAD | CLOCAL );

    tcsetattr( fd, TCSANOW , &tio);

    modem->channel = g_io_channel_unix_new( fd );

    if ( modem->channel == NULL )
    {
        DEBUG("Cannot opem modem channel!!!");
	while (close( fd )<0 && errno==EINTR );
	return FALSE;
    }
    g_io_channel_set_encoding( modem->channel, NULL, &error );
    error=NULL;
    g_io_channel_set_flags( modem->channel, G_IO_FLAG_NONBLOCK, &error );

    g_io_channel_set_close_on_unref( modem->channel, TRUE );

    DEBUG("Modem successfully opened");

    return TRUE;
}

static void generic_close( modem_instance_t instance )
{
    struct generic_modem_t *modem = (struct generic_modem_t*)instance;

    DEBUG("Closing modem");

    if ( modem == NULL ) {
	return;
    }
    if ( modem->channel != NULL ) {
	if ( modem->reader >= 0 ) {
	    g_source_remove( modem->reader );
            modem->reader = -1;
	}
        DEBUG( "Closing channel %p", modem->channel );
	g_io_channel_unref( modem->channel );
        modem->channel = NULL;
    }
    DEBUG("all done");
}

static gboolean generic_writeln( modem_instance_t instance, const gchar *data )
{
    struct generic_modem_t *modem = (struct generic_modem_t*)instance;
    gsize written, bytes_to_write, bw;
    GError *error = NULL;
    GString *g = g_string_new( data );

    GIOStatus status;

    if (modem==NULL) {
	DEBUG("Null modem instance!");
        return FALSE;
    }

    if (modem->channel==NULL) {
	DEBUG("Null modem channel!");
        return FALSE;
    }

    g_string_append( g, "\r\n" );

    bytes_to_write = strlen( g->str );
    written = 0;

    DEBUG("Writing command to modem, size %d", bytes_to_write);

    do {
	status = g_io_channel_write_chars( modem->channel,
					  g->str + written,
					  bytes_to_write - written,
					  &bw,
					  &error
					 );
	DEBUG("Write status: %d, written %d", status, bw);
	if ( status == G_IO_STATUS_AGAIN ) {
            usleep(10000);
            error = NULL;
	} else {
	    if (status == G_IO_STATUS_NORMAL) {
		written += bw;
	    } else {
                break;
	    }
	}


    } while ( status == G_IO_STATUS_AGAIN || written<bytes_to_write );

    if ( status != G_IO_STATUS_NORMAL )
    {

	/* Close channel */

	DEBUG("Error writing ro modem, closing modem channel");
	if (error && error->message ) {
	    DEBUG("Error message: '%s'", error->message );
	}

        g_string_free( g, TRUE );
	g_io_channel_unref( modem->channel );
	modem->channel = NULL;
	return FALSE;
    }

    g_string_free( g, TRUE );

    g_io_channel_flush( modem->channel, &error );

    return TRUE;
}


static gboolean generic_set_reader( modem_instance_t instance, GIOFunc reader, gpointer data )
{
    struct generic_modem_t *modem = (struct generic_modem_t*)instance;

    /*modem->reader = reader;
    modem->reader_pvt = data;

    DEBUG("Data set to %p", data );
    */

    if ( modem->reader >= 0)
    {
        g_source_remove( modem->reader );
    }

    modem->reader = g_io_add_watch( modem->channel,
				   G_IO_IN,
				   reader,
				   data );

    return TRUE;
}

static modem_instance_t generic_create( )
{
    struct generic_modem_t *modem = g_new( struct generic_modem_t , 1);
    modem->reader = -1;
    modem->channel = NULL;
    return (modem_instance_t)modem;
}

static void generic_destroy( modem_instance_t modem )
{
    DEBUG("Destroying instance %p", modem);
    g_free( modem );
}


struct modem_driver_t modem_driver_generic =
{
    .name = "Generic Driver",
    .description = "Generic PCMCIA/PCCARD Driver",
    .open = &generic_open,
    .close = &generic_close,
    .writeln = &generic_writeln,
    .set_reader = &generic_set_reader,
    .create = &generic_create,
    .destroy = &generic_destroy
};
