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

   This library 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 of the License, or (at your option) any later version.

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  */

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

#include <stdlib.h>
#include <errno.h>

#include <mailutils/debug.h>
#include <mailutils/errno.h>
#include <mailutils/error.h>
#include <mailutils/folder.h>
#include <mailutils/iterator.h>
#include <mailutils/list.h>
#include <mailutils/locker.h>
#include <mailutils/observer.h>
#include <mailutils/property.h>
#include <mailutils/registrar.h>
#include <mailutils/stream.h>
#include <mailutils/url.h>
#include <mailutils/attribute.h>
#include <mailutils/message.h>

#include <mailbox0.h>

/* The Mailbox Factory.
   Create an iterator for registrar and see if any url scheme match,
   Then we call the mailbox's url_create() to parse the URL. Last
   initialize the concrete mailbox and folder.  */
int
mailbox_create (mailbox_t *pmbox, const char *name)
{
  int status = EINVAL;
  record_t record = NULL;
  int (*m_init) __P ((mailbox_t)) = NULL;
  int (*u_init) __P ((url_t)) = NULL;
  iterator_t iterator;
  list_t list;
  int found = 0;

  if (pmbox == NULL)
    return MU_ERR_OUT_PTR_NULL;

  /* Look in the registrar, for a match  */
  registrar_get_list (&list);
  status = list_get_iterator (list, &iterator);
  if (status != 0)
    return status;
  for (iterator_first (iterator); !iterator_is_done (iterator);
       iterator_next (iterator))
    {
      iterator_current (iterator, (void **)&record);
      if (record_is_scheme (record, name))
	{
	  record_get_mailbox (record, &m_init);
	  record_get_url (record, &u_init);
	  if (m_init && u_init)
	    {
	      found = 1;
	      break;
	    }
	}
    }
  iterator_destroy (&iterator);

  if (found)
    {
      url_t url = NULL;
      mailbox_t mbox;

      /* Allocate memory for mbox.  */
      mbox = calloc (1, sizeof (*mbox));
      if (mbox == NULL)
	return ENOMEM;

      /* Initialize the internal lock now, so the concrete mailbox
	 could use it. */
      status = monitor_create (&(mbox->monitor), 0, mbox);
      if (status != 0)
	{
	  mailbox_destroy (&mbox);
	  return status;
	}

      /* Parse the url, it may be a bad one and we should bailout if this
	 failed.  */
      if ((status = url_create (&url, name)) != 0
	  || (status = u_init (url)) != 0)
	{
	  mailbox_destroy (&mbox);
	  return status;
	}
      mbox->url = url;

      /* Create the folder before initializing the concrete mailbox.
	 The mailbox needs it's back pointer. */
      status = folder_create (&mbox->folder, name);

      if (status == 0)
	status = m_init (mbox);   /* Create the concrete mailbox type.  */

      if (status != 0)
	mailbox_destroy (&mbox);
      else
	*pmbox = mbox;
    }
  else
    status = MU_ERR_NO_HANDLER;

  return status;
}

void
mailbox_destroy (mailbox_t *pmbox)
{
  if (pmbox && *pmbox)
    {
      mailbox_t mbox = *pmbox;
      monitor_t monitor = mbox->monitor;

      /* Notify the observers.  */
      if (mbox->observable)
	{
	  observable_notify (mbox->observable, MU_EVT_MAILBOX_DESTROY);
	  observable_destroy (&(mbox->observable), mbox);
	}

      /* Call the concrete mailbox _destroy method. So it can clean itself.  */
      if (mbox->_destroy)
	mbox->_destroy (mbox);

      monitor_wrlock (monitor);

      /* Close the stream and nuke it */
      if (mbox->stream)
	{
	  /* FIXME:  Is this right, should be the client responsabilty to close
	     the stream?  */
	  /* stream_close (mbox->stream); */
	  stream_destroy (&(mbox->stream), mbox);
	}

      if (mbox->url)
        url_destroy (&(mbox->url));

      if (mbox->locker)
	locker_destroy (&(mbox->locker));

      if (mbox->debug)
	mu_debug_destroy (&(mbox->debug), mbox);

      if (mbox->folder)
	folder_destroy (&(mbox->folder));

      if (mbox->property)
	property_destroy (&(mbox->property), mbox);

      free (mbox);
      *pmbox = NULL;
      monitor_unlock (monitor);
      monitor_destroy (&monitor, mbox);
    }
}


/* -------------- stub functions ------------------- */

int
mailbox_open (mailbox_t mbox, int flag)
{
  if (mbox == NULL || mbox->_open == NULL)
    return MU_ERR_EMPTY_VFN;
  return mbox->_open (mbox, flag);
}

int
mailbox_close (mailbox_t mbox)
{
  if (mbox == NULL || mbox->_close == NULL)
    return MU_ERR_EMPTY_VFN;

  return mbox->_close (mbox);
}

int
mailbox_flush (mailbox_t mbox, int expunge)
{
  size_t i, total = 0;
  int status = 0;

  if (!mbox)
    return EINVAL;
  if (!(mbox->flags & (MU_STREAM_RDWR|MU_STREAM_WRITE|MU_STREAM_APPEND)))
    return EACCES;
  mailbox_messages_count (mbox, &total);
  for (i = 1; i <= total; i++)
    {
      message_t msg = NULL;
      attribute_t attr = NULL;
      mailbox_get_message (mbox, i, &msg);
      message_get_attribute (msg, &attr);
      attribute_set_seen (attr);
    }
  if (expunge)
    status = mailbox_expunge (mbox);
  else
    status = mailbox_save_attributes (mbox);
  return status;
}

/* messages */
int
mailbox_append_message (mailbox_t mbox, message_t msg)
{
  if (mbox == NULL || mbox->_append_message == NULL)
    return MU_ERR_EMPTY_VFN;
  if (!(mbox->flags & (MU_STREAM_RDWR|MU_STREAM_WRITE|MU_STREAM_APPEND)))
    return EACCES;
  return mbox->_append_message (mbox, msg);
}

int
mailbox_get_message (mailbox_t mbox, size_t msgno,  message_t *pmsg)
{
  if (mbox == NULL || mbox->_get_message == NULL)
    return MU_ERR_EMPTY_VFN;
  return mbox->_get_message (mbox, msgno, pmsg);
}

int
mailbox_messages_count (mailbox_t mbox, size_t *num)
{
  if (mbox == NULL || mbox->_messages_count == NULL)
    return MU_ERR_EMPTY_VFN;
  return mbox->_messages_count (mbox, num);
}

int
mailbox_messages_recent (mailbox_t mbox, size_t *num)
{
  if (mbox == NULL || mbox->_messages_recent == NULL)
    return MU_ERR_EMPTY_VFN;
  return mbox->_messages_recent (mbox, num);
}

int
mailbox_message_unseen (mailbox_t mbox, size_t *num)
{
  if (mbox == NULL || mbox->_message_unseen == NULL)
    return MU_ERR_EMPTY_VFN;
  return mbox->_message_unseen (mbox, num);
}

int
mailbox_save_attributes (mailbox_t mbox)
{
  if (mbox == NULL || mbox->_save_attributes == NULL)
    return MU_ERR_EMPTY_VFN;
  if (!(mbox->flags & (MU_STREAM_RDWR|MU_STREAM_WRITE|MU_STREAM_APPEND)))
    return EACCES;
  return mbox->_save_attributes (mbox);
}

int
mailbox_expunge (mailbox_t mbox)
{
  if (mbox == NULL || mbox->_expunge == NULL)
    return MU_ERR_EMPTY_VFN;
  if (!(mbox->flags & (MU_STREAM_RDWR|MU_STREAM_WRITE|MU_STREAM_APPEND)))
    return EACCES;
  return mbox->_expunge (mbox);
}

int
mailbox_is_updated (mailbox_t mbox)
{
  if (mbox == NULL || mbox->_is_updated == NULL)
    return 1;
  return mbox->_is_updated (mbox);
}

int
mailbox_scan (mailbox_t mbox, size_t msgno, size_t *pcount)
{
  if (mbox == NULL || mbox->_scan == NULL)
    return MU_ERR_EMPTY_VFN;
  return mbox->_scan (mbox, msgno, pcount);
}

int
mailbox_get_size (mailbox_t mbox, off_t *psize)
{
  if (mbox == NULL || mbox->_get_size == NULL)
    return MU_ERR_EMPTY_VFN;
  return mbox->_get_size (mbox, psize);
}

int
mailbox_uidvalidity (mailbox_t mbox, unsigned long *pvalid)
{
  if (mbox == NULL || mbox->_uidvalidity == NULL)
    return MU_ERR_EMPTY_VFN;
  return mbox->_uidvalidity (mbox, pvalid);
}

int
mailbox_uidnext (mailbox_t mbox, size_t *puidnext)
{
  if (mbox == NULL || mbox->_uidnext == NULL)
    return MU_ERR_EMPTY_VFN;
  return mbox->_uidnext (mbox, puidnext);
}

/* locking */
int
mailbox_set_locker (mailbox_t mbox, locker_t locker)
{
  if (mbox == NULL)
    return MU_ERR_MBX_NULL;
  if (mbox->locker)
    locker_destroy (&mbox->locker);
  mbox->locker = locker;
  return 0;
}

int
mailbox_get_locker (mailbox_t mbox, locker_t *plocker)
{
  if (mbox == NULL)
    return MU_ERR_MBX_NULL;
  if (plocker == NULL)
    return MU_ERR_OUT_PTR_NULL;
  *plocker = mbox->locker;
  return 0;
}

int
mailbox_set_stream (mailbox_t mbox, stream_t stream)
{
  if (mbox == NULL)
    return MU_ERR_MBX_NULL;
  if (mbox->stream)
    stream_destroy (&(mbox->stream), mbox);
  mbox->stream = stream;
  return 0;
}

/* FIXME: This is a problem.  We provide a mailbox_get_stream ()
   and this stream is special it should, in theory, represent
   a "view" of a flow of messages.  But providing this perspective
   may make sense for local mailboxes but downright impossible
   for a remote mailbox, short on downloading the entire mailbox
   locally.
   The question is : should this function be removed?
   So far it as been used on local mailboxes to get offsets.  */
int
mailbox_get_stream (mailbox_t mbox, stream_t *pstream)
{
  if (mbox == NULL)
    return MU_ERR_MBX_NULL;
  if (pstream == NULL)
    return MU_ERR_OUT_PTR_NULL;

  /* If null two cases:
     - it is no open yet.
     - it a remote stream and the socket stream is on the folder.  */
  if (mbox->stream == NULL)
    {
      if (mbox->folder)
	return folder_get_stream (mbox->folder, pstream);
    }
  *pstream = mbox->stream;
  return 0;
}

int
mailbox_get_observable (mailbox_t mbox, observable_t *pobservable)
{
  if (mbox == NULL)
    return MU_ERR_MBX_NULL;
  if (pobservable == NULL)
    return MU_ERR_OUT_PTR_NULL;

  if (mbox->observable == NULL)
    {
      int status = observable_create (&(mbox->observable), mbox);
      if (status != 0)
	return status;
    }
  *pobservable = mbox->observable;
  return 0;
}

int
mailbox_get_property (mailbox_t mbox, property_t *pproperty)
{
  if (mbox == NULL)
    return MU_ERR_MBX_NULL;
  if (pproperty == NULL)
    return MU_ERR_OUT_PTR_NULL;
  
  if (mbox->property == NULL)
    {
      int status = property_create (&(mbox->property), mbox);
      if (status != 0)
	return status;
    }
  *pproperty = mbox->property;
  return 0;
}

int
mailbox_has_debug (mailbox_t mailbox)
{
  if (mailbox == NULL)
    return 0;

  return mailbox->debug ? 1 : 0;
}

int
mailbox_set_debug (mailbox_t mbox, mu_debug_t debug)
{
  if (mbox == NULL)
    return MU_ERR_MBX_NULL;
  if (mbox->debug)
    mu_debug_destroy (&mbox->debug, mbox);
  mbox->debug = debug;
  if(!folder_has_debug(mbox->folder))
    folder_set_debug(mbox->folder, debug);
  return 0;
}

int
mailbox_get_debug (mailbox_t mbox, mu_debug_t *pdebug)
{
  if (mbox == NULL)
    return MU_ERR_MBX_NULL;
  if (pdebug == NULL)
    return MU_ERR_OUT_PTR_NULL;
  if (mbox->debug == NULL)
    {
      int status = mu_debug_create (&(mbox->debug), mbox);
      if (status != 0)
	return status;
    }
  *pdebug = mbox->debug;
  return 0;
}

int
mailbox_get_url (mailbox_t mbox, url_t *purl)
{
  if (mbox == NULL)
    return MU_ERR_MBX_NULL;
  if (purl == NULL)
    return MU_ERR_OUT_PTR_NULL;
  *purl = mbox->url;
  return 0;
}

int
mailbox_get_folder (mailbox_t mbox, folder_t *pfolder)
{
  if (mbox == NULL)
    return MU_ERR_MBX_NULL;
  if (pfolder == NULL)
    return MU_ERR_OUT_PTR_NULL;
  *pfolder = mbox->folder;
  return 0;
}

int
mailbox_set_folder (mailbox_t mbox, folder_t folder)
{
  if (mbox == NULL)
    return MU_ERR_MBX_NULL;
   mbox->folder = folder;
  return 0;
}

int
mailbox_lock (mailbox_t mbox)
{
  locker_t lock = NULL;
  mailbox_get_locker (mbox, &lock);
  return locker_lock (lock);
}

int
mailbox_unlock (mailbox_t mbox)
{
  locker_t lock = NULL;
  mailbox_get_locker (mbox, &lock);
  return locker_unlock (lock);
}