// common/DmDatabase.cc
// This file is part of Decimail; see http://decimail.org
// (C) 2004-2007 Philip Endecott
 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
#include "DmDatabase.hh"

#include "MessageDb.hh"
#include "mimetic_utils.hh"
#include "tspartindex.hh"

#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/trim.hpp>

#include <iostream>

using namespace std;
using namespace pbe;


void DmDatabase::MailboxDoesNotExist::report(ostream& s) const
{
  s << "Mailbox '" << mailbox_name << "' does not exist" << endl;
}

void DmDatabase::MailboxExists::report(ostream& s) const
{
  s << "Mailbox '" << mailbox_name << "' already exists" << endl;
}


/*static*/ void DmDatabase::add_connection_string(std::string connection_string_)
{
  if (connection_string != "") {
    connection_string += " ";
  }
  connection_string += connection_string_;
}


DmDatabase::DmDatabase(void):
  Database(connection_string),

  q_get_headers
    (*this, "select headers from messages_v where msg_id=$1"),
  q_get_body
    (*this, "select body from messages_v where msg_id=$1"),
  q_get_headers_body
    (*this, "select headers, body from messages_v where msg_id=$1"),
  q_get_size
    (*this, "select size from messages_v where msg_id=$1"),

  q_get_configuration
    (*this, "select variable, type, value from configuration"),

  q_get_next_msgid
    (*this, "select nextval('msg_ids')::int4"),
  q_get_mailbox_query
    (*this, "select replace(query,'now()',''''||now()||'''') from mailboxes where username=$1 and mailbox_name=$2"),
  q_insert_mailbox_flag
    (*this, "insert into imap_mailbox_flags (username, mailbox_name, flag) values ($1,$2,$3)"),
  q_delete_mailbox_flag
    (*this, "delete from imap_mailbox_flags where username=$1 and mailbox_name=$2 and flag=$3"),
  q_insert_message_flag
    (*this, "insert into imap_message_flags (username, msg_id, flag) values ($1,$2,$3)"),
  q_delete_message_flag
    (*this, "delete from imap_message_flags where username=$1 and msg_id=$2 and flag=$3"),
  q_delete_message_flags
    (*this, "delete from imap_message_flags where username=$1 and msg_id=$2"),
  q_get_message_flags
    (*this, "select flag from imap_message_flags where username=$1 and msg_id=$2"),
  q_check_password
    (*this, "select count(*)::int4 from users where username=$1 and passwd=$2"),
  q_insert_realname
    (*this, "insert into realnames (realname, email) values ($1,$2)")
{
}


string DmDatabase::get_mailbox_query(username_t username, mailbox_name_t mailbox_name)
{
  OptResult<string> r = q_get_mailbox_query(username, mailbox_name);
  if (r.empty()) {
    throw MailboxDoesNotExist(mailbox_name);
  }
  return r;
}


auto_ptr<list<Message*> > DmDatabase::get_messages(const list<msg_id_t>& msg_ids)
{
  if (msg_ids.empty()) {
    return auto_ptr<list<Message*> >(new list<Message*>);
  }

  string query = "select msg_id, headers from messages where msg_id in (";
  for(list<msg_id_t>::const_iterator i = msg_ids.begin();
      i != msg_ids.end(); ++i) {
    if (i!=msg_ids.begin()) {
      query += ",";
    }
    query += boost::lexical_cast<string>(*i);
  }
  query += ")";

  Query<> q(*this,query);
  Result r = q.runonce();

  list<Message*>* l = new list<Message*>;

  for (int i=0; i<r.rows; ++i) {
    DmDatabase::msg_id_t msg_id = r.get<msg_id_t>(i,0);
    string headers = r.get<string>(i,1);
    l->push_back(new MessageDb(*this,msg_id,headers));
  }

  return auto_ptr<list<Message*> >(l);
}


void DmDatabase::FlagExists::report(ostream& s) const
{
  s << "IMAP flag " << flag << " already exists" << endl;
}

void DmDatabase::FlagDoesNotExist::report(ostream& s) const
{
  s << "IMAP flag " << flag << " does not exist" << endl;
}


void DmDatabase::set_imap_mailbox_flag(username_t username, mailbox_name_t mailbox_name,
				       flag_t flag)
{
  Transaction t(*this);
  try {
    q_insert_mailbox_flag(username,mailbox_name,flag);
  }
  catch(QueryFailed& QF) {
    throw FlagExists(flag);
  }
  t.commit();
}


void DmDatabase::clear_imap_mailbox_flag(username_t username, mailbox_name_t mailbox_name,
					 flag_t flag)
{
  Transaction t(*this);
  Result r = q_delete_mailbox_flag(username,mailbox_name,flag);
  if (r.rows==0) {
    throw FlagDoesNotExist(flag);
  }
  t.commit();
}


void DmDatabase::set_imap_message_flag(username_t username, msg_id_t msg_id,
				       flag_t flag)
{
  if (flag=="\\Seen") {
    clear_imap_message_flag(username, msg_id, "\\Unseen");
    return;
  }

  Transaction t(*this);
  try {
    q_insert_message_flag(username,msg_id,flag);
  }
  catch(QueryFailed& QF) {
    // succeed silently if setting an already-set flag
  }
  t.commit();
}


void DmDatabase::clear_imap_message_flag(username_t username, msg_id_t msg_id,
					 flag_t flag)
{
  if (flag=="\\Seen") {
    set_imap_message_flag(username, msg_id, "\\Unseen");
    return;
  }

  Transaction t(*this);
  q_delete_message_flag(username,msg_id,flag);
  // succeed silently if clearing a non-existant flag
  t.commit();
}


void DmDatabase::clear_all_imap_message_flags(username_t username, msg_id_t msg_id)
{
  Transaction t(*this);
  q_delete_message_flags(username,msg_id);
  q_insert_message_flag(username,msg_id,"\\Unseen");
  t.commit();
}


list<string> DmDatabase::get_imap_message_flags(username_t username, msg_id_t msg_id)
{
  ColumnResult<flag_t> r = q_get_message_flags(username,msg_id);
  bool seen=true;
  list<flag_t> l;
  for(int i=0; i<r.rows; ++i) {
    flag_t flag=r(i);
    if (flag=="\\Unseen") {
      seen=false;
    } else {
      l.push_back(flag);
    }
  }
  if (seen) {
    l.push_back("\\Seen");
  }
  return l;
}


void DmDatabase::insert_addrs(msg_id_t msg_id, string what_addr,
			      const mimetic::AddressList addrs)
{
  list<string> addrs_l;
  addresslist_to_stringlist(addrs,addrs_l);
  for(list<string>::const_iterator i=addrs_l.begin();
      i!=addrs_l.end(); i++) {
    {
// TODO fix this to use prepared queries
      Query<string,int> insert_addr
        (*this,
         "insert into "+what_addr+"_addrs (addr, msg_id) values ($1,$2)");
      insert_addr.runonce(*i, msg_id);
    }
  }

  for (mimetic::AddressList::const_iterator i = addrs.begin();
       i != addrs.end(); ++i) {
    if (i->isGroup()) {
      continue;
    }
    insert_realname(i->mailbox());
  }
}


void DmDatabase::insert(const Message& message, string owner)
{
  Transaction t(*this);

  string f = message.get_from().mailbox() + '@'
    + message.get_from().domain();

  typecode_t argtypecodes[8] = {numeric_type, text_type, text_type, text_type,
                                text_type, text_type, bytea_type, bytea_type};
  int lengths[8] = {sizeof(message.get_id()), 0,0,0,0,0,message.get_headers().length(),message.get_body().length()};
  int formats[8] = {1, 0,0,0,0,0,1,1};

  QueryCore insert_message
    (*this,
     "insert into messages "
     "(msg_id, owner, subject, msgdate, from_addr, rfc822_messageid, headers, body) "
     "values ($1,$2,$3,$4::timestamp with time zone,$5,$6,$7,$8)",
     8, argtypecodes, lengths, formats);

  int msg_id_c = message.get_id();
  const char* values[8] = {encode_pq_arg<int>(msg_id_c), owner.c_str(),
                           message.get_subject().c_str(), message.get_str_date().c_str(),
                           f.c_str(), message.get_rfc822_messageid().c_str(),
                           message.get_headers().data(), message.get_body().data()};
                           
  insert_message.runonce(values);

  insert_realname(message.get_from());

  insert_addrs(message.get_id(),"to",message.get_to());
  insert_addrs(message.get_id(),"cc",message.get_cc());
  insert_addrs(message.get_id(),"bcc",message.get_bcc());

  clear_all_imap_message_flags(owner, message.get_id());

  tspartindex(*this, message);

  t.commit();
}


int DmDatabase::get_next_uid(void)
{
  return q_get_next_msgid();
}


void DmDatabase::listen_for_new_messages(username_t username)
{
  Query<> listen
    (*this, "listen new_message_for_"+username);
  listen.runonce();
}


bool DmDatabase::check_for_new_messages(void)
{
  string n = get_any_notification();
  return n!="";
}


bool DmDatabase::check_password(username_t username, passwd_t passwd)
{
  int match_count = q_check_password(username,passwd);
  return match_count==1;
}


void DmDatabase::insert_realname(const mimetic::Mailbox& mb)
{
  string realname = mb.label();
  boost::algorithm::trim_if(realname,boost::algorithm::is_any_of(" \""));
  if (realname=="") {
    return;
  }
  string email = mb.mailbox() + "@" + mb.domain();

  q_insert_realname(realname,email);
}


/*static*/ string DmDatabase::connection_string;
