/*
 * libEtPan! -- a mail stuff library
 *
 * Copyright (C) 2001, 2002 - DINH Viet Hoa
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the libEtPan! project nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * $Id$
 */

#include "imapdriver_cached_message.h"

#include "imapdriver_tools.h"
#include "imapdriver_message.h"
#include "imapdriver_cached.h"
#include "imapdriver_types.h"
#include "imapdriver.h"
#include "mailmessage.h"
#include "generic_cache.h"
#include "mail_cache_db.h"

#include <string.h>
#include <stdlib.h>

static int imap_initialize(mailmessage * msg_info);

static void imap_uninitialize(mailmessage * msg_info);

static void imap_flush(mailmessage * msg_info);

static void imap_check(mailmessage * msg_info);

static void imap_fetch_result_free(mailmessage * msg_info,
				   char * msg);

static int imap_fetch(mailmessage * msg_info,
		      char ** result,
		      size_t * result_len);

static int imap_fetch_header(mailmessage * msg_info,
			     char ** result,
			     size_t * result_len);

static int imap_fetch_body(mailmessage * msg_info,
			   char ** result, size_t * result_len);

static int imap_fetch_size(mailmessage * msg_info,
			   size_t * result);

static int imap_get_bodystructure(mailmessage * msg_info,
				  struct mailmime ** result);

static int imap_fetch_section(mailmessage * msg_info,
			      struct mailmime * mime,
			      char ** result, size_t * result_len);

static int imap_fetch_section_header(mailmessage * msg_info,
				     struct mailmime * mime,
				     char ** result,
				     size_t * result_len);

static int imap_fetch_section_mime(mailmessage * msg_info,
				   struct mailmime * mime,
				   char ** result,
				   size_t * result_len);

static int imap_fetch_section_body(mailmessage * msg_info,
				   struct mailmime * mime,
				   char ** result,
				   size_t * result_len);


static int imap_fetch_envelope(mailmessage * msg_info,
			       struct mailimf_fields ** result);

static int imap_get_flags(mailmessage * msg_info,
			  struct mail_flags ** result);

static mailmessage_driver local_imap_cached_message_driver = {
  .msg_name = "imap-cached",

  .msg_initialize = imap_initialize,
  .msg_uninitialize = imap_uninitialize,

  .msg_flush = imap_flush,
  .msg_check = imap_check,

  .msg_fetch_result_free = imap_fetch_result_free,

  .msg_fetch = imap_fetch,
  .msg_fetch_header = imap_fetch_header,
  .msg_fetch_body = imap_fetch_body,
  .msg_fetch_size = imap_fetch_size,
  .msg_get_bodystructure = imap_get_bodystructure,
  .msg_fetch_section = imap_fetch_section,
  .msg_fetch_section_header = imap_fetch_section_header,
  .msg_fetch_section_mime = imap_fetch_section_mime,
  .msg_fetch_section_body = imap_fetch_section_body,
  .msg_fetch_envelope = imap_fetch_envelope,

  .msg_get_flags = imap_get_flags,
};

mailmessage_driver * imap_cached_message_driver =
&local_imap_cached_message_driver;

static inline struct imap_cached_session_state_data *
get_cached_session_data(mailmessage * msg)
{
  return msg->msg_session->sess_data;
}

static inline mailmessage * get_ancestor(mailmessage * msg_info)
{
  return msg_info->msg_data;
}

static inline struct imap_cached_session_state_data *
cached_session_get_data(mailsession * s)
{
  return s->sess_data;
}

static inline mailsession * cached_session_get_ancestor(mailsession * s)
{
  return cached_session_get_data(s)->imap_ancestor;
}

static inline struct imap_session_state_data *
cached_session_get_ancestor_data(mailsession * s)
{
  return cached_session_get_ancestor(s)->sess_data;
}

static inline mailimap *
cached_session_get_imap_session(mailsession * session)
{
  return cached_session_get_ancestor_data(session)->imap_session;
}

static inline mailimap * get_imap_session(mailmessage * msg)
{
  return cached_session_get_imap_session(msg->msg_session);
}

static inline mailsession * get_ancestor_session(mailmessage * msg_info)
{
  return cached_session_get_ancestor(msg_info->msg_session);
}


static void generate_key_from_mime_section(char * key, size_t size,
					   struct mailmime * mime)
{
  clistiter * cur;
  MMAPString * gstr;
  struct mailmime_section * part;
  int r;

  snprintf(key, size, "unvalid");

  r = mailmime_get_section_id(mime, &part);
  if (r != MAILIMF_NO_ERROR)
    goto err;

  gstr = mmap_string_new("part");
  if (gstr == NULL)
    goto free_section;

  for(cur = clist_begin(part->sec_list) ;
      cur != NULL ; cur = clist_next(cur)) {
    char s[20];

    snprintf(s, 20, ".%u", * (uint32_t *) clist_content(cur));
    if (mmap_string_append(gstr, s) == NULL)
      goto free_str;
  }

  snprintf(key, size, "%s", gstr->str);

  mmap_string_free(gstr);
  mailmime_section_free(part);

  return;

 free_str:
  mmap_string_free(gstr);
 free_section:
  mailmime_section_free(part);
 err:;
}

static void generate_key_from_section(char * key, size_t size,
				      mailmessage * msg_info,
				      struct mailmime * mime, int type)
{
  char section_str[PATH_MAX];

  generate_key_from_mime_section(section_str, PATH_MAX, mime);

  switch (type) {
  case IMAP_SECTION_MESSAGE:
    snprintf(key, size, "%s-%s", msg_info->msg_uid, section_str);
    break;
  case IMAP_SECTION_HEADER:
    snprintf(key, size, "%s-%s-header", msg_info->msg_uid, section_str);
    break;
  case IMAP_SECTION_MIME:
    snprintf(key, size, "%s-%s-mime", msg_info->msg_uid, section_str);
    break;
  case IMAP_SECTION_BODY:
    snprintf(key, size, "%s-%s-text", msg_info->msg_uid, section_str);
    break;
  }
}

static void generate_key_from_message(char * key, size_t size,
				      mailmessage * msg_info,
				      int type)
{
  switch (type) {
  case MAILIMAP_MSG_ATT_RFC822:
    snprintf(key, size, "%s-rfc822", msg_info->msg_uid);
    break;
  case MAILIMAP_MSG_ATT_RFC822_HEADER:
    snprintf(key, size, "%s-rfc822-header", msg_info->msg_uid);
    break;
  case MAILIMAP_MSG_ATT_RFC822_TEXT:
    snprintf(key, size, "%s-rfc822-text", msg_info->msg_uid);
    break;
  case MAILIMAP_MSG_ATT_ENVELOPE:
    snprintf(key, size, "%s-envelope", msg_info->msg_uid);
    break;
  }
}

static void build_cache_name(char * filename, size_t size,
			     mailmessage * msg, char * key)
{
  char * quoted_mb;

  quoted_mb = get_cached_session_data(msg)->imap_quoted_mb;

  snprintf(filename, size, "%s/%s", quoted_mb, key);
}

static int imap_initialize(mailmessage * msg_info)
{
  mailmessage * ancestor;
  int r;
  char key[PATH_MAX];
  char * uid;
  mailimap * imap;

  ancestor = mailmessage_new();
  if (ancestor == NULL)
    return MAIL_ERROR_MEMORY;

  r = mailmessage_init(ancestor, get_ancestor_session(msg_info),
		       imap_message_driver,
		       msg_info->msg_index, 0);
  if (r != MAIL_NO_ERROR) {
    mailmessage_free(ancestor);
    return r;
  }

  imap = get_imap_session(msg_info);

  snprintf(key, PATH_MAX, "%u-%u",
	   imap->imap_selection_info->sel_uidvalidity, msg_info->msg_index);
  uid = strdup(key);
  if (uid == NULL) {
    mailmessage_free(ancestor);
    return MAIL_ERROR_MEMORY;
  }
  
  msg_info->msg_data = ancestor;
  msg_info->msg_uid = uid;

  return MAIL_NO_ERROR;
}
  
static void imap_uninitialize(mailmessage * msg_info)
{
  mailmessage_free(get_ancestor(msg_info));
  msg_info->msg_data = NULL;
}

static void imap_flush(mailmessage * msg_info)
{
  if (msg_info->msg_mime != NULL) {
    mailmime_free(msg_info->msg_mime);
    msg_info->msg_mime = NULL;
  }
}

static void imap_check(mailmessage * msg_info)
{
  get_ancestor(msg_info)->msg_flags = msg_info->msg_flags;
  mailmessage_check(get_ancestor(msg_info));
  get_ancestor(msg_info)->msg_flags = NULL;
}

static void imap_fetch_result_free(mailmessage * msg_info,
				   char * msg)
{
  mailmessage_fetch_result_free(get_ancestor(msg_info), msg);
}

static int imap_fetch(mailmessage * msg_info,
		      char ** result,
		      size_t * result_len)
{
  char key[PATH_MAX];
  char filename[PATH_MAX];
  int r;
  char * str;
  size_t len;

  generate_key_from_message(key, PATH_MAX,
			    msg_info, MAILIMAP_MSG_ATT_RFC822);

  build_cache_name(filename, PATH_MAX, msg_info, key);

  r = generic_cache_read(filename, &str, &len);
  if (r == MAIL_NO_ERROR) {
    * result = str;
    * result_len = len;

    return MAIL_NO_ERROR;
  }

  r = mailmessage_fetch(get_ancestor(msg_info),
			result, result_len);
  if (r == MAIL_NO_ERROR)
    generic_cache_store(filename, * result, strlen(* result));

  return r;
}
       
static int imap_fetch_header(mailmessage * msg_info,
			     char ** result,
			     size_t * result_len)
{
  char key[PATH_MAX];
  char filename[PATH_MAX];
  int r;
  char * str;
  size_t len;

  generate_key_from_message(key, PATH_MAX,
			    msg_info, MAILIMAP_MSG_ATT_RFC822_HEADER);

  build_cache_name(filename, PATH_MAX, msg_info, key);

  r = generic_cache_read(filename, &str, &len);
  if (r == MAIL_NO_ERROR) {
    * result = str;
    * result_len = len;

    return MAIL_NO_ERROR;
  }

  r = mailmessage_fetch_header(get_ancestor(msg_info), result,
			       result_len);
  if (r == MAIL_NO_ERROR)
    generic_cache_store(filename, * result, * result_len);

  return r;
}
  
static int imap_fetch_body(mailmessage * msg_info,
			   char ** result, size_t * result_len)
{
  char key[PATH_MAX];
  char filename[PATH_MAX];
  int r;
  char * str;
  size_t len;

  generate_key_from_message(key, PATH_MAX,
			    msg_info, MAILIMAP_MSG_ATT_RFC822_TEXT);

  build_cache_name(filename, PATH_MAX, msg_info, key);

  r = generic_cache_read(filename, &str, &len);
  if (r == MAIL_NO_ERROR) {
    * result = str;
    * result_len = len;
    return MAIL_NO_ERROR;
  }

  r = mailmessage_fetch_body(get_ancestor(msg_info), result,
			     result_len);
  if (r == MAIL_NO_ERROR)
    generic_cache_store(filename, * result, * result_len);

  return r;
}

static int imap_fetch_size(mailmessage * msg_info,
			   size_t * result)
{
  return mailmessage_fetch_size(get_ancestor(msg_info), result);
}

static int imap_get_bodystructure(mailmessage * msg_info,
				  struct mailmime ** result)
{
  int r;
  
  if (msg_info->msg_mime != NULL) {
    * result = msg_info->msg_mime;

    return MAIL_NO_ERROR;
  }
  
  r = mailmessage_get_bodystructure(get_ancestor(msg_info),
      result);
  if (r == MAIL_NO_ERROR) {
    msg_info->msg_mime = get_ancestor(msg_info)->msg_mime;
    get_ancestor(msg_info)->msg_mime = NULL;
  }
  
  return r;
}

static int imap_fetch_section(mailmessage * msg_info,
			      struct mailmime * mime,
			      char ** result, size_t * result_len)
{
  char key[PATH_MAX];
  char filename[PATH_MAX];
  int r;
  char * str;
  size_t len;

  generate_key_from_section(key, PATH_MAX,
			    msg_info, mime, IMAP_SECTION_MESSAGE);

  build_cache_name(filename, PATH_MAX, msg_info, key);

  r = generic_cache_read(filename, &str, &len);
  if (r == MAIL_NO_ERROR) {
    * result = str;
    * result_len = len;

    return MAIL_NO_ERROR;
  }

  r = mailmessage_fetch_section(get_ancestor(msg_info),
				mime, result, result_len);
  if (r == MAIL_NO_ERROR)
    generic_cache_store(filename, * result, * result_len);

  return r;
}
  
static int imap_fetch_section_header(mailmessage * msg_info,
				     struct mailmime * mime,
				     char ** result,
				     size_t * result_len)
{
  char key[PATH_MAX];
  char filename[PATH_MAX];
  int r;
  char * str;
  size_t len;

  generate_key_from_section(key, PATH_MAX,
			    msg_info, mime, IMAP_SECTION_HEADER);

  build_cache_name(filename, PATH_MAX, msg_info, key);

  r = generic_cache_read(filename, &str, &len);
  if (r == MAIL_NO_ERROR) {
    * result = str;
    * result_len = len;

    return MAIL_NO_ERROR;
  }

  r = mailmessage_fetch_section_header(get_ancestor(msg_info),
				       mime, result, result_len);
  if (r == MAIL_NO_ERROR)
    generic_cache_store(filename, * result, * result_len);

  return r;
}
  
static int imap_fetch_section_mime(mailmessage * msg_info,
				   struct mailmime * mime,
				   char ** result,
				   size_t * result_len)
{
  char key[PATH_MAX];
  char filename[PATH_MAX];
  int r;
  char * str;
  size_t len;

  generate_key_from_section(key, PATH_MAX,
			    msg_info, mime, IMAP_SECTION_MIME);

  build_cache_name(filename, PATH_MAX, msg_info, key);

  r = generic_cache_read(filename, &str, &len);
  if (r == MAIL_NO_ERROR) {
    * result = str;
    * result_len = len;

    return MAIL_NO_ERROR;
  }

  r = mailmessage_fetch_section_mime(get_ancestor(msg_info),
				     mime, result, result_len);
  if (r == MAIL_NO_ERROR)
    generic_cache_store(filename, * result, * result_len);

  return r;
}
  
static int imap_fetch_section_body(mailmessage * msg_info,
				   struct mailmime * mime,
				   char ** result,
				   size_t * result_len)
{
  char key[PATH_MAX];
  char filename[PATH_MAX];
  int r;
  char * str;
  size_t len;
  
  generate_key_from_section(key, PATH_MAX,
			    msg_info, mime, IMAP_SECTION_BODY);

  build_cache_name(filename, PATH_MAX, msg_info, key);

  r = generic_cache_read(filename, &str, &len);
  if (r == MAIL_NO_ERROR) {

    * result = str;
    * result_len = len;

    return MAIL_NO_ERROR;
  }

  r = mailmessage_fetch_section_body(get_ancestor(msg_info),
				     mime, result, result_len);
  if (r == MAIL_NO_ERROR)
    generic_cache_store(filename, * result, * result_len);

  return r;
}

static int imap_get_flags(mailmessage * msg_info,
			  struct mail_flags ** result)
{
  int r;
  struct mail_flags * flags;
  
  if (msg_info->msg_flags != NULL) {
    * result = msg_info->msg_flags;
    return MAIL_NO_ERROR;
  }
  
  r = mailmessage_get_flags(get_ancestor(msg_info), &flags);
  if (r != MAIL_NO_ERROR)
    return r;

  get_ancestor(msg_info)->msg_flags = NULL;
  msg_info->msg_flags = flags;
  * result = flags;
  
  return MAIL_NO_ERROR;
}

#define ENV_NAME "env.db"

static int imap_fetch_envelope(mailmessage * msg_info,
			       struct mailimf_fields ** result)
{
  struct mailimf_fields * fields;
  int r;
  struct mail_cache_db * cache_db;
  MMAPString * mmapstr;
  char filename[PATH_MAX];
  struct imap_cached_session_state_data * data;
  int res;
  
  data = get_cached_session_data(msg_info);
  if (data->imap_quoted_mb == NULL) {
    res = MAIL_ERROR_BAD_STATE;
    goto err;
  }

  snprintf(filename, PATH_MAX, "%s/%s", data->imap_quoted_mb, ENV_NAME);

  r = mail_cache_db_open_lock(filename, &cache_db);
  if (r < 0) {
    res = MAIL_ERROR_MEMORY;
    goto err;
  }

  mmapstr = mmap_string_new("");
  if (mmapstr == NULL) {
    res = MAIL_ERROR_MEMORY;
    goto close_db;
  }

  r = imapdriver_get_cached_envelope(cache_db, mmapstr,
      msg_info->msg_session, msg_info, &fields);

  if ((r != MAIL_ERROR_CACHE_MISS) && (r != MAIL_NO_ERROR)) {
    res = r;
    goto close_db;
  }
  
  r = mailmessage_fetch_envelope(get_ancestor(msg_info), &fields);
  if (r != MAIL_NO_ERROR) {
    res = r;
    goto close_db;
  }

  r = imapdriver_write_cached_envelope(cache_db, mmapstr,
      msg_info->msg_session, msg_info, fields);

  * result = fields;

  mmap_string_free(mmapstr);
  mail_cache_db_close_unlock(filename, cache_db);

  return MAIL_NO_ERROR;

 close_db:
  mail_cache_db_close_unlock(filename, cache_db);
 err:
  return res;
}