/* -*- Mode: c++; -*- */
/*  --------------------------------------------------------------------
 *  Filename:
 *    bincimapd-lsub.cc
 *  
 *  Description:
 *    Implementation of the LSUB command.
 *
 *  Authors:
 *    Andreas Aardal Hanssen <andreas-binc curly bincimap spot org>
 *
 *  Bugs:
 *
 *  ChangeLog:
 *
 *  --------------------------------------------------------------------
 *  Copyright 2002-2005 Andreas Aardal Hanssen
 *
 *  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
 *  (at your option) 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 Street #330, Boston, MA 02111-1307, USA.
 *  --------------------------------------------------------------------
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <algorithm>
#include <string>
#include <vector>
#include <iostream>

#include "recursivedescent.h"

#include "io.h"
#include "mailbox.h"
#include "convert.h"
#include "regmatch.h"

#include "session.h"
#include "depot.h"
#include "operators.h"

namespace {
  const int DIR_SELECT      = 0x01;
  const int DIR_MARKED      = 0x02;
  const int DIR_NOINFERIORS = 0x04;
}

using namespace ::std;
using namespace Binc;

//----------------------------------------------------------------------
LsubOperator::LsubOperator(void)
{
}

//----------------------------------------------------------------------
LsubOperator::~LsubOperator(void)
{
}

//----------------------------------------------------------------------
const string LsubOperator::getName(void) const
{
  return "LSUB";
}

//----------------------------------------------------------------------
int LsubOperator::getState(void) const
{
  return Session::AUTHENTICATED | Session::SELECTED;
}

//------------------------------------------------------------------------
Operator::ProcessResult LsubOperator::process(Depot &depot,
					      Request &command)
{
  Session &session = Session::getInstance();
  IO &com = IOFactory::getInstance().get(1);
  const char delim = depot.getDelimiter();

  // remove leading or trailing delimiter in wildcard
  string wildcard = command.getListMailbox();
  trim(wildcard, string(&delim, 1));

  // convert wildcard to regular expression
  string regex = toRegex(wildcard, depot.getDelimiter());
  string wildcardLower = regex;
  lowercase(wildcardLower);
  if (wildcardLower.substr(0, 6) == "^inbox")
    regex = "^[iI][nN][bB][oO][xX]" + regex.substr(6);

  // remove leading or trailing delimiter in reference
  string ref = command.getMailbox();
  trim(ref, string(&delim, 1));
  wildcardLower = ref;
  lowercase(wildcardLower);
  if (wildcardLower.substr(0, 5) == "inbox"
      && (wildcardLower.length() == 5 || wildcardLower[5] == delim))
    ref = "INBOX" + ref.substr(5);

  // a multimap from mailbox name to flags
  multimap<string, int> mailboxes;

  // read through all entries in depository.
  for (Depot::iterator i = depot.begin("."); i != depot.end(); ++i) {
    const string path = *i;
    if (path == "")
      continue;

    const string mpath = depot.filenameToMailbox(path);
    Mailbox *m = 0;

    // skip entries that are not identified as mailboxes
    if ((m = depot.get(mpath)) == 0)
      continue;

    // convert file name to mailbox name. skip it if there is no
    // corresponding mailbox name.
    string tmp = toCanonMailbox(depot.filenameToMailbox(path));
    trim(tmp, string(&delim, 1));
    if (tmp == "")
      continue;
    else {
      int flags = DIR_SELECT;
      multimap<string, int>::iterator mi = mailboxes.find(tmp);
      if (mi != mailboxes.end()) {
	flags |= mi->second;
	mailboxes.erase(mi);
      }

      mailboxes.insert(make_pair(tmp, flags));
    }

    // now add all superior mailboxes with no flags set if not
    // added already.
    string::size_type pos = tmp.rfind(delim);
    while (pos != string::npos) {
      tmp = tmp.substr(0, pos);
      trim(tmp, string(&delim, 1));

      multimap<string, int>::iterator mi = mailboxes.find(tmp);
      if (mi == mailboxes.end())
	mailboxes.insert(make_pair(tmp, 0));

      pos = tmp.rfind(delim);
    }
  }

  // find leaf nodes O(N^2)
  map<string, int>::iterator i;
  for (i = mailboxes.begin(); i != mailboxes.end(); ++i) {
    string mailbox = i->first;
    mailbox += delim;

    bool leaf = true;
    map<string, int>::const_iterator j;
    for (j = mailboxes.begin(); j != mailboxes.end(); ++j) {
      string::size_type pos = j->first.rfind(delim);
      if (pos == string::npos) continue;

      string base = j->first.substr(0, pos + 1);

      if (mailbox == base) {
	leaf = false;
	break;
      }
    }
  }

  session.loadSubscribes();

  vector<string> &subscribed = session.subscribed;
  sort(subscribed.begin(), subscribed.end());

  // finally, print all mailbox entries with flags.  
  for (vector<string>::const_iterator j = subscribed.begin();
       j != subscribed.end(); ++j) {
    if (ref == "" || (ref.length() <= (*j).length() && ref == (*j).substr(0, ref.length())))
      if (regexMatch((*j).substr(ref.length()), regex) == 0) {

	int flags = 0;
    
	for (i = mailboxes.begin(); i != mailboxes.end(); ++i) {
	  if (i->first == *j) {
	    flags = i->second;
	    break;
	  }
	}

	com << "* LSUB (";
	string sep = "";

	bool noselect = false;
	if (!(flags & DIR_SELECT)) {
	  com << sep << "\\Noselect";
	  sep = " ";
	  noselect = true;
	}
	
	if (!noselect) {
	  if (flags & DIR_MARKED)
	    com << sep << "\\Marked";
	  else
	    com << sep << "\\Unmarked";
	  sep = " ";
	}

	if (flags & DIR_NOINFERIORS)
	  com << sep << "\\Noinferiors";
    
	com << ") \"" << depot.getDelimiter() << "\" "
	    << toImapString(*j) << endl;
      }
  }
    
  return OK;
}

//----------------------------------------------------------------------
Operator::ParseResult LsubOperator::parse(Request &c_in) const
{
  Session &session = Session::getInstance();

  if (c_in.getUidMode())
    return REJECT;

  Operator::ParseResult res;
  if ((res = expectSPACE()) != ACCEPT) {
    session.setLastError("Expected SPACE after LSUB");
    return ERROR;
  }

  string mailbox;
  if ((res = expectMailbox(mailbox)) != ACCEPT) {
    session.setLastError("Expected mailbox after LSUB SPACE");
    return ERROR;
  }

  c_in.setMailbox(mailbox);

  if ((res = expectSPACE()) != ACCEPT) {
    session.setLastError("Expected SPACE after LSUB SPACE mailbox");
    return ERROR;
  }

  string listmailbox;
  if ((res = expectListMailbox(listmailbox)) != ACCEPT) {
    session.setLastError("Expected list_mailbox after LSUB SPACE"
			 " mailbox SPACE");
    return ERROR;
  }

  if ((res = expectCRLF()) != ACCEPT) {
    session.setLastError("Expected CRLF after LSUB SPACE"
			 " mailbox SPACE list_mailbox");
    return ERROR;
  }

  c_in.setListMailbox(listmailbox);
  c_in.setName("LSUB");
  return ACCEPT;
}


syntax highlighted by Code2HTML, v. 0.9.1