// imapd/SearchCmd.cc
// This file is part of Decimail; see http://decimail.org
// (C) 2004 Philip Endecott
 
// This is version $Name$
//   (if there is no version (e.g. V0-1) mentioned in the previous line,
//    this is probably a snapshot from between "official" releases.)
 
// 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 "SearchCmd.hh"


namespace Imapd {


  ostream& SearchKeyRowCondition::write_query(ostream& s, Session& session)
  {
    s << "select msg_id from " << table_name << " where " << condition;
    return s;
  }


  ostream& SearchKeyNot::write_query(ostream& s, Session& session)
  {
    if (search_key->is_all()) {
      s << "select 0 as msg_id where false";
      return s;
    } 
    return search_key->write_query(s,session);
  }

  bool SearchKeyNot::get_sense(void)
  {
    if (search_key->is_all()) {
      return true;
    }
    return !search_key->get_sense();
  }


  ostream& SearchKeyOr::write_query(ostream& s, Session& session)
  {
    if (left->get_sense() && right->get_sense()) {
      // L U R
      s << "(";
      left->write_query(s,session);
      s << ") union (";
      right->write_query(s,session);
      s << ")";
    } else if (left->get_sense() && !right->get_sense()) {
      // L U !R  == L - R
      s << "(";
      left->write_query(s,session);
      s << ") except (";
      right->write_query(s,session);
      s << ")";
    } else if (!left->get_sense() && right->get_sense()) {
      // !L U R  == R - L
      s << "(";
      right->write_query(s,session);
      s << ") except (";
      left->write_query(s,session);
      s << ")";
    } else {
      // !L U !R  ==  !(L n R)
      // get_sense() returns false in this case
      s << "(";
      left->write_query(s,session);
      s << ") intersect (";
      right->write_query(s,session);
      s << ")";
    }
    return s;
  }

  bool SearchKeyOr::get_sense(void)
  {
    if (!left->get_sense() && !right->get_sense()) {
      return false;
    } else {
      return true;
    }
  }

  bool SearchKeyOr::is_all(void)
  {
    return left->is_all() || right->is_all();
  }

  
  ostream& SearchKeyUid::write_query(ostream& s, Session& session)
  {
    list<int> msg_ids;
    msgs->select_uid(session,msg_ids);
    s << "(select msg_id from messages where msg_id in (";
    bool first=true;
    for(list<int>::const_iterator i = msg_ids.begin();
        i != msg_ids.end(); ++i) {
      if (!first) {
        s << ",";
      }
      s << *i;
      first=false;
    }
    s << "))";
    return s;
  }


  ostream& SearchKeyMsgset::write_query(ostream& s, Session& session)
  {
    list<int> msg_ids;
    msgs->select_seqnum(session,msg_ids);
    s << "(select msg_id from messages where msg_id in (";
    bool first=true;
    for(list<int>::const_iterator i = msg_ids.begin();
        i != msg_ids.end(); ++i) {
      if (!first) {
        s << ",";
      }
      s << *i;
      first=false;
    }
    s << "))";
    return s;
  }


  ostream& SearchKeySubexpression::write_query(ostream& s, Session& session)
  {
    if (get_sense()) {
      s << "(";
      bool first=true;
      bool some_inv=false;
      for(list<SearchKey*>::const_iterator i = subexprs.begin();
	  i!=subexprs.end(); ++i) {
	if ((*i)->get_sense()) {
	  if (!(*i)->is_all()) {
	    if (first) {
	      first=false;
	    } else {
	      s << " intersect ";
	    }
	    s << "(";
	    (*i)->write_query(s,session);
	    s << ")";
	  }
	} else {
	  some_inv=true;
	}
      }
      s << ")";
      if (some_inv) {
	s <<" except (";
	bool first=true;
	for(list<SearchKey*>::const_iterator i = subexprs.begin();
	    i!=subexprs.end(); ++i) {
	  if (!(*i)->get_sense()) {
	    if (first) {
	      first=false;
	    } else {
	      s << " intersect ";
	    }
	    s << "(";
	    (*i)->write_query(s,session);
	    s << ")";
	  }
	}
	s << ")";
      }
    } else {
      bool first=true;
      for (list<SearchKey*>::const_iterator i = subexprs.begin();
	  i!=subexprs.end(); ++i) {
	if (!(*i)->is_all()) {
	  if (first) {
	    first=false;
	  } else 	{
	  s << " union ";
	  }
	  s << "(";
	  (*i)->write_query(s,session);
	  s << ")";
	}
      }
    }
    return s;
  }

  bool SearchKeySubexpression::get_sense(void)
  {
    for(list<SearchKey*>::const_iterator i = subexprs.begin();
	i!=subexprs.end(); ++i) {
      if (!(*i)->is_all()) {
	if ((*i)->get_sense()) {
	  return true;
	}
      }
    }
    return false;
  }

  bool SearchKeySubexpression::is_all(void)
  {
    return subexprs.size()==1 && subexprs.front()->is_all();
  }


  void SearchCmd::runbody(Session& session)
  {
    session.check_state(Session::selected);
    
    ostringstream s;
    s << "(" << session.get_mailbox_query() << ")";
    for(list<SearchKey*>::iterator i = search_keys.begin();
	i != search_keys.end(); ++i) {
      if (!(*i)->is_all()) {
	if ((*i)->get_sense()) {
	  s << " intersect (";
	} else {
	  s << " except (";
	}
	(*i)->write_query(s,session);
	s << ")";
      }
    }

    DmDatabase::Query q(&session.get_db());
    q << s.str();
    cerr << "Search query is: " << s.str() << endl;
    q.run();

    ostringstream r;
    r << "SEARCH";

    for(int i=0; i<q.get_ntuples(); ++i) {
      r << ' ';
      int msg_id = q.get_num(i,0);
      if (uid_mode) {
	r << msg_id;
      } else {
	r << session.msgid_to_seqnum[msg_id];
      }
    }

    session.respond_untagged(r.str());
  }


  SearchCmd::~SearchCmd()
  {
    for(list<SearchKey*>::iterator i = search_keys.begin();
	i != search_keys.end(); ++i) {
      delete *i;
    }
  }

};
