// imapd/FetchCmd.cc
// This file is part of Decimail; see http://decimail.org
// (C) 2004 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 "FetchCmd.hh"

#include "utils.hh"

namespace Imapd {

  static string qsn(string s)
  {
    if (s=="") {
      return "NIL";
    } else {
      return '\"' + escape_for_quoted_string(s) + '\"';
    }
  }

  static string fmt_addr(mimetic::Mailbox addr)
  {
    return "(" + qsn(addr.label())
      + " " + qsn(addr.sourceroute())
      + " " + qsn(addr.mailbox())
      + " " + qsn(addr.domain()) + ")";
  }

  static string fmt_addrlist(mimetic::AddressList addrs)
  {
    list<string> strs;
    for(mimetic::AddressList::const_iterator i = addrs.begin();
	i != addrs.end(); ++i) {
      if (i->isGroup()) {
	for(mimetic::Group::const_iterator j = i->group().begin();
	    j != i->group().end(); ++j) {
	  strs.push_back(fmt_addr(*j));
	}
      } else {
	strs.push_back(fmt_addr(i->mailbox()));
      }
    }
    if (strs.empty()) {
      return "NIL";
    } else {
      return '(' + join(strs,"") + ')';
    }
  }

  
  void FetchEnvelope::extract(Session& session, const Message& msg, ostream& s)
  {
    const mimetic::Header& h = msg.get_mime_header();
    const mimetic::Mailbox& sender =
      h.hasField("Sender") ? h.sender() : msg.get_from();
    const mimetic::AddressList& replyto =
      h.hasField("Reply-to") ? h.replyto() : mimetic::AddressList(msg.get_from().str());

    s << "(\"" << msg.get_str_date() << "\""
      << " \"" << escape_for_quoted_string(msg.get_subject()) << "\""
      << " (" << fmt_addr(msg.get_from()) << ")"
      << " (" << fmt_addr(sender) << ")"
      << " " << fmt_addrlist(replyto)
      << " " << fmt_addrlist(msg.get_to())
      << " " << fmt_addrlist(msg.get_cc())
      << " " << fmt_addrlist(msg.get_bcc())
      << " \"\""                             // in-reply-to
      << " \"" << msg.get_rfc822_messageid() << "\""
      << ")";
  }


  void FetchFlags::extract(Session& session, const Message& msg, ostream& s)
  {
    s << "("
      << join(session.get_db().
	      get_imap_message_flags(session.get_username(),
				     msg.get_id()),
	      " ")
      << ")";
  }

  void FetchInternaldate::extract(Session& session, const Message& msg,
				  ostream& s)
  {
    // required format: " 1-Jan-2004 20:19:59 +0100"
    // NB %b uses current locale; IMAP requires English.
    // Should be OK if we have ignored locale, as it defaults to "C".
    struct tm tm;
    time_t t = msg.get_date();
    localtime_r(&t,&tm);
    const int sz=27;
    char buf[sz];
    strftime(buf, sz, "%e-%b-%Y %H:%M:%S %z", &tm);
    s << '"' << buf << '"';
  }


  static string mk_literal(string s)
  {
    ostringstream lit;
    lit << "{" << s.size() << "}\r\n" << s;
    return lit.str();
  }
  
  void FetchBodySection::Section::extract(Session& session, const Message& msg,
					  ostream& s, const Range& r) const
  {
    extract(session, msg.get_mimeentity(),s,r);
  }

  void FetchBodySection::SectionHeader::extract(Session& session,
						const Message& msg, ostream& s,
						const Range& r) const
  {
    s << mk_literal(r.truncate(msg.get_headers()));
  }

  void FetchBodySection::SectionHeader::extract(Session& session,
						const mimetic::MimeEntity& me,
						ostream& s, const Range& r) const
  {
    extract(session, me.header(), s,  r);
  }

  void FetchBodySection::SectionHeader::extract(Session& session,
						const mimetic::Header& h,
						ostream& s, const Range& r) const
  {
    ostringstream os;
    for(mimetic::Header::const_iterator i=h.begin();
	i!=h.end(); ++i) {
      os << *i;
      // could be clever and terminate early if truncating (not worth the effort)
    }
    s << mk_literal(r.truncate(os.str()));
  }

  string FetchBodySection::SectionHeaderFields::get_name(void) const
  {
    string s = "HEADER.FIELDS";
    if (inverse) {
      s+=".NOT";
    }
    s += " (";
    for(set<ci_string>::const_iterator i=headers.begin();
	i!=headers.end();) {
      s += i->c_str();
      ++i;
      if (i!=headers.end()) {
	s += ' ';
      }
    }
    s += ")";
    return s;
  }

  void 
  FetchBodySection::SectionHeaderFields::extract(Session& session,
						 const Message& msg,
						 ostream& s, const Range& r) const
  {
    extract(session, msg.get_mime_header(), s,  r);
  }

  void 
  FetchBodySection::SectionHeaderFields::extract(Session& session,
						 const mimetic::MimeEntity& me,
						 ostream& s, const Range& r) const
  {
    extract(session, me.header(), s,  r);
  }

  void 
  FetchBodySection::SectionHeaderFields::extract(Session& session,
						 const mimetic::Header& h,
						 ostream& s, const Range& r) const
  {
    string t;
    
    for(mimetic::Rfc822Header::const_iterator i=h.begin();
	i!=h.end(); ++i) {
      const mimetic::Field& f = *i;
      if (inverse ^ (headers.find(f.name().c_str())!=headers.end())) {
	t += f.name() + ": " + f.value() + "\r\n";
      }
    }
    t += "\r\n";
    s << mk_literal(r.truncate(t));
  }

  void 
  FetchBodySection::SectionText::extract(Session& session,
					 const Message& msg, ostream& s,
					 const Range& r) const
  {
    s << mk_literal(r.truncate(msg.get_body()));
  }

  void
  FetchBodySection::SectionText::extract(Session& session,
					 const mimetic::MimeEntity& me,
					 ostream& s, const Range& r) const
  {
    s << mk_literal(r.truncate(me.body()));
  }

  void
  FetchBodySection::SectionMime::extract(Session& session,
					 const mimetic::MimeEntity& me,
					 ostream& s, const Range& r) const
  {
    ostringstream os;
    const mimetic::Header h = me.header();
    for(mimetic::Header::const_iterator i=h.begin();
	i!=h.end(); ++i) {
      os << *i;
      // could be clever and terminate early if truncating (not worth the effort)
    }
    s << mk_literal(r.truncate(os.str()));
  }

  string FetchBodySection::SectionNumbered::get_name(void) const
  {
    string s = int_to_string(num);
    if (subsection) {
      s += '.' + subsection->get_name();
    }
    return s;
  }

  void FetchBodySection::SectionNumbered::extract(Session& session,
						  const mimetic::MimeEntity& me,
						  ostream& s, const Range& r) const
  {
    if (me.body().parts().empty()) {
      if (num==1) {
	if (subsection) {
	  throw RespondNo("No hierarchy under single-part");
	} else {
	  s << mk_literal(r.truncate(me.body()));
	  return;
	}
      } else {
	throw RespondNo("Not enough parts in message (single-part)");
      }
    }

    mimetic::MimeEntityList::const_iterator i = me.body().parts().begin();
    for (int j=1; j<num; ++j) {
      ++i;
      if (i==me.body().parts().end()) {
	throw RespondNo("Not enough parts in message");
      }
    }
    const mimetic::MimeEntity& subsec = **i;
    if (subsection) {
      subsection->extract(session, subsec,s,r);
    } else {
      ostringstream ss;
      ss << subsec.body();
      s << mk_literal(r.truncate(ss.str()));
    }
  }

  string FetchBodySection::LimitedRange::get_name(void) const
  {
    string s = "<";
    s += int_to_string(start);
    s += ">";
    return s;
  }

  void FetchBodySection::extract(Session& session, const Message& msg, ostream& s)
  {
    if(section) {
      section->extract(session, msg,s,*range);
    } else {
      s << mk_literal(range->truncate(msg.get_entire_message()));
    }

    if (!peek) {
      session.get_db().set_imap_message_flag(session.get_username(),
					     msg.get_id(),"\\Seen");
      // following can be removed if it happens automatically after a trigger
      s << " FLAGS ("
	<< join(session.get_db().get_imap_message_flags(session.get_username(),
							msg.get_id())," ")
	<< ")";
    }

  }


  FetchBodySection::FetchBodySection(bool p, Section* s, Range* r):
    FetchAtt((s?("BODY["+s->get_name()+"]"):"BODY[]")+r->get_name()),
      peek(p), section(s), range(r)
  {}


  FetchBodySection::~FetchBodySection()
  {
    if(section) {
      delete section;
    }
    delete range;
  }


  void FetchRfc822Size::extract(Session& session, const Message& msg, ostream& s)
  {
    s << msg.get_size();
  }


  static void mimeentity_to_bodystructure(ostream& s,
					  const mimetic::MimeEntity& me)
  {
    s << '(';
    const mimetic::Header& h = me.header();
    const mimetic::ContentType& ct = h.contentType();
    const mimetic::Body& b = me.body();
    const mimetic::MimeEntityList& p = b.parts();
    if (p.empty()) {
      s << '\"' << ct.type() << "\" \"" << ct.subtype() << "\" ";
      list<string> params;
      for(mimetic::ContentType::ParamList::const_iterator i=ct.paramList().begin();
	  i!=ct.paramList().end(); ++i) {
	params.push_back("\""+i->name()+"\"");
	params.push_back("\""+i->value()+"\"");
      }
      if (params.empty()) {
        s << "NIL";
      } else {
        s << "(" << join(params," ") << ")";
      }
      s << " \""
        << h.contentId() << "\" \""
        << h.contentDescription() << "\" \""
        << h.contentTransferEncoding() << "\" "
        << me.size();
      if (ci_string(ct.type().c_str())=="message"
	  && ci_string(ct.subtype().c_str())=="rfc822") {
	// What does mimetic do with a message/rfc822 part?
	// It is probably considered an additional level of hierarchy, so
	// this code is not reached.  Needs investigation.

	// s << " " << envelope_structure;  (whatever that means)
	// mimeentity_to_bodystructure(s, the-message? );
	// s << " " << bodylines();  (as below)
      }
      if (ci_string(ct.type().c_str())=="text") {
	int bodylines = count_if(b.begin(), b.end(),
				 bind2nd(equal_to<char>(),'\n'));
	s << " " << bodylines;
      }
      // possible extension data here: MD5, disposition, language
      // (Only for BODYSTRUCTURE, not for BODY)
    } else {
      for(mimetic::MimeEntityList::const_iterator i=p.begin();
	  i!=p.end(); ++i) {
	mimeentity_to_bodystructure(s,**i);
      }
      s << " \"" << ct.subtype() << '\"';
      // possible extension data here: parameter list, disposition, language
      // (Only for BODYSTRUCTURE, not for BODY)
    }
    s << ')';
  }

  void FetchBodyMaybeStructure::extract(Session& session, const Message& msg,
					ostream& s)
  {
    mimeentity_to_bodystructure(s,msg.get_mimeentity());
  }


  void FetchUid::extract(Session& session, const Message& msg, ostream& s)
  {
    s << msg.get_id();
  }


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

    list<int> msg_ids;
    get_msg_ids(session,msg_ids);

    session.prefetch_messages(msg_ids);

    for(list<int>::const_iterator i=msg_ids.begin();
	i!=msg_ids.end(); ++i) {
      Message* m = session.get_message(*i);
      ostringstream r;
      r << session.msgid_to_seqnum[*i] << " FETCH (";
      if (uid_mode) {
	r << "UID " << *i << " ";
      }
      for(list<FetchAtt*>::const_iterator j=fetch_atts.begin();
	  j!=fetch_atts.end(); ++j) {
	if (j!=fetch_atts.begin()) {
	  r << " ";
	}
	r << (*j)->get_name() << ' ';
	(*j)->extract(session,*m,r);
      }
      r << ')';
      session.respond_untagged(r.str());
    }
  }


  FetchCmd::~FetchCmd()
  {
    for(list<FetchAtt*>::const_iterator i = fetch_atts.begin();
	i != fetch_atts.end(); ++i) {
      delete *i;
    }
  }

};
