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

static guint32 get_current_time()
{
    /* we should use g_date_get_julian();*/
    struct timeval tv;
    gettimeofday( &tv,NULL );
    return tv.tv_sec;
}

static gboolean send_reply( struct fake_modem_t *modem )
{
    gsize written;
    GError *error = NULL;
    DEBUG("Sending modem reply: %s", modem->reply->str );
    if ( g_io_channel_write_chars( modem->channel,
				  modem->reply->str,
				  strlen(modem->reply->str),
				  &written,
				  &error
				 ) != G_IO_STATUS_NORMAL )
    {
	/* Close channel */
        DEBUG("Error writing response, closing modem channel");
	g_io_channel_unref( modem->channel );
        modem->channel = NULL;
	return FALSE;
    }


    g_io_channel_flush( modem->channel, &error );

    /* TODO - Check also written bytes */

    if ( modem->reader != NULL )
	modem->reader( modem->channel, G_IO_IN, modem->reader_pvt );

    return FALSE;
}


static gboolean queue_reply( struct fake_modem_t *modem )
{
    g_timeout_add(1,
		  (GSourceFunc)send_reply,
		  modem
		 );
    return TRUE;
}


static void
reply_ok( struct fake_modem_t *modem, const gchar *string )
{
    g_string_append( modem->reply, string);
    g_string_append( modem->reply, "\r\nOK\r\n" );
    queue_reply( modem );
}

static gboolean fake_handle_command( struct fake_modem_t *modem, const char *data )
{
    const char *cmd;

    if ( strlen( data ) < 3 ) {
        DEBUG("Short command");
        return queue_reply(modem);  /* Not AT command, too short */
    }

    if ( ! g_str_has_prefix( data, "AT") ) {
        DEBUG("Not AT Command");
        return queue_reply(modem); /* Not AT command */
    }
    cmd = data + 2;

    DEBUG("Cmd: %s", cmd );

    /* Add newlines */

    g_string_append(modem->reply,"\r\n");

    if ( strncmp( cmd, "+CREG?", 6)==0) {
	DEBUG("PIN time: %u, this time %u", modem->pin_time, get_current_time() );
	if ( modem->valid_pin )

	    if (  get_current_time() > modem->pin_time + REGISTRATION_TIME ) {
		reply_ok( modem, "+CREG: 0,1" );
	    } else {
		reply_ok( modem, "+CREG: 0,2" );
	    }
	else
            reply_ok( modem, "+CREG: 0,0" );
        return TRUE;
    }
    if ( strncmp( cmd, "+COPS?", 6)==0) {
	reply_ok( modem,  "+COPS: 0,0,\"Testing network\",2" );
        return TRUE;
    }

    if ( strncmp( cmd, "+CSQ", 4)==0) {
	reply_ok( modem,  "+CSQ: 15,0" );
        return TRUE;
    }

    if ( strncmp( cmd, "+CPIN?", 6)==0) {
        if ( modem->valid_pin )
	    reply_ok( modem,  "+CPIN: READY" );
	else
            reply_ok( modem,  "+CPIN: SIM PIN" );
        return TRUE;
    }

    if ( strncmp( cmd, "+CPIN=", 6)==0) {
	const char *pin = cmd+6;
	if (strncmp( pin, "1234", 4)==0) {
	    if (! modem->valid_pin) {
		modem->pin_time = get_current_time();
	    }
	    modem->valid_pin = TRUE;
	    g_string_append( modem->reply, "OK\r\n" );
	    queue_reply( modem );
	    return TRUE;
	} else {
	    /* What to return here? */
	    g_string_append( modem->reply, "OK\r\n" );
	    queue_reply( modem );
            return TRUE;
	}
    }

    /* Queue error for now */
    g_string_append( modem->reply, "ERROR\r\n" );
    queue_reply( modem );

    return FALSE;
}

static gboolean fake_open( modem_instance_t instance, cellmodem_options_t *options  )
{
    /* Tricky - we need to set up a pipe
     otherwise we won't be able to speak
     with the core plugin */
    struct fake_modem_t *modem = (struct fake_modem_t*)instance;
    const gchar *tempdir = "/tmp";
    GString *pipename;
    GError *error = NULL;

    DEBUG("Opening fake modem");

    pid_t mypid = getpid();

    pipename = g_string_new( "" );

    g_string_printf( pipename, "%s/fakemodem-%d-pipe", tempdir, mypid );

    if ( mkfifo( pipename->str, 0600 ) != 0 )
    {
        DEBUG("Cannot create pipe!!!");
        g_string_free( pipename, TRUE );
        return FALSE;
    }

    int fd = open( pipename->str, O_RDWR| O_EXCL);

    if (fd<0) {
	/* Unlink fifo */
        DEBUG("Cannot open pipe!!!");
	unlink(pipename->str);
	g_string_free( pipename, TRUE );
	return FALSE;
    }

    modem->channel = g_io_channel_unix_new( fd );

    if ( modem->channel == NULL )
    {
        DEBUG("Cannot opem modem channel!!!");
	while (close( fd )<0 && errno==EINTR );
	unlink( pipename->str );
	g_string_free( pipename, TRUE );
	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 );

    unlink( pipename->str );  /* Not needed any more. The inode will stay there */
    g_string_free( pipename, TRUE );

    DEBUG("Modem opened (pipe)");

    return TRUE;
}

static void fake_close( modem_instance_t instance )
{
    struct fake_modem_t *modem = (struct fake_modem_t*)instance;

    if ( modem == NULL ) {
        return;
    }
    if ( modem->channel != NULL ) {
	g_io_channel_unref( modem->channel );
        modem->channel = NULL;
    } 
}

static gboolean fake_writeln( modem_instance_t instance, const gchar *data )
{
    struct fake_modem_t *modem = (struct fake_modem_t*)instance;

    /* Free up the string if already allocated */
    if ( modem->reply != NULL )
    {
        g_string_free( modem->reply, TRUE );
    }

    if ( data == NULL )
	return FALSE; /* NULL data, return error */

    modem->reply = g_string_new( data ); /* For echoing the command */

    DEBUG("Got command: %s", data );

    return fake_handle_command( instance , data );

}


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

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

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

    return TRUE;
}

static modem_instance_t fake_create( )
{
    struct fake_modem_t *modem = g_new( struct fake_modem_t , 1);
    modem->reply = NULL;
    modem->reader = NULL;

    modem->valid_pin = FALSE;
    modem->pin_time = 0;
    return (modem_instance_t)modem;
}

static void fake_destroy( modem_instance_t modem )
{
    g_free( modem );
}


struct modem_driver_t modem_driver_fake =
{
    .name = "Testing Driver",
    .description = "Simple Driver for Testing Purposes",
    .open = &fake_open,
    .close = &fake_close,
    .writeln = &fake_writeln,
    .set_reader = &fake_set_reader,
    .create = &fake_create,
    .destroy = &fake_destroy
};
