/* pgginput.c - data source object implemtation
 *      Copyright (C) 1999 Michael Roth <mroth@gnupg.org>
 *
 * This file is part of PGG (Privacy Guard Glue).
 *
 * PGG 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.
 *
 * PGG 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 <includes.h>
#include <pgg.h>
#include <pggdebug.h>
#include <pgginput.h>


/*
 * Type casts
 */
#define in		((PggInputPtr)(_in))
#define old_in		((PggInputPtr)(_old_in))


PggInput pgg_input_new(PggErrenv errenv)
{
    PggInputPtr		new_in;
    
    PGG_CHECK_SKIP_ARG(NULL);
    
    if (!( new_in = _malloc(PggInput) ))
        PGG_RETURN_ERR_ARG(RESOURCE, MEMORY, NULL);
    
    memset(new_in, 0, _size(PggInput));
    
    new_in->magic = PggInputMAGIC;
    new_in->refcounter = 1;
    new_in->fd = -1;
    
    return _hd(PggInput, new_in);
}


/*
 * FIXME: should we really clone the complete state
 * including FDs and so on?
 */
PggInput pgg_input_clone(PggInput _old_in, PggErrenv errenv)
{
    PggErrenv		local_errenv;
    PggInputPtr		new_in;
    
    PGG_STD_ASSERT_ARG(PggInput, old_in, NULL);
    
    if (!( new_in = _malloc(PggInput) ))
        PGG_RETURN_ERR_ARG(RESOURCE, MEMORY, NULL);
    
    memcpy(new_in, old_in, _size(PggInput));
    
    new_in->refcounter = 1;
    new_in->fd         = -1;
    new_in->bytes_read = 0;
    
    switch (new_in->state) {
        case 1: /* filename */
            if (!( new_in->data.filename = strdup(old_in->data.filename) )) {
                free(new_in);
                PGG_RETURN_ERR_ARG(RESOURCE, MEMORY, NULL);
            }
            break;
        
        case 2: /* buffer */
            new_in->data.buf = pgg_buffer_clone(old_in->data.buf, local_errenv);
            if (pgg_errenv_is_set(local_errenv)) {
                free(new_in);
                PGG_RETURN_ERR_ARG(RESOURCE, MEMORY, NULL);
            }
            break;
    }
    
    return _hd(PggInput, new_in);
}


void pgg_input_addref(PggInput _in, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggInput, in);
    in->refcounter++;
}


static void cleanup(PggInputPtr inptr)
{
    switch (inptr->state) {
        case 1: /* filename */
            if (inptr->fd != -1) {
                close(inptr->fd);
                inptr->fd = -1;
            }
            free(inptr->data.filename);
            break;
        
        case 2:
            pgg_buffer_release(inptr->data.buf, NULL);
            break;
    }
    
    inptr->bytes_read = 0;
}


void pgg_input_release(PggInput _in, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggInput, in);
    
    if (!(--in->refcounter)) {
        cleanup(in);
        free(in);
    }
}


void pgg_input_set_filename(PggInput _in, char *filename, PggErrenv errenv)
{
    char *		tmp;
    
    PGG_STD_ASSERT(PggInput, in);
    PGG_ASSERT(filename, ARGUMENT, NULLPTR);
    
    if (strcmp(filename, "-")==0)		/* Filename '-' not allowed */
        PGG_RETURN_ERR(ARGUMENT, VALUE);
    
    if (!( tmp = strdup(filename) ))
        PGG_RETURN_ERR(RESOURCE, MEMORY);
    
    cleanup(in);
    
    in->data.filename = tmp;
    in->state         = 1;
}


void pgg_input_set_buffer(PggInput _in, PggBuffer buf, PggErrenv errenv)
{
    PggErrenv		local_errenv;
    
    PGG_STD_ASSERT(PggInput, in);
    PGG_ASSERT(buf, ARGUMENT, NULLPTR);
    
    pgg_errenv_reset(local_errenv);
    pgg_buffer_addref(buf, local_errenv);
    
    if (pgg_errenv_is_set(local_errenv)) {
        pgg_errenv_copy(errenv, local_errenv);
        return;
    }
    
    cleanup(in);
    
    in->data.buf = buf;
    in->state    = 2;
}


void pgg_input_set_fd(PggInput _in, int fd, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggInput, in);
    PGG_ASSERT(fd >= 0, ARGUMENT, VALUE);
    
    cleanup(in);
    
    in->fd    = fd;
    in->state = 3;
}


void pgg_input_set_rawdata(PggInput _in, void *data, long amount, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggInput, in);
    PGG_ASSERT(data, ARGUMENT, NULLPTR);
    PGG_ASSERT(amount > 0, ARGUMENT, VALUE);
    
    cleanup(in);
    
    in->data.raw.ptr  = (char *)data;
    in->data.raw.size = amount;
    in->state         = 4;
}


void pgg_input_open(PggInput _in, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggInput, in);
    
    switch (in->state) {		/* FIXME: Track state of open or close and catch the error cases */
        case 1: /* filename */
            if ((in->fd = creat(in->data.filename, 0666)) == -1)
                PGG_RETURN_ERR(UNKNOWN, NONE);
            else
                return;
        
        case 2: /* buffer */
        case 3: /* FD */
        case 4: /* raw data */
            return;
        
        default:
            PGG_RETURN_ERR(INTERNAL, NONE);
    }
}


long pgg_input_read(PggInput _in, void *data, long amount, PggErrenv errenv)
{
    PggErrenv		local_errenv;
    long		len;
    
    PGG_STD_ASSERT_ARG(PggInput, in, -1);
    PGG_ASSERT_ARG(data, ARGUMENT, NULLPTR, -1);
    PGG_ASSERT_ARG(amount >= 0, ARGUMENT, VALUE, -1);
    
    pgg_errenv_reset(local_errenv);
    
    switch (in->state) {
        case 1: /* filename */
            if (in->fd == -1)
                PGG_RETURN_ERR_ARG(REQUEST, STATE, -1);
            /* fall through */
        
        case 3: /* FD */
            if ((len = read(in->fd, data, amount)) == -1)
                PGG_RETURN_ERR_ARG(UNKNOWN, NONE, -1);
            else
                return len;
        
        case 2: /* buffer */
            len = amount + in->bytes_read > pgg_buffer_get_size(in->data.buf, local_errenv)
                  ? pgg_buffer_get_size(in->data.buf, local_errenv) - in->bytes_read
                  : amount;
            
            if (pgg_errenv_is_set(local_errenv)) {
                pgg_errenv_copy(errenv, local_errenv);
                return -1;
            }
            
            if (len < 0)			/* someone modified the buffer unexpected */
                PGG_RETURN_ERR_ARG(UNKNOWN, NONE, -1);
            
            if (len > 0)
                memcpy(data, (char *)pgg_buffer_get_data(in->data.buf, local_errenv) + in->bytes_read, len);
            
            if (pgg_errenv_is_set(local_errenv)) {
                pgg_errenv_copy(errenv, local_errenv);
                return -1;
            }
            
            in->bytes_read += len;
            return len;
        
        case 4: /* raw data */
            len = amount + in->bytes_read > in->data.raw.size
                  ? in->data.raw.size - in->bytes_read
                  : amount;
            
            if (len <0)
                PGG_RETURN_ERR_ARG(UNKNOWN, NONE, -1);
            
            if (len >0)
                memcpy(data, in->data.raw.ptr + in->bytes_read, len);
            
            in->bytes_read += len;
            return len;
        
        default:
            PGG_RETURN_ERR_ARG(INTERNAL, NONE, -1);
    }
}


void pgg_input_close(PggInput _in, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggInput, in);
    
    switch (in->state) {
        case 1: /* filename */
            if (in->fd != -1)
                PGG_RETURN_ERR(ARGUMENT, STATE);
            
            close(in->fd);
            in->fd = -1;
            break;
        
        case 2: /* buffer */
        case 3: /* FD */
        case 4: /* raw data */
            break;
        
        default:
            PGG_RETURN_ERR(INTERNAL, NONE);
    }
}


