/* pggstdio.c - combination of input, output and output options
 *      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 <pggstdio.h>
#include <pggoutput.h>
#include <pgginput.h>


/*
 * Type casts
 */
#define io		((PggStdioPtr)(_io))
#define old_io		((PggStdioPtr)(_old_io))


PggStdio pgg_stdio_new(PggErrenv errenv)
{
    PggStdioPtr	new_io;
    
    PGG_CHECK_SKIP_ARG(NULL);
    
    if (!( new_io = _malloc(PggStdio) ))
        PGG_RETURN_ERR_ARG(RESOURCE, MEMORY, NULL);
    
    memset(new_io, 0, _size(PggStdio));
    
    new_io->magic      = PggStdioMAGIC;
    new_io->refcounter = 1;
    
    return _hd(PggStdio, new_io);
}


PggStdio pgg_stdio_clone(PggStdio _old_io, PggErrenv errenv)
{
    PggStdioPtr	new_io;
    PggErrenv		local_errenv;
    
    PGG_STD_ASSERT_ARG(PggStdio, old_io, NULL);
    
    if (!( new_io = _malloc(PggStdio) ))
        PGG_RETURN_ERR_ARG(RESOURCE, MEMORY, NULL);
    
    memset(new_io, 0, _size(PggStdio));
    
    pgg_errenv_reset(local_errenv);
    
    if (old_io->input)
        new_io->input = pgg_input_clone(old_io->input, local_errenv);
    
    if (old_io->output)
        new_io->output = pgg_output_clone(old_io->output, local_errenv);
    
    if (old_io->opts)
        new_io->opts = pgg_outopts_clone(new_io->opts, local_errenv);
    
    if (pgg_errenv_is_set(local_errenv)) {
        pgg_input_release(new_io->input, NULL);
        pgg_output_release(new_io->output, NULL);
        pgg_outopts_release(new_io->opts, NULL);
        free(new_io);
        pgg_errenv_copy(errenv, local_errenv);
        return NULL;
    }
    
    new_io->magic      = PggStdioMAGIC;
    new_io->refcounter = 1;
    new_io->textmode   = old_io->textmode;
    
    return _hd(PggStdio, new_io);
}


void pgg_stdio_addref(PggStdio _io, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggStdio, io);
    io->refcounter++;
}


void pgg_stdio_release(PggStdio _io, PggErrenv errenv)
{
    PggErrenv		local_errenv;
    
    PGG_STD_ASSERT(PggStdio, io);
    
    pgg_errenv_reset(local_errenv);
    
    if (!--io->refcounter) {
        if (io->input)
            pgg_input_release(io->input, local_errenv);
        if (io->output)
            pgg_output_release(io->output, local_errenv);
        if (io->opts)
            pgg_outopts_release(io->opts, local_errenv);
        free(io);
    }
    
    if (pgg_errenv_is_set(local_errenv)) {
        PGG_DEBUG(("error on releasing input/output/outopts ?!?!"));
        pgg_errenv_copy(errenv, local_errenv);
    }
}


void pgg_stdio_set_input(PggStdio _io, PggInput input, PggErrenv errenv)
{
    PggErrenv		local_errenv;
    
    PGG_STD_ASSERT(PggStdio, io);
    
    /* NOTE: Don't assert input!=NULL because NULL means no input at all */
    
    pgg_errenv_reset(local_errenv);
    
    if (input)
        pgg_input_addref(input, local_errenv);
    
    if (!pgg_errenv_is_set(local_errenv) && io->input)
        pgg_input_release(io->input, local_errenv);
    
    if (!pgg_errenv_is_set(local_errenv))
        io->input = input;
    else
        pgg_errenv_copy(errenv, local_errenv);
}


void pgg_stdio_set_output(PggStdio _io, PggOutput output, PggErrenv errenv)
{
    PggErrenv		local_errenv; 
    
    PGG_STD_ASSERT(PggStdio, io);
    
    /* NOTE: Don't assert output!=NULL because NULL means no output at all */
    
    pgg_errenv_reset(local_errenv);
    
    if (output)
        pgg_output_addref(output, local_errenv);
    
    if (!pgg_errenv_is_set(local_errenv) && io->output)
        pgg_output_release(io->output, local_errenv);
    
    if (!pgg_errenv_is_set(local_errenv))
        io->output = output;
    else
        pgg_errenv_copy(errenv, local_errenv);
}


void pgg_stdio_set_outopts(PggStdio _io, PggOutopts opts, PggErrenv errenv)
{
    PggErrenv		local_errenv;
    
    PGG_STD_ASSERT(PggStdio, io);
    
    /* NOTE: Don't assert outopts!=NULL because NULL means no outopts at all */
    
    pgg_errenv_reset(local_errenv);
    
    if (opts)
        pgg_outopts_addref(opts, local_errenv);
    
    if (!pgg_errenv_is_set(local_errenv) && io->opts)
        pgg_outopts_release(io->opts, local_errenv);
    
    if (!pgg_errenv_is_set(local_errenv))
        io->opts = opts;
    else
        pgg_errenv_copy(errenv, local_errenv);
}


void pgg_stdio_set_textmode(PggStdio _io, int yesno, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggStdio, io);
    io->textmode = yesno ? 1 : 0;
}


PggInput pgg_stdio_get_input(PggStdio _io, PggErrenv errenv)
{
    PggErrenv		local_errenv;
    
    PGG_STD_ASSERT_ARG(PggStdio, io, NULL);
    PGG_ASSERT_ARG(io->input, REQUEST, NOTSET, NULL);
    
    pgg_errenv_reset(local_errenv);
    
    pgg_input_addref(io->input, local_errenv);
    
    if (pgg_errenv_is_set(local_errenv)) {
        pgg_errenv_copy(errenv, local_errenv);
        return NULL;
    }
    
    return io->input;
}


PggOutput pgg_stdio_get_output(PggStdio _io, PggErrenv errenv)
{
    PggErrenv           local_errenv;
    
    PGG_STD_ASSERT_ARG(PggStdio, io, NULL);
    PGG_ASSERT_ARG(io->output, REQUEST, NOTSET, NULL);
    
    pgg_errenv_reset(local_errenv);
    
    pgg_output_addref(io->output, local_errenv);
    
    if (pgg_errenv_is_set(local_errenv)) {
        pgg_errenv_copy(errenv, local_errenv);
        return NULL;
    }
    
    return io->output;
}


PggOutopts pgg_stdio_get_outopts(PggStdio _io, PggErrenv errenv)
{
    PggErrenv		local_errenv;
    
    PGG_STD_ASSERT_ARG(PggStdio, io, NULL);
    PGG_ASSERT_ARG(io->opts, REQUEST, NOTSET, NULL);
    
    pgg_errenv_reset(local_errenv);
    
    pgg_outopts_addref(io->opts, local_errenv);
    
    if (pgg_errenv_is_set(local_errenv)) {
        pgg_errenv_copy(errenv, local_errenv);
        return NULL;
    }
    
    return io->opts;
}


int pgg_stdio_get_textmode(PggStdio _io, PggErrenv errenv)
{
    PGG_STD_ASSERT_ARG(PggStdio, io, -1);
    return io->textmode;
}


static long read_cb(void * output, void *data, long amount)
{
    PggErrenv		local_errenv;
    long		len;
    
    if (!output || !data || amount < 0)
        return -1;
    
    pgg_errenv_reset(local_errenv);
    
    if (amount == 0) {
        pgg_output_close((PggOutput)output, local_errenv);
        return pgg_errenv_is_set(local_errenv) ? -1 : 0;
    }
    
    len = pgg_output_write((PggOutput)output, data, amount, local_errenv);
    
    return pgg_errenv_is_set(local_errenv) ? -1 : len;
}


static long write_cb(void *input, void *data, long amount)
{
    PggErrenv		local_errenv;
    long		len;
    
    if (!input || !data || amount < 0)
        return -1;
    
    pgg_errenv_reset(local_errenv);
    
    len = pgg_input_read((PggInput)input, data, amount, local_errenv);
    
    if (len == 0)
        pgg_input_close((PggInput)input, local_errenv);
    
    return pgg_errenv_is_set(local_errenv) ? -1 : len;
}


void pgg_stdio_setup_exe(PggStdio _io, PggExe exe, PggErrenv errenv)
{
    PggErrenv		local_errenv;
    
    PGG_STD_ASSERT(PggStdio, io);
    PGG_ASSERT(exe, ARGUMENT, NULLPTR);
    
    pgg_errenv_reset(local_errenv);
    
    if (io->textmode)
        pgg_exe_add_arg(exe, "--textmode", local_errenv);
    
    if (io->opts)
        pgg_outopts_setup_exe(io->opts, exe, local_errenv);
    
    if (io->output)
        switch (_ptr(PggOutput, io->output)->state) {
            case 1: /* filename */
                pgg_exe_add_arg(exe, "--output", local_errenv);
                pgg_exe_add_arg(exe, _ptr(PggOutput, io->output)->data.filename, local_errenv);
                break;
            
            case 2: /* FD */
            case 3: /* buffer */
                pgg_output_open(io->output, local_errenv);
                pgg_exe_add_arg(exe, "--output", local_errenv);	/* Force stdout */
                pgg_exe_add_arg(exe, "-", local_errenv);
                pgg_exe_set_read_cb(exe, read_cb, io->output, local_errenv);
                break;
            
            default: /* unknown state */
                PGG_RETURN_ERR(INTERNAL, NONE);
        }
    #if 0
    else {
        /* FIXME: make this sense? IMHO no */
        pgg_exe_add_arg(exe, "--output", local_errenv);		/* No output at all result in stdout which is */
        pgg_exe_add_arg(exe, "-", local_errenv);		/* send to /dev/null */
    }
    #endif
    
    if (io->input)
        switch (_ptr(PggInput, io->input)->state) {
            case 1: /* filename */
                pgg_exe_add_arg(exe, _ptr(PggInput, io->input)->data.filename, local_errenv);
                break;
            
            case 2: /* buffer */
            case 3: /* FD */
            case 4: /* raw data */
                pgg_input_open(io->input, local_errenv);
                pgg_exe_add_arg(exe, "-", local_errenv);	/* Force stdin */
                pgg_exe_set_write_cb(exe, write_cb, io->input, local_errenv);
                break;
            
            default: /* unknown state */
                PGG_RETURN_ERR(INTERNAL, NONE);
        }
    #if 0
    else {
         /* FIXME: make this sense? IMHO no */
        pgg_exe_add_arg(exe, "-", local_errenv);		/* Effectivly read from /dev/null */
    }
    #endif
    
    if (pgg_errenv_is_set(local_errenv)) {
        PGG_DEBUG(("problems on setting PggExe"));
        pgg_errenv_copy(errenv, local_errenv);
    }
}




