/* -*- Mode: c++; -*- */
/*  --------------------------------------------------------------------
 *  Filename:
 *    depot.cc
 *  
 *  Description:
 *    <--->
 *
 *  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.
 *  --------------------------------------------------------------------
 */
#include <map>
#include <string>

#include "depot.h"
#include "mailbox.h"
#include "status.h"
#include "convert.h"
#include "io.h"

using namespace ::std;
using namespace Binc;

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

//--------------------------------------------------------------------
DepotFactory::~DepotFactory(void)
{
  for (vector<Depot *>::iterator i = depots.begin(); i != depots.end();
       ++i)
    delete *i;
}

//--------------------------------------------------------------------
Depot *DepotFactory::get(const string &name) const
{
  for (vector<Depot *>::const_iterator i = depots.begin(); i != depots.end();
       ++i)
    if ((*i)->getName() == name)
      return *i;

  return 0;
}

//--------------------------------------------------------------------
void DepotFactory::assign(Depot *depot)
{
  depots.push_back(depot);
}

//--------------------------------------------------------------------
DepotFactory &DepotFactory::getInstance(void)
{
  static DepotFactory depotfactory;
  return depotfactory;
}

//--------------------------------------------------------------------
Depot::Depot(void) : enditerator(0, 0)
{
  defaultmailbox = 0;
  selectedmailbox = 0;

  delimiter = '/';
}

//--------------------------------------------------------------------
Depot::Depot(const string &name) : enditerator(0, 0)
{
  defaultmailbox = 0;
  selectedmailbox = 0;

  delimiter = '/';

  this->name = name;
}

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

//--------------------------------------------------------------------
const string &Depot::getLastError(void) const
{
  return lastError;
}

//--------------------------------------------------------------------
void Depot::setLastError(const string &error) const
{
  lastError = error;
}

//--------------------------------------------------------------------
void Depot::assign(Mailbox *m)
{
  for (vector<Mailbox *>::const_iterator i = backends.begin();
       i != backends.end(); ++i)
    if (*i == m) break;

  backends.push_back(m);
}

//--------------------------------------------------------------------
Mailbox *Depot::get(const string &s_in) const
{
  for (vector<Mailbox *>::const_iterator i = backends.begin();
       i != backends.end(); ++i)
    if ((*i)->isMailbox(mailboxToFilename(s_in)))
      return *i;

  setLastError("No such mailbox " + toImapString(s_in));
  return 0;
}

//--------------------------------------------------------------------
bool Depot::setSelected(Mailbox *m)
{
  for (vector<Mailbox *>::const_iterator i = backends.begin();
       i != backends.end(); ++i)
    if (*i == m) {
      selectedmailbox = m;
      return true;
    }

  setLastError("Attempted to select unregistered Mailbox type in Depot");
  return false;
}

//--------------------------------------------------------------------
const string &Depot::getName(void) const
{
  return name;
}

//--------------------------------------------------------------------
void Depot::setDelimiter(char c)
{
  delimiter = c;
}

//--------------------------------------------------------------------
const char Depot::getDelimiter(void) const
{
  return delimiter;
}

//--------------------------------------------------------------------
bool Depot::setDefaultType(const string &name)
{
  for (vector<Mailbox *>::const_iterator i = backends.begin();
       i != backends.end(); ++i)
    if ((*i)->getTypeName() == name) {
      defaultmailbox = *i;
      return true;
    }

  setLastError("attempt to default to unregistered Mailbox type " + name);
  return false;
}

//--------------------------------------------------------------------
Mailbox *Depot::getSelected(void) const
{
  return selectedmailbox;
}

//--------------------------------------------------------------------
void Depot::resetSelected(void)
{
  selectedmailbox = 0;
}

//--------------------------------------------------------------------
Mailbox *Depot::getDefault(void) const
{
  return defaultmailbox;
}

//--------------------------------------------------------------------
bool Depot::createMailbox(const string &s_in) const
{
  const string &mailboxname = mailboxToFilename(toCanonMailbox(s_in));
  if (mailboxname == "")
    return false;

  Mailbox *mailbox = getDefault();
  if (mailbox == 0) {
    setLastError("no default mailbox defined");
    return false;
  }

  bool result = mailbox->createMailbox(mailboxname, 0777);
  if (result)
    return true;
  else {
    setLastError(mailbox->getLastError());
    return false;
  }
}

//--------------------------------------------------------------------
bool Depot::deleteMailbox(const string &s_in) const
{
  const string &mailboxname = mailboxToFilename(toCanonMailbox(s_in));
  if (mailboxname == "")
    return false;

  Mailbox *mailbox = get(s_in);
  if (mailbox == 0) {
    setLastError(s_in + ": no such mailbox");
    return false;
  }

  bool result = mailbox->deleteMailbox(mailboxname);
  if (result)
    return true;
  else {
    setLastError(mailbox->getLastError());
    return false;
  }
}

//--------------------------------------------------------------------
bool Depot::renameMailbox(const string &s_in, const string &t_in) const
{
  IO &logger = IOFactory::getInstance().get(2);
    
  const string &source = mailboxToFilename(s_in).c_str();
  const string &dest = mailboxToFilename(t_in).c_str();
  if (source == "" || dest == "")
    return false;

  int nrenamed = 0;
  const iterator e = end();
  for (iterator i = begin("."); i != e; ++i) {
    string entry = *i;

    if (entry.substr(0, source.length()) == source) {
      string sourcename, destname;

      if (entry.length() == source.length()) {
	sourcename = source;
	destname = dest;

      } else if (entry.length() > source.length() 
		 && entry[source.length()] == '.') {
	sourcename = entry;
	destname = dest + entry.substr(source.length());
      } else continue;

      if (rename(sourcename.c_str(), destname.c_str()) != 0) {
	logger << "error renaming " << sourcename << " to " 
	       << destname << ": " << strerror(errno) << endl;
      } else
	nrenamed++;
      
      Mailbox *mailbox;
      if ((mailbox = get(filenameToMailbox(sourcename))) != 0)
	mailbox->bumpUidValidity(filenameToMailbox(sourcename));
      if ((mailbox = get(filenameToMailbox(destname))) != 0)
	mailbox->bumpUidValidity(filenameToMailbox(destname));
    }
  }

  if (nrenamed == 0) {
    setLastError("An error occurred when renaming " 
		 + toImapString(s_in)
		 + " to " + toImapString(t_in)
		 + ". Try creating a new mailbox,"
		 " then copy over all messages."
		 " Finally, delete the original mailbox");
    return false;
  } else
    return true;
}

//--------------------------------------------------------------------
bool Depot::getStatus(const std::string &s_in, Status &dest) const
{
  const string mailbox = toCanonMailbox(s_in);
  if (mailbox == "") {
    setLastError("Unrecognized mailbox: " + toImapString(s_in));
    return false;
  }

  string mailboxFilename = mailboxToFilename(mailbox);
  if (mailboxFilename == "")
    return false;

  Mailbox *m = get(mailbox);
  if (m == 0) {
    setLastError("Unrecognized mailbox: " + toImapString(s_in));
    return false;
  }

  int statusid = m->getStatusID(mailboxFilename);
  if (mailboxstatuses.find(mailbox) != mailboxstatuses.end()) {
    dest = mailboxstatuses[mailbox];
    if (dest.getStatusID() == statusid)
      return true;
  }

  if (!m->getStatus(mailboxFilename, dest)) {
    setLastError(m->getLastError());
    return false;
  }

  dest.setStatusID(statusid);
  mailboxstatuses[mailbox] = dest;
  return true;
}

//--------------------------------------------------------------------
Depot::iterator::iterator(void)
{
  dirp = 0;
  ref = new int;
  *ref = 1;
}

//--------------------------------------------------------------------
Depot::iterator::iterator(DIR *dp, struct dirent *sp)
{
  dirp = dp;
  direntp = sp;

  ref = new int;
  *ref = 1;
}

//--------------------------------------------------------------------
Depot::iterator::iterator(const iterator &copy)
{
  if (*copy.ref != 0)
    ++(*copy.ref);

  ref = copy.ref;
  dirp = copy.dirp;
  direntp = copy.direntp;
}

//--------------------------------------------------------------------
Depot::iterator::~iterator(void)
{
  deref();
}

//--------------------------------------------------------------------
Depot::iterator &Depot::iterator::operator =(const iterator &copy)
{
  if (*copy.ref != 0)
    ++(*copy.ref);

  deref();

  ref = copy.ref;
  dirp = copy.dirp;
  direntp = copy.direntp;

  return *this;
}

//--------------------------------------------------------------------
void Depot::iterator::deref(void)
{
  // decrease existing copy ref if there is one
  if (*ref != 0 && --(*ref) == 0) {
    if (dirp) {
      closedir(dirp);
      dirp = 0;
    }

    delete ref;
    ref = 0;
  }
}

//--------------------------------------------------------------------
string Depot::iterator::operator * (void) const
{
  if (direntp == 0)
    return "";

  return direntp->d_name;
}

//--------------------------------------------------------------------
void Depot::iterator::operator ++ (void)
{
  direntp = readdir(dirp);
}

//--------------------------------------------------------------------
bool Depot::iterator::operator == (Depot::iterator i) const
{
  return direntp == i.direntp;
}

//--------------------------------------------------------------------
bool Depot::iterator::operator != (Depot::iterator i) const
{
  return direntp != i.direntp;
}

//--------------------------------------------------------------------
Depot::iterator Depot::begin(const string &path) const
{
  Depot::iterator i;

  if ((i.dirp = opendir(path.c_str())) == 0) {
    IO &logger = IOFactory::getInstance().get(2);

    logger << "opendir on " + path + " failed" << endl;
    setLastError("opendir on " + path + " failed");
    return end();
  }

  ++i;
  return i;
}

//--------------------------------------------------------------------
const Depot::iterator &Depot::end(void) const
{
  return enditerator;
}

//--------------------------------------------------------------------
MaildirPPDepot::MaildirPPDepot(void) : Depot("Maildir++")
{
}

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

//--------------------------------------------------------------------
string MaildirPPDepot::mailboxToFilename(const string &m) const
{
  string prefix = "INBOX"; prefix += delimiter;

  string mm = m;
  trim(mm, string(&delimiter, 1));
  string tmp = mm;
  uppercase(tmp);
  if (tmp != "INBOX" && tmp.substr(0, 6) != "INBOX/") {
    setLastError("With a Maildir++ depot, you must create all"
		 " mailboxes under INBOX. Try creating"
		 " INBOX/" + mm + ".");
    return "";
  }

  string twodelim;
  twodelim += delimiter;
  twodelim += delimiter;

  if (mm == "INBOX") return ".";
  else if (mm.length() <= 6) {
    setLastError("With a Maildir++ depot, you must create all"
		 " mailboxes under INBOX.");
    return "";
  } else if (mm.substr(0, 6) != prefix) {
    setLastError("With a Maildir++ depot, you must create all"
		 " mailboxes under INBOX.");
    return "";
  } else if (mm.find(twodelim) != string::npos) {
    setLastError("Invalid character combination " 
		 + twodelim + " in mailbox name");
    return "";
  } else if (mm != "" && mm.substr(1).find('.') != string::npos) {
    setLastError("Invalid character '.' in mailbox name");
    return "";
  } else {
    string tmp = mm.substr(6);
    for (string::iterator i = tmp.begin(); i != tmp.end(); ++i)
      if (*i == '/') *i = '.';

    return "." + tmp;
  }
}

//--------------------------------------------------------------------
string MaildirPPDepot::filenameToMailbox(const string &m) const
{
  if (m == "." || m == "..") 
    return "INBOX";
  else if (m.find(delimiter) != string::npos) 
    return "";
  else if (m != "" && m[0] == '.') {
    string tmp = m;
    for (string::iterator i = tmp.begin(); i != tmp.end(); ++i)
      if (*i == '.') *i = delimiter;

    return "INBOX" + tmp;
  } else 
    return "";
}

//--------------------------------------------------------------------
IMAPdirDepot::IMAPdirDepot(void) : Depot("IMAPdir")
{
}

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

//--------------------------------------------------------------------
string IMAPdirDepot::mailboxToFilename(const string &m) const
{
  string tmp;
  string mm = m;
  trim(mm, string(&delimiter, 1));

  string twodelim;
  twodelim += delimiter;
  twodelim += delimiter;

  if (mm.find(twodelim) != string::npos) {
    setLastError("Invalid character combination " 
		 + twodelim + " in mailbox name");
    return "";
  }

  string::const_iterator i = mm.begin();
  while (i != mm.end()) {
    if (*i == delimiter) {
      tmp += '.';
    } else if (*i == '\\') {
      tmp += "\\\\";
    } else if (*i == '.') {
      if (i == mm.begin())
	tmp += ".";
      else
	tmp += "\\.";
    } else {
      tmp += *i;
    }

    ++i;
  }

  return tmp;
}

//--------------------------------------------------------------------
string IMAPdirDepot::filenameToMailbox(const string &m) const
{
  string tmp;
  bool escape = false;

  // hide the magic "." mailbox.
  if (m == "." || m == "..")
    return "";

  string::const_iterator i = m.begin();
  while (i != m.end()) {
    if (*i == '.') {
      if (i != m.begin() && !escape) tmp += delimiter;
      else if (i == m.begin() || escape) tmp += '.';
      escape = false;
    } else if (*i == '\\') {
      if (!escape) escape = true; else {
	tmp += '\\';
	escape = false;
      }
    } else if (*i == delimiter) {
      return "";
    } else {
      if (escape) return "";
      else {
	tmp += *i;
	escape = false;
      }
    }

    ++i;
  }

  return tmp;
}


syntax highlighted by Code2HTML, v. 0.9.1