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

#include "utils.hh"

#include <boost/lexical_cast.hpp>

using namespace std;
using namespace pbe;


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.

    // This is important because of the temporary table used below.
    Transaction t(session.get_db());

    // Get all messages pending delete (all users, all mailboxes)
    //   [we could filter on user and mailbox in the DB, but don't]
    ColumnResult<ImapdDb::msg_id_t> r = session.get_db().q_get_deleted_messages();

    // Now extract just those that are in the current mailbox.
    // Record "old" seqnums and msg_ids.
    typedef list<ImapdDb::msg_id_t> msg_ids_to_delete_t;
    msg_ids_to_delete_t msg_ids_to_delete;
    list<int> seqnums_to_delete;
    for (int i=0; i<r.rows; ++i) {
      ImapdDb::msg_id_t msg_id = r(i);
      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;
    }

    // Create and populate a table containing the message IDs to b deleted.
    session.get_db().q_create_todelete();
    for(msg_ids_to_delete_t::const_iterator i=msg_ids_to_delete.begin();
	i!=msg_ids_to_delete.end(); ++i) {
      session.get_db().q_insert_todelete(*i);
    }

    // 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.
    session.get_db().q_delete_todelete();

    // 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(boost::lexical_cast<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;

    t.commit();
  }

};
