/*
 *   moofilewriter.c
 *
 *   Copyright (C) 2004-2010 by Yevgen Muntyan <emuntyan@users.sourceforge.net>
 *
 *   This file is part of medit.  medit is free software; you can
 *   redistribute it and/or modify it under the terms of the
 *   GNU Lesser General Public License as published by the
 *   Free Software Foundation; either version 2.1 of the License,
 *   or (at your option) any later version.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with medit.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>
#include "mooutils/moofilewriter-private.h"
#include "mooutils/mootype-macros.h"
#include "mooutils/mooutils-fs.h"
#include "mooutils/mooutils-misc.h"
#include "mooutils/mooi18n.h"
#include "mooutils/moocompat.h"
#include <stdio.h>
#include <string.h>
#include <mooglib/moo-glib.h>
#include <gio/gio.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

/************************************************************************/
/* MooFileReader
 */

struct _MooFileReader {
    GObject base;
    MGW_FILE *file;
};

G_DEFINE_TYPE (MooFileReader, moo_file_reader, G_TYPE_OBJECT)

static void
moo_file_reader_init (MooFileReader *reader)
{
    reader->file = NULL;
}

static void
moo_file_reader_close_file (MooFileReader *reader)
{
    if (reader->file)
    {
        mgw_fclose (reader->file);
        reader->file = NULL;
    }
}

static void
moo_file_reader_dispose (GObject *object)
{
    MooFileReader *reader = MOO_FILE_READER (object);
    moo_file_reader_close_file (reader);
    G_OBJECT_CLASS (moo_file_reader_parent_class)->dispose (object);
}

static void
moo_file_reader_class_init (MooFileReaderClass *klass)
{
    G_OBJECT_CLASS (klass)->dispose = moo_file_reader_dispose;
}

void
moo_file_reader_close (MooFileReader *reader)
{
    g_return_if_fail (MOO_IS_FILE_READER (reader));
    moo_file_reader_close_file (reader);
    g_object_unref (reader);
}

static MooFileReader *
moo_file_reader_new_real (const char  *filename,
                          gboolean     binary,
                          GError     **error)
{
    const char *mode = binary ? "rb" : "r";
    MGW_FILE *file;
    MooFileReader *reader;
    mgw_errno_t err;

    g_return_val_if_fail (filename != NULL, NULL);
    g_return_val_if_fail (!error || !*error, NULL);

    if (!(file = mgw_fopen (filename, mode, &err)))
    {
        g_set_error (error, MOO_FILE_ERROR,
                     _moo_file_error_from_errno (err),
                     _("Could not open %s: %s"), filename,
                     mgw_strerror (err));
        return NULL;
    }

    reader = MOO_FILE_READER (g_object_new (MOO_TYPE_FILE_READER, (const char*) NULL));
    reader->file = file;

    return reader;
}

MooFileReader *
moo_file_reader_new (const char  *filename,
                     GError     **error)
{
    return moo_file_reader_new_real (filename, TRUE, error);
}

MooFileReader *
moo_text_reader_new (const char  *filename,
                     GError     **error)
{
    return moo_file_reader_new_real (filename, FALSE, error);
}

gboolean
moo_file_reader_read (MooFileReader  *reader,
                      char           *buf,
                      gsize           buf_size,
                      gsize          *size_read_p,
                      GError        **error)
{
    gsize size_read;
    mgw_errno_t err;

    g_return_val_if_fail (MOO_IS_FILE_READER (reader), FALSE);
    g_return_val_if_fail (size_read_p != NULL, FALSE);
    g_return_val_if_fail (!error || !*error, FALSE);
    g_return_val_if_fail (reader->file != NULL, FALSE);
    g_return_val_if_fail (buf_size == 0 || buf != NULL, FALSE);

    if (buf_size == 0)
        return TRUE;

    size_read = mgw_fread (buf, 1, buf_size, reader->file, &err);

    if (size_read != buf_size && mgw_ferror (reader->file))
    {
        g_set_error (error, MOO_FILE_ERROR,
                     _moo_file_error_from_errno (err),
                     "error reading file: %s",
                     mgw_strerror (err));
        return FALSE;
    }

    *size_read_p = size_read;
    return TRUE;
}


/************************************************************************/
/* MooFileWriter
 */

G_DEFINE_TYPE (MooFileWriter, moo_file_writer, G_TYPE_OBJECT)

static void
moo_file_writer_init (G_GNUC_UNUSED MooFileWriter *writer)
{
}

static void
moo_file_writer_class_init (G_GNUC_UNUSED MooFileWriterClass *klass)
{
}

gboolean
moo_file_writer_write (MooFileWriter *writer,
                       const char    *data,
                       gssize         len)
{
    g_return_val_if_fail (MOO_IS_FILE_WRITER (writer), FALSE);
    g_return_val_if_fail (data != NULL, FALSE);

    if (len < 0)
        len = strlen (data);
    if (!len)
        return TRUE;

    return MOO_FILE_WRITER_GET_CLASS (writer)->meth_write (writer, data, len);
}

gboolean
moo_file_writer_printf (MooFileWriter  *writer,
                        const char     *fmt,
                        ...)
{
    va_list args;
    gboolean ret;

    g_return_val_if_fail (MOO_IS_FILE_WRITER (writer), FALSE);
    g_return_val_if_fail (fmt != NULL, FALSE);

    va_start (args, fmt);
    ret = MOO_FILE_WRITER_GET_CLASS (writer)->meth_printf (writer, fmt, args);
    va_end (args);

    return ret;
}

gboolean
moo_file_writer_printf_markup (MooFileWriter  *writer,
                               const char     *fmt,
                               ...)
{
    va_list args;
    gboolean ret;
    char *string;

    g_return_val_if_fail (MOO_IS_FILE_WRITER (writer), FALSE);
    g_return_val_if_fail (fmt != NULL, FALSE);

    va_start (args, fmt);
    string = g_markup_vprintf_escaped (fmt, args);
    va_end (args);

    g_return_val_if_fail (string != NULL, FALSE);

    ret = moo_file_writer_write (writer, string, -1);

    g_free (string);
    return ret;
}

gboolean
moo_file_writer_close (MooFileWriter  *writer,
                       GError        **error)
{
    gboolean ret;

    g_return_val_if_fail (MOO_IS_FILE_WRITER (writer), FALSE);
    g_return_val_if_fail (!error || !*error, FALSE);

    ret = MOO_FILE_WRITER_GET_CLASS (writer)->meth_close (writer, error);

    g_object_unref (writer);
    return ret;
}


/************************************************************************/
/* MooLocalFileWriter
 */

struct _MooLocalFileWriter {
    MooFileWriter base;
    GFile *file;
    GOutputStream *stream;
    MooFileWriterFlags flags;
    GError *error;
};

G_DEFINE_TYPE (MooLocalFileWriter, moo_local_file_writer, MOO_TYPE_FILE_WRITER)

static MooFileWriter *
moo_local_file_writer_new (GFile               *file,
                           MooFileWriterFlags   flags,
                           GError             **error)
{
    MooLocalFileWriter *writer = NULL;
    GFileOutputStream *stream = NULL;
    GFile *file_copy = NULL;

    g_return_val_if_fail (G_IS_FILE (file), NULL);

    if (flags & MOO_FILE_WRITER_CONFIG_MODE)
    {
        char *dirname;
        char *filename;
        mgw_errno_t err;

        filename = g_file_get_path (file);
        dirname = filename ? g_path_get_dirname (filename) : NULL;

        if (dirname && _moo_mkdir_with_parents (dirname, &err) != 0)
        {
            char *display_name = g_filename_display_name (dirname);
            g_set_error (error, G_FILE_ERROR,
                         mgw_file_error_from_errno (err),
                         _("Could not create folder %s: %s"),
                         display_name, mgw_strerror (err));
            g_free (display_name);
            g_free (dirname);
            goto error;
        }

        g_free (dirname);
        g_free (filename);
    }

    file_copy = g_file_dup (file);
    stream = g_file_replace (file_copy, NULL,
                             (flags & MOO_FILE_WRITER_SAVE_BACKUP) != 0,
                             G_FILE_CREATE_NONE,
                             NULL, error);

    if (!stream)
        goto error;

    writer = (MooLocalFileWriter*) g_object_new (MOO_TYPE_LOCAL_FILE_WRITER, (const char*) NULL);
    writer->file = file_copy;
    writer->stream = G_OUTPUT_STREAM (stream);
    writer->flags = flags;

    return MOO_FILE_WRITER (writer);

error:
    if (file_copy)
        g_object_unref (file_copy);
    if (stream)
        g_object_unref (stream);
    return NULL;
}

MooFileWriter *
moo_file_writer_new (const char          *filename,
                     MooFileWriterFlags   flags,
                     GError             **error)
{
    GFile *file;
    MooFileWriter *writer;

    g_return_val_if_fail (filename != NULL, NULL);
    g_return_val_if_fail (!error || !*error, NULL);

    file = g_file_new_for_path (filename);
    writer = moo_local_file_writer_new (file, flags, error);

    g_object_unref (file);
    return writer;
}

MooFileWriter *
moo_file_writer_new_for_file (GFile               *file,
                              MooFileWriterFlags   flags,
                              GError             **error)
{
    g_return_val_if_fail (G_IS_FILE (file), NULL);
    g_return_val_if_fail (!error || !*error, NULL);
    return moo_local_file_writer_new (file, flags, error);
}

MooFileWriter *
moo_config_writer_new (const char  *filename,
                       gboolean     save_backup,
                       GError     **error)
{
    MooFileWriterFlags flags;

    g_return_val_if_fail (filename != NULL, NULL);
    g_return_val_if_fail (!error || !*error, NULL);

    flags = (MooFileWriterFlags) (MOO_FILE_WRITER_CONFIG_MODE | MOO_FILE_WRITER_TEXT_MODE);
    if (save_backup)
        flags = (MooFileWriterFlags) (flags | MOO_FILE_WRITER_SAVE_BACKUP);

    return moo_file_writer_new (filename, flags, error);
}


static gboolean
moo_local_file_writer_write (MooFileWriter *fwriter,
                             const char    *data,
                             gsize          len)
{
    MooLocalFileWriter *writer = (MooLocalFileWriter*) fwriter;

    if (writer->error)
        return FALSE;

    while (len > 0)
    {
        gsize chunk_len = len;
        gsize next_chunk = len;
        gsize bytes_written;

        if (!g_output_stream_write_all (writer->stream,
                                        data, chunk_len,
                                        &bytes_written, NULL,
                                        &writer->error))
            return FALSE;

        data += next_chunk;
        len -= next_chunk;
    }

    return TRUE;
}

G_GNUC_PRINTF (2, 0)
static gboolean
moo_local_file_writer_printf (MooFileWriter *fwriter,
                              const char    *fmt,
                              va_list        args)
{
    MooLocalFileWriter *writer = (MooLocalFileWriter*) fwriter;
    char *text;
    gboolean retval;

    if (writer->error)
        return FALSE;

    text = g_strdup_vprintf (fmt, args);
    retval = moo_local_file_writer_write (fwriter, text, strlen (text));
    g_free (text);

    return retval;
}

static gboolean
moo_local_file_writer_close (MooFileWriter *fwriter,
                             GError       **error)
{
    MooLocalFileWriter *writer = (MooLocalFileWriter*) fwriter;
    gboolean retval;

    g_return_val_if_fail (writer->stream != NULL, FALSE);

    if (!writer->error)
    {
        g_output_stream_flush (writer->stream, NULL, &writer->error);
        g_output_stream_close (writer->stream, NULL,
                               writer->error ? NULL : &writer->error);
        g_object_unref (writer->stream);
        g_object_unref (writer->file);
        writer->stream = NULL;
        writer->file = NULL;
    }

    retval = writer->error == NULL;

    if (writer->error)
        g_propagate_error (error, writer->error);

    writer->error = NULL;
    return retval;
}


static void
moo_local_file_writer_class_init (MooLocalFileWriterClass *klass)
{
    MooFileWriterClass *writer_class = MOO_FILE_WRITER_CLASS (klass);
    writer_class->meth_write = moo_local_file_writer_write;
    writer_class->meth_printf = moo_local_file_writer_printf;
    writer_class->meth_close = moo_local_file_writer_close;
}

static void
moo_local_file_writer_init (MooLocalFileWriter *writer)
{
    writer->file = NULL;
    writer->stream = NULL;
    writer->error = NULL;
}


/************************************************************************/
/* MooStringWriter
 */

struct _MooStringWriter {
    MooFileWriter base;
    GString *string;
};

G_DEFINE_TYPE (MooStringWriter, moo_string_writer, MOO_TYPE_FILE_WRITER)

static gboolean
moo_string_writer_write (MooFileWriter *fwriter,
                         const char    *data,
                         gsize          len)
{
    MooStringWriter *writer = (MooStringWriter*) fwriter;
    g_string_append_len (writer->string, data, len);
    return TRUE;
}

G_GNUC_PRINTF (2, 0)
static gboolean
moo_string_writer_printf (MooFileWriter *fwriter,
                          const char    *fmt,
                          va_list        args)
{
    char *buf = NULL;
    gint len = g_vasprintf (&buf, fmt, args);

    if (len >= 0)
        g_string_append_len (((MooStringWriter*) fwriter)->string, buf, len);

    g_free (buf);

    return TRUE;
}

static gboolean
moo_string_writer_close (MooFileWriter *fwriter,
                         G_GNUC_UNUSED GError **error)
{
    MooStringWriter *writer = (MooStringWriter*) fwriter;
    g_string_free (writer->string, TRUE);
    writer->string = NULL;
    return TRUE;
}

static void
moo_string_writer_class_init (MooStringWriterClass *klass)
{
    MooFileWriterClass *writer_class = MOO_FILE_WRITER_CLASS (klass);
    writer_class->meth_write = moo_string_writer_write;
    writer_class->meth_printf = moo_string_writer_printf;
    writer_class->meth_close = moo_string_writer_close;
}

static void
moo_string_writer_init (MooStringWriter *writer)
{
    writer->string = g_string_new (NULL);
}
