/* GNU mailutils - a suite of utilities for electronic mail
   Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Library Public License as published by
   the Free Software Foundation; either version 2, 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 Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include <mailutils/error.h>
#include <mailutils/sys/memstream.h>

#undef min
#define min(a,b) ((a) < (b) ? (a) : (b))

int
_stream_memory_ref (stream_t stream)
{
  struct _stream_memory *mem = (struct _stream_memory *)stream;
  return mu_refcount_inc (mem->refcount);
}

void
_stream_memory_destroy (stream_t *pstream)
{
  struct _stream_memory *mem = (struct _stream_memory *)*pstream;
  if (mu_refcount_dec (mem->refcount) == 0)
    {
      mu_refcount_destroy (&mem->refcount);
      free (mem);
    }
}

int
_stream_memory_read (stream_t stream, void *optr, size_t osize,
		     off_t offset, size_t *nbytes)
{
  struct _stream_memory *mem = (struct _stream_memory *)stream;
  size_t n = 0;

  mu_refcount_lock (mem->refcount);
  if (mem->ptr != NULL && (offset < (off_t)mem->size))
    {
      n = ((offset + osize) > mem->size) ? mem->size - offset :  osize;
      memcpy (optr, mem->ptr + offset, n);
    }
  mu_refcount_unlock (mem->refcount);
  if (nbytes)
    *nbytes = n;
  return 0;
}

int
_stream_memory_readline (stream_t stream, char *optr, size_t osize,
			 off_t offset, size_t *nbytes)
{
  struct _stream_memory *mem = (struct _stream_memory *)stream;
  char *nl;
  size_t n = 0;
  mu_refcount_lock (mem->refcount);
  if (mem->ptr && (offset < (off_t)mem->size))
    {
      /* Save space for the null byte.  */
      osize--;
      nl = memchr (mem->ptr + offset, '\n', mem->size - offset);
      n = (nl) ? (size_t)(nl - (mem->ptr + offset) + 1) : mem->size - offset;
      n = min (n, osize);
      memcpy (optr, mem->ptr + offset, n);
      optr[n] = '\0';
    }
  mu_refcount_unlock (mem->refcount);
  if (nbytes)
    *nbytes = n;
  return 0;
}

int
_stream_memory_write (stream_t stream, const void *iptr, size_t isize,
		      off_t offset, size_t *nbytes)
{
  struct _stream_memory *mem = (struct _stream_memory *)stream;

  mu_refcount_lock (mem->refcount);
  /* Bigger we have to realloc.  */
  if (mem->capacity < (offset + isize))
    {
      /* Realloc by blocks of 512.  */
      int newsize = MU_STREAM_MEMORY_BLOCKSIZE *
	(((offset + isize)/MU_STREAM_MEMORY_BLOCKSIZE) + 1);
      char *tmp =  realloc (mem->ptr, newsize);
      if (tmp == NULL)
	return ENOMEM;
      mem->ptr = tmp;
      mem->size = offset + isize;
      mem->capacity = newsize;
    }

  memcpy (mem->ptr + offset, iptr, isize);
  mu_refcount_unlock (mem->refcount);
  if (nbytes)
    *nbytes = isize;
  return 0;
}

int
_stream_memory_truncate (stream_t stream, off_t len)
{
  struct _stream_memory *mem = (struct _stream_memory *)stream;

  mu_refcount_lock (mem->refcount);
  if (len == 0)
    {
      if (mem->ptr)
	free (mem->ptr);
      mem->ptr = NULL;
    }
  else
    {
      char *tmp = realloc (mem, len);
      if (tmp == NULL)
	return ENOMEM;
      mem->ptr = tmp;
    }
  mem->capacity = len;
  mem->size = len;
  mu_refcount_unlock (mem->refcount);
  return 0;
}

int
_stream_memory_get_size (stream_t stream, off_t *psize)
{
  struct _stream_memory *mem = (struct _stream_memory *)stream;
  mu_refcount_lock (mem->refcount);
  if (psize)
    *psize = mem->size;
  mu_refcount_unlock (mem->refcount);
  return 0;
}

int
_stream_memory_close (stream_t stream)
{
  struct _stream_memory *mem = (struct _stream_memory *)stream;
  mu_refcount_lock (mem->refcount);
  if (mem->ptr)
    free (mem->ptr);
  mem->ptr = NULL;
  mem->capacity = 0;
  mem->size = 0;
  mu_refcount_unlock (mem->refcount);
  return 0;
}

int
_stream_memory_flush (stream_t stream)
{
  (void)stream;
  return 0;
}

int
_stream_memory_get_fd (stream_t stream, int *pfd)
{
  (void)stream; (void)pfd;
  return MU_ERROR_NOT_SUPPORTED;
}

int
_stream_memory_get_flags (stream_t stream, int *flags)
{
  struct _stream_memory *mem = (struct _stream_memory *)stream;
  if (flags == NULL)
    return MU_ERROR_INVALID_PARAMETER;
  *flags = mem->flags;
  return 0;
}

int
_stream_memory_get_state (stream_t stream, enum stream_state *state)
{
  (void)stream;
  if (state == NULL)
    return MU_ERROR_INVALID_PARAMETER;
  *state = MU_STREAM_NO_STATE;
  return 0;
}

int
_stream_memory_is_seekable (stream_t stream)
{
  (void)stream;
  return 1;
}

int
_stream_memory_tell (stream_t stream, off_t *off)
{
  (void)stream;
  if (off == NULL)
    return MU_ERROR_INVALID_PARAMETER;
  *off = 0;
  return 0;
}

int
_stream_memory_is_open (stream_t stream)
{
  (void)stream;
  return 1;
}

int
_stream_memory_is_readready (stream_t stream, int timeout)
{
  (void)stream;
  (void)timeout;
  return 1;
}

int
_stream_memory_is_writeready (stream_t stream, int timeout)
{
  (void)stream;
  (void)timeout;
  return 1;
}

int
_stream_memory_is_exceptionpending (stream_t stream, int timeout)
{
  (void)stream;
  (void)timeout;
  return 0;
}


int
_stream_memory_open (stream_t stream, const char *filename, int port,
		     int flags)
{
  struct _stream_memory *mem = (struct _stream_memory *)stream;
  int status = 0;

  (void)port; /* Ignored.  */

  mu_refcount_lock (mem->refcount);
  /* Free any previous memory.  */
  if (mem->ptr)
    free (mem->ptr);
  mem->ptr = NULL;
  mem->capacity = 0;
  mem->size = 0;
  mem->flags = flags;
  if (filename)
    {
      struct stat statbuf;
      if (stat (filename, &statbuf) == 0)
	{
	  mem->ptr = calloc (1, statbuf.st_size);
	  if (mem->ptr)
	    {
	      FILE *fp;
	      mem->capacity = statbuf.st_size;
	      mem->size = statbuf.st_size;
	      fp = fopen (filename, "r");
	      if (fp)
		{
		  size_t r = fread (mem->ptr, mem->size, 1, fp);
		  if (r != mem->size)
		    status = MU_ERROR_IO;
		  fclose (fp);
		}
	      else
		status = errno;
	      if (status != 0)
		{
		  free (mem->ptr);
		  mem->ptr = NULL;
		  mem->capacity = 0;
		  mem->size = 0;
		}
	    }
	  else
	    status = MU_ERROR_NO_MEMORY;
	}
      else
	status = MU_ERROR_IO;
    }
  mu_refcount_unlock (mem->refcount);
  return status;
}

static struct _stream_vtable _stream_memory_vtable =
{
  _stream_memory_ref,
  _stream_memory_destroy,

  _stream_memory_open,
  _stream_memory_close,

  _stream_memory_read,
  _stream_memory_readline,
  _stream_memory_write,

  _stream_memory_tell,

  _stream_memory_get_size,
  _stream_memory_truncate,
  _stream_memory_flush,

  _stream_memory_get_fd,
  _stream_memory_get_flags,
  _stream_memory_get_state,

  _stream_memory_is_seekable,
  _stream_memory_is_readready,
  _stream_memory_is_writeready,
  _stream_memory_is_exceptionpending,

  _stream_memory_is_open
};

int
_stream_memory_ctor (struct _stream_memory *mem, size_t capacity)
{
  mu_refcount_create (&mem->refcount);
  if (mem->refcount == NULL)
    return MU_ERROR_NO_MEMORY;
  if (capacity)
    {
      mem->ptr = calloc (1, capacity);
      if (mem->ptr == NULL)
	{
	  mu_refcount_destroy (&mem->refcount);
	  return MU_ERROR_NO_MEMORY;
	}
      mem->capacity = capacity;
    }
  else
    mem->capacity = 0;
  mem->size = 0;
  mem->flags = 0;
  mem->base.vtable = &_stream_memory_vtable;
  return 0;
}

void
_stream_memory_dtor (stream_t stream)
{
  struct _stream_memory *mem = (struct _stream_memory *)stream;
  if (mem)
    {
      mu_refcount_destroy (&mem->refcount);
      mem->ptr = NULL;
      mem->capacity = 0;
      mem->size = 0;
      mem->flags = 0;
    }
}

int
stream_memory_create (stream_t *pstream, size_t capacity)
{
  struct _stream_memory *mem;
  int status;

  if (pstream == NULL)
    return MU_ERROR_INVALID_PARAMETER;

  mem = calloc (1, sizeof (*mem));
  if (mem == NULL)
    return MU_ERROR_NO_MEMORY;

  status = _stream_memory_ctor (mem, capacity);
  if (status != 0)
    {
      free (mem);
      return status;
    }
  mem->base.vtable = &_stream_memory_vtable;
  *pstream = &mem->base;
  return 0;
}