// common/DmDatabase.cc
// This file is part of Decimail; see http://decimail.org
// (C) 2004 Philip Endecott
 
// This is version $Name$
//   (if there is no version (e.g. V0-1) mentioned in the previous line,
//    this is probably a snapshot from between "official" releases.)
 
// 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 "utils.hh"
#include "MessageDb.hh"
#include "mimetic_utils.hh"

#include <iostream>
using namespace std;


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

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


DmDatabase::DmDatabase(void):
  Database("dbname=decimail user=decimail")
{
}


string DmDatabase::get_mailbox_query(string username, string mailbox_name)
{
  Query q(this);
  q << "select query, now() from mailboxes where "
    << "username=" << Query::qs(username)
    << " and mailbox_name=" << Query::qs(mailbox_name) << ";";
  q.run();
  if (q.get_ntuples()==0) {
    throw MailboxDoesNotExist(mailbox_name);
  }
  string query = q.get(0,0);
  string now = "\'" + q.get(0,1) + "\'";
  
  unsigned int pos=0;
  while(1) {
    pos = query.find("now()",pos);
    if (pos==query.npos) {
      break;
    }
    query.replace(pos,5,now);
    pos=pos+now.size();
  }

  return query;
}


auto_ptr<list<Message*> > DmDatabase::get_messages(const list<int>& msg_ids)
{
  // Ought to see if this can be reduced to a single query

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

  for(list<int>::const_iterator i = msg_ids.begin();
      i != msg_ids.end(); ++i) {
    l->push_back(new MessageDb(*this,*i));
  }

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


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

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


void DmDatabase::set_imap_mailbox_flag(string username, string mailbox_name,
				       string flag)
{
  Transaction t(this);

  Query q(this);
  q << "insert into imap_mailbox_flags (username, mailbox_name, flag) "
    << "values (" << Query::qs(username) << ", " << Query::qs(mailbox_name)
    << ", " << Query::qs(flag) << ");";
  try {
    q.run();
  }
  catch(QueryFailed& QF) {
    throw FlagExists(flag);
  }
  t.commit();
}


void DmDatabase::clear_imap_mailbox_flag(string username, string mailbox_name,
					 string flag)
{
  Transaction t(this);

  Query q(this);
  q << "delete from imap_mailbox_flags where username=" << Query::qs(username)
    << " and mailbox_name=" << Query::qs(mailbox_name)
    << " and flag=" << Query::qs(flag) << ";";
  q.run();
  if (q.get_ntuples()==0) {
    throw FlagDoesNotExist(flag);
  }
  t.commit();
}


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

  Transaction t(this);

  Query q(this);
  q << "insert into imap_message_flags (username, msg_id, flag) "
    << "values (" << Query::qs(username) << ", " << msg_id
    << ", " << Query::qs(flag) << ");";
  try {
    q.run();
  }
  catch(QueryFailed& QF) {
    // throw FlagExists(flag);
    // succeed silently if setting an already-set flag
  }
  t.commit();
}


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

  Transaction t(this);

  Query q(this);
  q << "delete from imap_message_flags where username=" << Query::qs(username)
    << " and msg_id=" << msg_id
    << " and flag=" << Query::qs(flag) << ";";
  q.run();
  if (q.get_ntuples()==0) {
    // throw FlagDoesNotExist(flag);
    // succeed silently if clearing a non-existant flag
  }
  t.commit();
}


void DmDatabase::clear_all_imap_message_flags(string username, int msg_id)
{
  Transaction t(this);

  Query q1(this);
  q1 << "delete from imap_message_flags where username=" << Query::qs(username)
     << " and msg_id=" << msg_id << ";";
  q1.run();

  Query q2(this);
  q2 << "insert into imap_message_flags (username, msg_id, flag) values ("
     << Query::qs(username) << ", " << msg_id << ",\'\\\\Unseen\');";
  q2.run();

  t.commit();
}


list<string> DmDatabase::get_imap_message_flags(string username, int msg_id)
{
  Query q(this);
  q << "select flag from imap_message_flags where "
    << "username = " << Query::qs(username)
    << " and msg_id = " << msg_id << ";";
  q.run();
  bool seen=true;
  list<string> l;
  for(int i=0; i<q.get_ntuples(); ++i) {
    string flag=q.get(i,0);
    if (flag=="\\Unseen") {
      seen=false;
    } else {
      l.push_back(flag);
    }
  }
  if (seen) {
    l.push_back("\\Seen");
  }
  return l;
}


void DmDatabase::insert_addrs(int 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++) {
    {
      Query q(this);
      q << "insert into " << what_addr << "_addrs (addr, msg_id) values ("
	<< Query::qs(*i) << ", "
	<< msg_id << ");";
      q.run();
    }
  }
}


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

  {
    string f = message.get_from().mailbox() + '@'
      + message.get_from().domain();
    Query q(this);
    q << "insert into messages "
      << "(msg_id, owner, subject, msgdate, from_addr, rfc822_messageid, "
      << " headers, body) "
      << "values ( "
      << message.get_id()                      << ", "
      << Query::qs(owner)                      << ", "
      << Query::qs(message.get_subject())      << ", "
      << Query::qs(message.get_str_date())     << ", "
      << Query::qs(f)                          << ", "
      << Query::qs(message.get_rfc822_messageid()) << ", "
      << Query::qb(message.get_headers())      << ", "
      << Query::qb(message.get_body())         << ");";
    q.run();
  }

  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());

  t.commit();
}


int DmDatabase::get_next_uid(void)
{
  Query q(this);
  q << "select nextval('msg_ids');";
  q.run();
  int msg_id = q.get_num(0,0);
  return msg_id;
}


void DmDatabase::listen_for_new_messages(string username)
{
  Query q(this);
  q << "listen new_message_for_"+username+";";
  q.run();
}


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


bool DmDatabase::check_password(string username, string passwd)
{
  Query q(this);
  q << "select count(*) from users where "
    << "username=" << Query::qs(username)
    << " and passwd=" << Query::qs(passwd) << ";";
  q.run();
  return q.get_num(0,0)==1;
}
