// imapd/ExpungeCmd.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 "ExpungeCmd.hh"

#include "utils.hh"


namespace Imapd {

  void ExpungeCmd::runbody(Session& session)
  {
    session.check_state(Session::selected);

    // Trickyness here is that seqnums change as things are deleted.
    // See example in rfc.
    // Remember that seqnums start at 1.

    // Get all messages pending delete (all users, all mailboxes)
    //   [we could filter on user and mailbox in the DB, but don't]
    DmDatabase::Query q1(&session.get_db());
    q1 << "select msg_id from imap_message_flags where flag='\\\\Deleted'"
       << " order by msg_id";
    q1.run();

    // Now extract just those that are in the current mailbox.
    // Record "old" seqnums and msg_ids.
    list<int> msg_ids_to_delete;
    list<int> seqnums_to_delete;
    for (int i=0; i<q1.get_ntuples(); ++i) {
      int msg_id = q1.get_num(i,0);
      map<int,int>::const_iterator f = session.msgid_to_seqnum.find(msg_id);
      if (f!=session.msgid_to_seqnum.end()) {
	msg_ids_to_delete.push_back(msg_id);
	seqnums_to_delete.push_back(f->second);
      }
    }

    // If there are no messages to expunge, finish now.  If we don't
    // do this we get a syntax error in the next query due to "()".
    if (msg_ids_to_delete.empty()) {
      return;
    }

    // Now run the query to delete these messages.
    // This should either completely-succeed or completely-fail.
    // If it fails, an exception is thrown and no "EXPUNGE" responses are
    // sent anywhere.
    DmDatabase::Query q2(&session.get_db());
    q2 << "delete from messages where msg_id in (";
    for (list<int>::const_iterator i = msg_ids_to_delete.begin();
	 i != msg_ids_to_delete.end(); ++i) {
      if (i!=msg_ids_to_delete.begin()) {
	q2 << ",";
      }
      q2 << *i;
    }
    q2 << ");";
    q2.run();

    // Now build replacement seqnum<->msgid maps.
    // Avoid O(n*m) complexity when only O(n) is needed:
    //   don't delete in-place in a vector, build a copy.
    vector<int> new_seqnum_to_msgid;
    new_seqnum_to_msgid.push_back(0);
    map<int,int> new_msgid_to_seqnum;

    list<int>::const_iterator i = seqnums_to_delete.begin();
    int j = 1;
    int k = 1;
    while (j<static_cast<int>(session.seqnum_to_msgid.size())) {
      if (i!=seqnums_to_delete.end() && j==*i) {
	session.respond_untagged(int_to_string(k)+" EXPUNGE");
	++i;
      } else {
	int msg_id = session.seqnum_to_msgid[j];
	new_seqnum_to_msgid.push_back(msg_id);
	new_msgid_to_seqnum[msg_id]=k;
	++k;
      }
      ++j;
    }

    // Store the new maps in the session.
    session.seqnum_to_msgid = new_seqnum_to_msgid;
    session.msgid_to_seqnum = new_msgid_to_seqnum;
  }

};
