/*
Copyright (C) 2008-2010, Parrot Foundation.
$Id: filehandle.pmc 47784 2010-06-23 15:19:57Z NotFound $

=head1 NAME

src/pmc/filehandle.pmc - FileHandle PMC

=head1 DESCRIPTION

The FileHandle PMC performs I/O operations on a source or destination file.

=head2 Vtable Functions

=over 4

=cut

*/

#include "../src/io/io_private.h"

#ifdef PARROT_HAS_READLINE
#ifdef __cplusplus
extern "C" {
#endif
    char *readline(const char *);
    void add_history(const char*);
#ifdef __cplusplus
}
#endif
#endif

/* HEADERIZER HFILE: none */
/* HEADERIZER BEGIN: static */
/* HEADERIZER END: static */

pmclass FileHandle extends Handle auto_attrs {
    ATTR INTVAL flags;                /* Filehandle flags             */
    ATTR STRING *filename;            /* The opened path and filename */
    ATTR STRING *mode;                /* The mode string used in open */
    ATTR STRING *encoding;            /* The encoding for read/write  */
    ATTR INTVAL process_id;           /* Child process on pipes       */
    ATTR INTVAL exit_status;          /* Child exit status on pipes   */
    ATTR PIOOFF_T file_size;          /* Current file size            */
    ATTR PIOOFF_T file_pos;           /* Current real file pointer    */
    ATTR PIOOFF_T last_pos;           /* Last file position           */
    ATTR size_t buffer_size;          /* Buffer size                  */
    ATTR INTVAL buffer_flags;         /* Buffer flags                 */
    ATTR unsigned char *buffer_start; /* Start of buffer              */
    ATTR unsigned char *buffer_end;   /* End of buffer                */
    ATTR unsigned char *buffer_next;  /* Current read/write pointer   */

/*
 * Using INTVAL for process_id is a temporary solution.
 * We may need to define a custom type to store it in a platform dependant way.
 */

/*

=item C<void init()>

Initializes a newly created FileHandle object.

=cut

*/

    VTABLE void init() {
        Parrot_FileHandle_attributes * const data_struct =
            PMC_data_typed(SELF, Parrot_FileHandle_attributes *);

        data_struct->flags         = 0;
        data_struct->filename      = NULL;
        data_struct->mode          = NULL;
        data_struct->encoding      = NULL;
        data_struct->process_id    = 0;
        data_struct->exit_status   = 0;
        data_struct->file_size     = 0;
        data_struct->file_pos      = piooffsetzero;
        data_struct->last_pos      = piooffsetzero;
        data_struct->buffer_size   = 0;
        data_struct->buffer_flags  = 0;
        data_struct->buffer_start  = NULL;
        data_struct->buffer_end    = NULL;
        data_struct->buffer_next   = NULL;

        /* Initialize the os_handle to the platform-specific value for closed */
        data_struct->os_handle     = (PIOHANDLE) PIO_INVALID_HANDLE;

        PObj_custom_mark_SET(SELF);
        PObj_custom_destroy_SET(SELF);
    }


/*

=item C<PMC *clone()>

Create a copy of the filehandle.

=cut

*/

    VTABLE PMC *clone() {
        Parrot_FileHandle_attributes * const old_struct
            = PARROT_FILEHANDLE(SELF);
        PMC * const copy = Parrot_io_new_pmc(INTERP, old_struct->flags);
        Parrot_FileHandle_attributes * const data_struct
            = PARROT_FILEHANDLE(copy);

        data_struct->os_handle = (PIOHANDLE)Parrot_dup(old_struct->os_handle);

        return copy;
    }


/*

=item C<void mark()>

Mark active filehandle data as live.

=cut

*/

    VTABLE void mark() {
        Parrot_FileHandle_attributes * const data_struct
            = PARROT_FILEHANDLE(SELF);

        Parrot_gc_mark_STRING_alive(INTERP, data_struct->mode);
        Parrot_gc_mark_STRING_alive(INTERP, data_struct->filename);
        Parrot_gc_mark_STRING_alive(INTERP, data_struct->encoding);
    }


/*

=item C<void destroy()>

Free structures.

=cut

*/
    VTABLE void destroy() {
        if (PARROT_FILEHANDLE(SELF)) {
            Parrot_FileHandle_attributes *data_struct = PARROT_FILEHANDLE(SELF);

            if (!Parrot_io_is_closed_filehandle(INTERP, SELF)) {
                if (data_struct->flags & PIO_F_SHARED)
                    Parrot_io_flush_filehandle(INTERP, SELF);
                else
                    Parrot_io_close_filehandle(INTERP, SELF);
            }

            if (data_struct->buffer_start)
                mem_gc_free(INTERP, data_struct->buffer_start);
        }
    }


/*

=item C<INTVAL get_integer_keyed_int(INTVAL key)>

Shortcut to get the value of some attributes.
For internal usage only, subject to change without notice.

=cut

*/

    VTABLE INTVAL get_integer_keyed_int(INTVAL key) {
        INTVAL result;
        switch (key) {
          case 0:
            GET_ATTR_process_id(INTERP, SELF, result);
            break;
          default:
            result = 0;
        }

        return result;
    }


/*

=item C<void set_integer_keyed_int(INTVAL key, INTVAL value)>

Shortcut to set the value of some attributes
For internal usage only, subject to change without notice.

=cut

*/

    VTABLE void set_integer_keyed_int(INTVAL key, INTVAL value) {
        switch (key) {
          case 0:
            SET_ATTR_process_id(INTERP, SELF, value);
            break;
          default:
            break;
        }
    }


/*

=item C<INTVAL get_bool()>

Returns whether the FileHandle has reached the end of the file.

=cut

*/

    VTABLE INTVAL get_bool() {
        return !Parrot_io_eof(INTERP, SELF);
    }


/*

=back

=head2 Methods

=over 4

=item C<METHOD open(STRING *filename :optional, STRING *mode :optional)>

Opens the file at the given filename (including path) with the given mode. The
invocant is modified and becomes an open filehandle. A copy of the invocant is
also returned by the method (some subclasses may create this as the primary
filehandle, rather than modifying the invocant).

=cut

*/

    METHOD open(STRING *filename :optional, INTVAL got_filename :opt_flag,
        STRING *mode :optional, INTVAL got_mode :opt_flag) {
        PMC    *filehandle;
        STRING *open_filename, *open_mode;

        if (!Parrot_io_is_closed_filehandle(INTERP, SELF))
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_PIO_ERROR,
                                "Cannot reopen already open filehandle");

        if (got_mode && !STRING_IS_NULL(mode))
            SET_ATTR_mode(INTERP, SELF, mode);

        if (got_filename && !STRING_IS_NULL(filename))
            SET_ATTR_filename(INTERP, SELF, filename);

        /* Open the file. With no options, reopen the same file as before */
        GET_ATTR_filename(INTERP, SELF, open_filename);
        GET_ATTR_mode(INTERP, SELF, open_mode);

        if (STRING_IS_NULL(open_filename))
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_PIO_ERROR,
                            "Cannot open filehandle, no filename");

        if (STRING_IS_NULL(open_mode)) {
            open_mode = CONST_STRING(INTERP, "r");
            SET_ATTR_mode(INTERP, SELF, open_mode);
        }

        filehandle = Parrot_io_open(INTERP, SELF, open_filename, open_mode);

        RETURN(PMC *filehandle);
    }


/*

=item C<METHOD isatty()>

Returns a boolean value indicating whether C<SELF> is a console/tty.

=cut

*/

    METHOD isatty() {
        const INTVAL isatty = Parrot_io_is_tty(INTERP, SELF);
        RETURN(INTVAL isatty);
    }

/*

=item C<METHOD close()>

Close the filehandle.

=cut

*/

    METHOD close() {
        const INTVAL status = Parrot_io_close(INTERP, SELF);
        RETURN(INTVAL status);
    }


/*

=item C<METHOD is_closed()>

Test if the filehandle is closed.

=cut

*/

    METHOD is_closed() {
        const INTVAL status = Parrot_io_is_closed(INTERP, SELF);
        RETURN(INTVAL status);
    }


/*

=item C<METHOD read(INTVAL bytes)>

Read the given number of bytes from the filehandle and return them in a string.

=cut

*/

    METHOD read(INTVAL length) {
        STRING * const string_result = Parrot_io_reads(INTERP, SELF, length);

        RETURN(STRING *string_result);
    }


/*

=item C<METHOD readline()>

Read a line from the filehandle and return it in a string.

=cut

*/

    METHOD readline() {
        STRING * const string_result = Parrot_io_readline(INTERP, SELF);
        RETURN(STRING *string_result);
    }


/*

=item C<METHOD readline_interactive(STRING *prompt)>

Read a line from the filehandle and return it in a string.

=cut

*/

    METHOD readline_interactive(STRING *prompt     :optional,
                                INTVAL  got_prompt :opt_flag) {
        STRING *string_result = NULL;

#ifdef PARROT_HAS_READLINE
    /* 4-column indent to get c_indent.t to DTRT */
        char * const r = readline(got_prompt ? prompt->strstart : NULL);

        if (r) {
            if (*r)
                add_history(r);

            string_result = Parrot_str_new(INTERP, r, 0);
            mem_internal_free(r);
        }
#else
        if (got_prompt)
            fprintf(stderr, "%s", prompt->strstart);

        if (!(PARROT_FILEHANDLE(SELF)->flags & PIO_F_LINEBUF))
            Parrot_io_setlinebuf(INTERP, SELF);

        string_result = Parrot_io_reads(INTERP, SELF, 0);

        if (string_result) {
            UINTVAL len = Parrot_str_byte_length(INTERP, string_result);
            if (len < 1)
                string_result = NULL;
            else {
                while (len > 0
                &&    (((char *)string_result->strstart)[len - 1] == '\n'
                  ||   ((char *)string_result->strstart)[len - 1] == '\r')) {
                        --len;
                        --string_result->strlen;
                        --string_result->bufused;
                }
            }
        }
#endif

        if (string_result)
            RETURN(STRING *string_result);
        else
            RETURN(PMC *PMCNULL);
    }

/*

=item METHOD readall(STRING *name);

Read the entire contents of a file named I<name> into a Parrot string. On a
filehandle object that isn't opened yet, the path to a file can be passed to
C<readall> and it will open a filehandle on that file, read in the contents,
and close the filehandle.

  .local pmc pio
  pio = new 'FileHandle'
  $S0 = pio.'readall'('the_file')

If the filehandle is already open, then no file path should be passed. The
C<readall> method will read the contents of the file, and will not close the
filehandle when finished.

  pio = open 'the_file', 'r'
  $S0 = pio.'readall'()

=cut

*/

    METHOD readall(STRING *name :optional, INTVAL got_name :opt_flag) {
        STRING *result;

        if (got_name) {
            /* called as class method - open, slurp, close file */
            PMC    *filehandle;
            STRING *encoding;
            size_t  size;

            GET_ATTR_encoding(INTERP, SELF, encoding);

            if (!Parrot_io_is_closed_filehandle(INTERP, SELF))
                Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_PIO_ERROR,
                    "Cannot readall on a new file from an already open filehandle");

            filehandle  = Parrot_io_open(INTERP, PMCNULL, name, NULL);

            PARROT_ASSERT(filehandle->vtable->base_type == enum_class_FileHandle);

            SET_ATTR_encoding(INTERP, filehandle, encoding);
            size = (size_t)(Parrot_stat_info_intval(INTERP, name, STAT_FILESIZE));

            result = Parrot_io_reads(INTERP, filehandle, size);
            Parrot_io_close(INTERP, filehandle);
        }
        else {
            /* slurp open file */
            if (Parrot_io_is_closed_filehandle(INTERP, SELF))
                Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_PIO_ERROR,
                    "Cannot readall without a file name or open filehandle");

            /* Do line buffering on the filehandle */
            if (!(PARROT_FILEHANDLE(SELF)->flags & PIO_F_LINEBUF))
                Parrot_io_setlinebuf(INTERP, SELF);

            result = STRINGNULL;
            do {
                STRING * const part = Parrot_io_reads(INTERP, SELF, 0);
                result = STRING_IS_NULL(result) ? part :
                        Parrot_str_concat(INTERP, result, part);
            } while (!Parrot_io_eof(INTERP, SELF));
        }

        RETURN(STRING *result);
    }


/*

=item C<METHOD flush()>

Flushes the filehandle.

=cut

*/

    METHOD flush() {
        Parrot_io_flush(INTERP, SELF);
    }

/*

=item C<METHOD print([INTVAL|FLOATVAL|STRING *|PMC*] value)>

Print the passed in integer, number, string, or PMC to the filehandle.
(Integers, numbers, and strings are auto-boxed as PMCs.)

=cut

*/

    METHOD print(PMC *to_print) {
        STRING * const string_to_print = VTABLE_get_string(INTERP, to_print);

        Parrot_io_putps(INTERP, SELF, string_to_print);
    }


/*

=item C<METHOD puts(STRING *value)>

Print the string to the filehandle.

=cut

*/

    METHOD puts(STRING *to_print) {
        const INTVAL status = Parrot_io_putps(INTERP, SELF, to_print);
        RETURN(INTVAL status);
    }


/*

=item C<METHOD buffer_type(STRING *new_type :optional)>

Set or retrieve the buffering behavior for the filehandle. The argument and
return value are one of the following:

=over

=item C<unbuffered>

Buffering disabled, bytes are sent as soon as possible.

=item C<line-buffered>

Line buffering, bytes are sent when a record separator is encountered.

=item C<full-buffered>

Full buffering, bytes are sent when the buffer is full.

=cut

*/

    METHOD buffer_type(STRING *new_type :optional, INTVAL got_type :opt_flag) {
        STRING * const nobuffer_string   = CONST_STRING(INTERP, "unbuffered");
        STRING * const linebuffer_string = CONST_STRING(INTERP, "line-buffered");
        STRING * const fullbuffer_string = CONST_STRING(INTERP, "full-buffered");
        INTVAL flags;

        if (got_type) {
            if (Parrot_str_equal(INTERP, new_type, nobuffer_string))
                Parrot_io_setbuf(INTERP, SELF, PIOCTL_NONBUF);
            else if (Parrot_str_equal(INTERP, new_type, linebuffer_string))
                Parrot_io_setlinebuf(INTERP, SELF);
            else if (Parrot_str_equal(INTERP, new_type, fullbuffer_string))
                Parrot_io_setbuf(INTERP, SELF, PIO_UNBOUND);
        }

        GET_ATTR_flags(INTERP, SELF, flags);

        if (flags & PIO_F_LINEBUF)
            RETURN(STRING *linebuffer_string);
        else if (flags & PIO_F_BLKBUF)
            RETURN(STRING *fullbuffer_string);

        RETURN(STRING *nobuffer_string);
    }


/*

=item C<METHOD buffer_size(INTVAL new_size :optional)>

Set or retrieve the buffer size for the filehandle.

=cut

*/

    METHOD buffer_size(INTVAL new_size :optional, INTVAL got_size :opt_flag) {
        INTVAL buffer_size;

        if (got_size)
            Parrot_io_setbuf(INTERP, SELF, (size_t)new_size);

        buffer_size = (INTVAL)Parrot_io_get_buffer_size(INTERP, SELF);

        RETURN(INTVAL buffer_size);
    }


/*

=item C<METHOD mode()>

Retrieve the read mode string for the filehandle.

=cut

*/

    METHOD mode() {
        STRING *mode;
        GET_ATTR_mode(INTERP, SELF, mode);
        RETURN(STRING *mode);
    }


/*

=item C<METHOD encoding(STRING *new_encoding)>

Set or retrieve the encoding attribute (a string name of the selected encoding
scheme) for the filehandle.

=cut

*/

    METHOD encoding(STRING *new_encoding :optional, INTVAL got_encoding :opt_flag) {
        STRING *encoding;

        if (got_encoding) {
            SET_ATTR_encoding(INTERP, SELF, new_encoding);
            RETURN(STRING *new_encoding);
        }

        GET_ATTR_encoding(INTERP, SELF, encoding);
        RETURN(STRING *encoding);
    }


/*

=item C<METHOD eof()>

Returns true if the filehandle is at end-of-file, returns false otherwise.

=cut

*/

    METHOD eof() {
        const INTVAL flags = Parrot_io_eof(INTERP, SELF);
        RETURN(INTVAL flags);
    }

/*

=item C<METHOD exit_status()>

If this is a pipe, return the exit status of the child process.

=cut

*/
    METHOD exit_status() {
        INTVAL exit_status;
        GET_ATTR_exit_status(INTERP, SELF, exit_status);
        RETURN(INTVAL exit_status);
    }


/*

=item C<METHOD get_fd()>

Retrieve the integer file descriptor for the FileHandle (only available on
platforms that use integer file descriptors).

=cut

*/

    METHOD get_fd() {
#ifndef PIO_OS_STDIO
        PIOHANDLE os_handle;
        INTVAL    fd;
        GET_ATTR_os_handle(INTERP, SELF, os_handle);
        fd = (INTVAL)os_handle;
        RETURN(INTVAL fd);
#endif /*PIO_OS_STDIO*/

        RETURN(INTVAL -1);
    }

/*

=item C<METHOD tell()>

Get the file position of the stream. 2 C<INTVAL>s are returned. The first is
the position. The second is the position shifted down by 32 bits to handle
overflows on 32-bit systems.

=cut

*/

    METHOD tell() {
        PIOOFF_T pos;
        GET_ATTR_file_pos(INTERP, SELF, pos);
        RETURN(INTVAL pos, INTVAL pos >> 32);
    }

/*

=item C<METHOD seek(INTVAL whence, INTVAL offs, INTVAL offs_overflow)>

Set the file position to an offset specified by C<offs> (and optionally
C<offs_overflow>). C<whence> determines from where in the file the offset is
taken.

 Whence Value      Meaning
 0                 Seek from the beginning of the file
 1                 Seek from the current position
 2                 Seek from the end of the file

C<offs_overflow> is optional and is used to handle offsets higher than 2Gb on
32bit systems.

=cut

*/

    METHOD seek(INTVAL whence, INTVAL offs, INTVAL offs_overflow :optional,
                int has_overflow :opt_flag) {
        const PIOOFF_T pio_offs = has_overflow ?
                                    Parrot_io_make_offset32(offs_overflow, offs) :
                                    offs;
        if (0 > Parrot_io_seek(INTERP, SELF, pio_offs, whence))
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_PIO_ERROR, "seek failed");
    }

/*

=item C<METHOD peek()>

Returns the next byte from the stream, but does not remove it.

=cut

*/

    METHOD peek() {
        STRING *s = NULL;
        if (Parrot_io_peek(INTERP, SELF, &s) < 0)
            RETURN(STRING* STRINGNULL);
        else
            RETURN(STRING* s);
    }

/*

=back

=cut

*/

} /* end pmclass */

/*
 * Local variables:
 *   c-file-style: "parrot"
 * End:
 * vim: expandtab shiftwidth=4:
 */
