// imapd/SearchCmd.hh
// 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.
 
#ifndef Imapd_SearchCmd_hh
#define Imapd_SearchCmd_hh

#include "Command.hh"
#include "unimplemented_commands.hh"

#include "utils.hh"

namespace Imapd {

  class SearchKey {
  public:
    virtual ~SearchKey() {}
    virtual ostream& write_query(ostream& s, Session& session) = 0;
    virtual bool get_sense(void) = 0;
    virtual bool is_all(void) { return false; }
  };


  class SearchKeyNotImplemented: public SearchKey {
  public:
    SearchKeyNotImplemented(void) {}
    ostream& write_query(ostream& s, Session& session)
    { throw NotImplemented(); return s; }
    virtual bool get_sense(void) { throw NotImplemented(); return true; }
  };


  class SearchKeyRowCondition: public SearchKey {
    private:
    const string table_name;
    const string condition;
    const bool sense;
  public:
    SearchKeyRowCondition(string t, string c, bool s):
      table_name(t), condition(c), sense(s) {}
    ostream& write_query(ostream& s, Session& session);
    bool get_sense(void) { return sense; }
  };


  class SearchKeyFieldEqual: public SearchKeyRowCondition {
  public:
    SearchKeyFieldEqual(string t, string n, string v, bool s):
      SearchKeyRowCondition(t, n+"="+DmDatabase::Query::qs(v), s) {}
  };


  class SearchKeyFieldSubstring: public SearchKeyRowCondition {
  public:
    SearchKeyFieldSubstring(string t, string n, string v, bool s):
      SearchKeyRowCondition(t, n+" like '%"+escape_for_quoted_string(v)+"%'",
			      s) {}
  };


  class SearchKeyAll: public SearchKey {
  public:
    ostream& write_query(ostream& s, Session& session) { return s; }
    bool get_sense(void) { return true; }
    bool is_all(void) { return true; }
  };


  class SearchKeyFlag: public SearchKeyFieldEqual {
  public:
    SearchKeyFlag(string f):
      SearchKeyFieldEqual("imap_message_flags","flag",f,true) {}
  };


  class SearchKeyNoFlag: public SearchKeyFieldEqual {
  public:
    SearchKeyNoFlag(string f):
      SearchKeyFieldEqual("imap_message_flags","flag",f,false) {}
  };


  class SearchKeyAddr: public SearchKeyFieldSubstring {
  public:
    SearchKeyAddr(string addr_type, string pattern):
      SearchKeyFieldSubstring(addr_type+"_addrs","addr",pattern,true) {}
  };


  class SearchKeyDateBefore: public SearchKeyRowCondition {
  public:
    SearchKeyDateBefore(string date):
      SearchKeyRowCondition("messages","msgdate<'"+date+"'",true) {}
  };


  class SearchKeyBody: public SearchKeyFieldSubstring {
  public:
    SearchKeyBody(string pattern):
      SearchKeyFieldSubstring("messages","body",pattern,true) {}
  };
  

  class SearchKeyFrom: public SearchKeyFieldSubstring {
  public:
    SearchKeyFrom(string pattern):
      SearchKeyFieldSubstring("messages","from_addr",pattern,true) {}
  };


  class SearchKeyDateSameDay: public SearchKeyRowCondition {
  public:
    SearchKeyDateSameDay(string date):
      SearchKeyRowCondition("messages",
	      "date_trunc('day',msgdate)=date_trunc('day','"+date+"')",
			      true) {}
  };
  
  
  class SearchKeyDateOnOrAfter: public SearchKeyRowCondition {
  public:
    SearchKeyDateOnOrAfter(string date):
      SearchKeyRowCondition("messages",
	      "date_trunc('day',msgdate)>=date_trunc('day','"+date+"')",
			      true) {}

  };
  

  class SearchKeySubject: public SearchKeyFieldSubstring {
  public:
    SearchKeySubject(string pattern):
      SearchKeyFieldSubstring("messages","subject",pattern,true) {}
  };
  

  class SearchKeyText: public SearchKeyRowCondition {
  public:
    SearchKeyText(string pattern):
      SearchKeyRowCondition("messages",
	      "header like '%"+pattern+"%' or body like '%"+pattern+"%'",
			    true) {}
  };
  

  class SearchKeyHeader: public SearchKeyRowCondition {
  public:
    SearchKeyHeader(ci_string header, string pattern):
      SearchKeyRowCondition("messages",
      // Hmm, the .* ought to not match into the next headers.
      // SQL requires that the whole regexp has the same 
      // case-sensitivity, which is not what we want.
              string("headers::text ~* '")+header.c_str()+":.*"+pattern+"'",
              true) {}
  };


  class SearchKeySizeLarger: public SearchKeyRowCondition {
  public:
    SearchKeySizeLarger(int value):
      SearchKeyRowCondition("messages","size>"+int_to_string(value),true) {}
  };


  class SearchKeyNot: public SearchKey {
  private:
    SearchKey* const search_key;
  public:
    SearchKeyNot(SearchKey* sk): search_key(sk) {}
    ~SearchKeyNot() { delete search_key; }
    ostream& write_query(ostream& s, Session& session);
    bool get_sense(void);
  };
  
  
  class SearchKeyOr: public SearchKey {
  private:
    SearchKey* const left;
    SearchKey* const right;
  public:
    SearchKeyOr(SearchKey* l, SearchKey* r): left(l), right(r) {}
    ~SearchKeyOr() { delete left; delete right; }
    ostream& write_query(ostream& s, Session& session);
    bool get_sense(void);
    bool is_all(void);
  };
  

  class SearchKeySentBefore: public SearchKeyDateBefore {
  public:
    SearchKeySentBefore(string date): SearchKeyDateBefore(date) {}
  };


  class SearchKeySentSameDay: public SearchKeyDateSameDay {
  public:
    SearchKeySentSameDay(string date): SearchKeyDateSameDay(date) {}
  };
  
  
  class SearchKeySentOnOrAfter: public SearchKeyDateOnOrAfter {
  public:
    SearchKeySentOnOrAfter(string date): SearchKeyDateOnOrAfter(date) {}
  };
  

  class SearchKeySizeSmaller: public SearchKeyRowCondition {
  public:
    SearchKeySizeSmaller(int value):
      SearchKeyRowCondition("messages","size<"+int_to_string(value),true) {}

  };
  

  class SearchKeyUid: public SearchKey {
  private:
    MessageSet* msgs;
  public:
    SearchKeyUid(MessageSet* s): msgs(s) {}
    ~SearchKeyUid() { delete msgs; }
    ostream& write_query(ostream& s, Session& session);
    bool get_sense(void) {return true;}
  };


  class SearchKeyMsgset: public SearchKey {
  private:
    MessageSet* msgs;
  public:
    SearchKeyMsgset(MessageSet* s): msgs(s) {}
    ~SearchKeyMsgset() { delete msgs; }
    ostream& write_query(ostream& s, Session& session);
    bool get_sense(void) {return true;}
  };


  class SearchKeySubexpression: public SearchKey {
  private:
    list<SearchKey*> subexprs;  // who deletes these?
  public:
    SearchKeySubexpression(list<SearchKey*> s): subexprs(s) {}
    ostream& write_query(ostream& s, Session& session);
    bool get_sense(void);
    bool is_all(void);
  };


  class SearchCmd: public UidableCmd {
  private:
    string charset;
    list<SearchKey*> search_keys;
  public:
    SearchCmd(string t, string charset, list<SearchKey*> s):
      UidableCmd(t,"SEARCH"), search_keys(s) {}
    ~SearchCmd();
  private:
    void runbody(Session& session);
  };

};

#endif
