/*
 * Copyright (C) 2003-2008 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * 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, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.faq.service;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import org.exoplatform.commons.utils.ISO8601;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;

/**
 * Created by The eXo Platform SARL
 * 
 * This object intermediate to give search Category or question in form advance search.
 * All value user input search will save this object
 * 
 * Author : Truong Nguyen
 * truong.nguyen@exoplatform.com
 * May 5, 2008, 3:48:51 PM
 */
public class FAQEventQuery {

  public static final String FAQ_CATEGORY               = "faqCategory";

  public static final String FAQ_QUESTION               = "faqQuestion";

  public static final String CATEGORY_AND_QUESTION      = "categoryAndQuestion";

  private static Log         log                        = ExoLogger.getLogger(FAQEventQuery.class);

  private String             type;

  private String             text;

  private String             name;

  private String             isModeQuestion;

  private String             moderator;

  private String             path;

  private String             author;

  private String             userId;

  private String             questionDisplayMode;

  private String             email;

  private String             question;

  private String             response;

  private String             comment;

  private String             attachment;

  private List<String>       userMembers;

  private Calendar           fromDate;

  private Calendar           toDate;

  private String             language;

  private boolean            isAnd                      = false;

  private boolean            isAdmin                    = false;

  private List<String>       viewingCategories;

  private boolean            isQuestionLevelSearch      = false;

  private boolean            isLanguageLevelSearch      = false;

  private boolean            isAnswerCommentLevelSearch = false;

  private boolean            isSearchOnDefaultLanguage  = false;

  public String getLanguage() {
    return language;
  }

  public void setLanguage(String language) {
    this.language = language;
  }

  public void setQuestionDisplayMode(String questionDisplayMode) {
    this.questionDisplayMode = questionDisplayMode;
  }

  public String getQuestionDisplayMode() {
    return questionDisplayMode;
  }

  /**
   * Instantiates a new fAQ event query.
   */
  public FAQEventQuery() {
    viewingCategories = new ArrayList<String>();
  }

  /**
   * Gets the type.
   * 
   * @return the type
   */
  public String getType() {
    return type;
  }

  /**
   * Sets the type.
   * 
   * @param type the new type
   */
  public void setType(String type) {
    this.type = type;
  }

  /**
   * Gets the text.
   * 
   * @return the text
   */
  public String getText() {
    return text;
  }

  /**
   * Sets the text.
   * 
   * @param text the new text
   */
  public void setText(String text) {
    this.text = text;
  }

  /**
   * Gets the name.
   * 
   * @return the name
   */
  public String getName() {
    return name;
  }

  /**
   * Sets the checks if is mode question.
   * 
   * @param isModeQuestion the new checks if is mode question
   */
  public void setIsModeQuestion(String isModeQuestion) {
    this.isModeQuestion = isModeQuestion;
  }

  /**
   * Gets the checks if is mode question.
   * 
   * @return the checks if is mode question
   */
  public String getIsModeQuestion() {
    return isModeQuestion;
  }

  /**
   * Sets the name.
   * 
   * @param name the new name
   */
  public void setName(String name) {
    this.name = name;
  }

  /**
   * Gets the moderator.
   * 
   * @return the moderator
   */
  public String getModerator() {
    return moderator;
  }

  /**
   * Sets the moderator.
   * 
   * @param moderator the new moderator
   */
  public void setModerator(String moderator) {
    this.moderator = moderator;
  }

  /**
   * Gets the path.
   * 
   * @return the path
   */
  public String getPath() {
    return path;
  }

  /**
   * Sets the path.
   * 
   * @param path the new path
   */
  public void setPath(String path) {
    this.path = path;
  }

  /**
   * Gets the author.
   * 
   * @return the author
   */
  public String getAuthor() {
    return author;
  }

  public void setUserId(String userId) {
    this.userId = userId;
  }

  public String getUserId() {
    return userId;
  }

  /**
   * Sets the author.
   * 
   * @param author the new author
   */
  public void setAuthor(String author) {
    this.author = author;
  }

  /**
   * Gets the email.
   * 
   * @return the checks if is lock
   */
  public String getEmail() {
    return email;
  }

  /**
   * Sets the email.
   * 
   * @param email the new email
   */
  public void setEmail(String email) {
    this.email = email;
  }

  /**
   * Gets the question.
   * 
   * @return the question
   */
  public String getQuestion() {
    return question;
  }

  /**
   * Sets the question.
   * 
   * @param question the new question
   */
  public void setQuestion(String question) {
    this.question = question;
  }

  /**
   * Gets the response.
   * 
   * @return the response
   */
  public String getResponse() {
    return response;
  }

  /**
   * Sets the response.
   * 
   * @param response the new response
   */
  public void setResponse(String response) {
    this.response = response;
  }

  /**
   * Gets the attachment.
   * 
   * @return the attachment
   */
  public String getAttachment() {
    return attachment;
  }

  /**
   * Sets the attachment.
   * 
   * @param attachment the new attachment
   */
  public void setAttachment(String attachment) {
    this.attachment = attachment;
  }

  /**
   * @return the userMembers
   */
  public List<String> getUserMembers() {
    return userMembers;
  }

  /**
   * @param userMembers the userMembers to set
   */
  public void setUserMembers(List<String> userMembers) {
    this.userMembers = userMembers;
  }

  /**
   * Gets the from date.
   * 
   * @return the from date
   */
  public Calendar getFromDate() {
    return fromDate;
  }

  /**
   * Sets the from date.
   * 
   * @param fromDate the new from date
   */
  public void setFromDate(Calendar fromDate) {
    this.fromDate = fromDate;
  }

  /**
   * Gets the to date.
   * 
   * @return the to date
   */
  public Calendar getToDate() {
    return toDate;
  }

  /**
   * Sets the to date.
   * 
   * @param toDate the new to date
   */
  public void setToDate(Calendar toDate) {
    this.toDate = toDate;
  }

  /**
   * Gets the checks.
   * 
   * @return the checks if is true or false
   */
  public boolean getIsAnd() {
    return this.isAnd;
  }

  /**
   * @return the isAdmin
   */
  public boolean isAdmin() {
    return isAdmin;
  }

  /**
   * @param isAdmin the isAdmin to set
   */
  public void setAdmin(boolean isAdmin) {
    this.isAdmin = isAdmin;
  }

  /**
   * This method is query on search 
   * Gets the path query.
   * 
   * @return string the path query
   */
  public String getQuery() throws Exception {
    StringBuilder queryString = new StringBuilder();

    // ######################### Category Search ###########################
    if (type.equals(FAQ_CATEGORY)) { // Category Search
      queryString = buildCategoryQuery();
      // ###################### Question Search ############################
    } else if (type.equals(FAQ_QUESTION)) {
      queryString = buildQuestionQuery();
      // ###################### Quick Search ###############################
    } else if (type.equals(CATEGORY_AND_QUESTION)) {

      queryString = buildCategoryAndQuestionQuery();
    }
    if (log.isDebugEnabled()) {
      log.debug(queryString.toString());
    }
    return queryString.toString();
  }

  private StringBuilder buildCategoryQuery() {
    StringBuilder queryString;
    queryString = new StringBuilder();
    queryString.append("/jcr:root").append(path);
    queryString.append("//element(*,exo:faqCategory)");
    queryString.append("[(@exo:isView='true') ");

    if (text != null && text.length() > 0) {
      queryString.append(" and (jcr:contains(., '").append(text).append("'))");
    }
    if (name != null && name.length() > 0) {
      queryString.append(" and (jcr:contains(@exo:name, '").append(name).append("'))");
    }
    if (isModeQuestion != null && isModeQuestion.length() > 0 && !isModeQuestion.equals("AllCategories")) {
      queryString.append(" and (@exo:isModerateQuestions='").append(isModeQuestion).append("')");
    }
    if (moderator != null && moderator.length() > 0) {
      queryString.append(" and (jcr:contains(@exo:moderators, '").append(moderator).append("'))");
    }

    if (!isAdmin) {
      queryString.append(" and (not(@exo:userPrivate) or @exo:userPrivate=''");
      if (userMembers != null && !userMembers.isEmpty()) {
        for (String str : userMembers) {
          queryString.append(" or @exo:userPrivate='").append(str).append("' or @exo:moderators='").append(str).append("'");
        }
      }
      queryString.append(")");
    }

    if (fromDate != null) {
      queryString.append(" and (@exo:createdDate >= xs:dateTime('").append(ISO8601.format(fromDate)).append("'))");
    }
    if (toDate != null) {
      queryString.append(" and (@exo:createdDate <= xs:dateTime('").append(ISO8601.format(toDate)).append("'))");
    }

    queryString.append("]");
    return queryString;
  }

  private StringBuilder buildQuestionQuery() {
    StringBuilder queryString;
    // ## (questionSearch (or|and) languageSearch or answerSearch) and CategoryScoping and fulltextSearch ##
    queryString = new StringBuilder();
    isQuestionLevelSearch = false;
    isAnswerCommentLevelSearch = false;
    isAnd = false;
    queryString.append("/jcr:root").append(path);
    queryString.append("//* [");

    // search on main questions
    StringBuilder propertiesSearch = new StringBuilder("(");
    isAnd = false;
    if (author != null && author.length() > 0) {
      if (isAnd)
        propertiesSearch.append(" and ");
      propertiesSearch.append("jcr:contains(@exo:author, '").append(author).append("')");
      isAnd = true;
    }
    if (email != null && email.length() > 0) {
      if (isAnd)
        propertiesSearch.append(" and ");
      propertiesSearch.append("jcr:contains(@exo:email, '").append(email).append("')");
      isAnd = true;
    }
    if (fromDate != null) {
      if (isAnd)
        propertiesSearch.append(" and ");
      // propertiesSearch.append("@exo:createdDate").append(" >= xs:dateTime('").append(ISO8601.format(fromDate)).append("')") ;
      propertiesSearch.append("((@exo:createdDate >= xs:dateTime('").append(ISO8601.format(fromDate)).append("')) " + "or (@exo:dateResponse >= xs:dateTime('").append(ISO8601.format(fromDate)).append("')) " + "or (@exo:dateComment >= xs:dateTime('").append(ISO8601.format(fromDate)).append("')))");
      isAnd = true;
    }
    if (toDate != null) {
      if (isAnd)
        propertiesSearch.append(" and ");
      // propertiesSearch.append("@exo:createdDate").append(" <= xs:dateTime('").append(ISO8601.format(toDate)).append("')") ;
      propertiesSearch.append("((@exo:createdDate <= xs:dateTime('").append(ISO8601.format(toDate)).append("')) " + "or (@exo:dateResponse <= xs:dateTime('").append(ISO8601.format(toDate)).append("')) " + "or (@exo:dateComment <= xs:dateTime('").append(ISO8601.format(toDate)).append("')))");
      isAnd = true;
    }
    propertiesSearch.append(")");
    if (propertiesSearch.length() > 2 || isSearchOnDefaultLanguage)
      isQuestionLevelSearch = true;

    // search on language questions
    StringBuilder questionLanguageSearch = new StringBuilder("");
    if (question != null && question.length() > 0) {
      questionLanguageSearch.append("((exo:language='").append(language).append("')");
      questionLanguageSearch.append(" and ( jcr:contains(@exo:title,'").append(question).append("') or jcr:contains(@exo:name, '").append(question).append("') )");
      questionLanguageSearch.append(")");
      if (isSearchOnDefaultLanguage)
        isLanguageLevelSearch = false;
      else
        isLanguageLevelSearch = true;
    }

    // search on answers
    StringBuilder answerSearch = new StringBuilder("");
    if (response != null && response.length() > 0) {
      answerSearch.append("( exo:responseLanguage='").append(language).append("'");
      answerSearch.append(" and jcr:contains(@exo:responses,'" + response + "')");
      answerSearch.append(")");
      isAnswerCommentLevelSearch = true;
    }

    // search on comments
    StringBuilder commentSearch = new StringBuilder();
    if (comment != null && comment.length() > 0) {
      commentSearch.append("( exo:commentLanguage='").append(language).append("'");
      commentSearch.append(" and jcr:contains(@exo:comments,'" + comment + "')");
      commentSearch.append(")");
      isAnswerCommentLevelSearch = true;
    }

    // search on category scoping
    StringBuilder searchCategoryScoping = new StringBuilder("(");
    for (String category : getViewingCategories()) {
      if (searchCategoryScoping.length() > 1)
        searchCategoryScoping.append(" or ");
      searchCategoryScoping.append("exo:categoryId='").append(category).append("'");
    }
    searchCategoryScoping.append(")");

    if (isAnd) {
      // System.out.println("questionSearch" + questionSearch.toString());
      queryString.append(propertiesSearch.toString());
    }
    boolean isAdd = false;
    // build scope of searching question, answer, comment
    // queryString.append(" and "); // [properties search clause] and [ question, answer, comment search clause]
    StringBuilder quesAnsComClause = new StringBuilder("");
    if (questionLanguageSearch.length() > 0) {
      quesAnsComClause.append("(").append(questionLanguageSearch.toString());
      isAdd = true;
    }

    if (answerSearch.length() > 2) {
      if (isAdd) {
        quesAnsComClause.append(" or ").append(answerSearch.toString());
      } else {
        quesAnsComClause.append("(").append(answerSearch.toString());
        isAdd = true;
      }
    }

    if (commentSearch.length() > 2) {
      if (isAdd) {
        quesAnsComClause.append(" or ").append(commentSearch.toString());
      } else {
        quesAnsComClause.append("(").append(commentSearch.toString());
        isAdd = true;
      }
    }

    StringBuilder textSearch = new StringBuilder("");

    if (text != null && text.length() > 0) {
      textSearch.append("("); // open block.
      textSearch.append("jcr:contains(., '").append(text).append("')");
      textSearch.append(" and ( ").append(" exo:language='").append(language).append("'").append(" or exo:commentLanguage='").append(language).append("'").append(" or exo:responseLanguage='").append(language).append("'").append(")");
      textSearch.append(")"); // close block.
      isLanguageLevelSearch = false;
      isAnswerCommentLevelSearch = false;
      isQuestionLevelSearch = false;
    }

    if (textSearch.length() > 0) {
      if (isAdd) {
        quesAnsComClause.append(" or ").append(textSearch.toString());
      } else {
        quesAnsComClause.append("(").append(textSearch.toString());
        isAdd = true;
      }
    }
    if (isAdd)
      quesAnsComClause.append(")"); // close question, answer, comment search clause.

    if (quesAnsComClause.length() > 0) {
      if (isAnd) {
        queryString.append(" and ");
      }
      queryString.append(quesAnsComClause.toString());
    }

    if (!isAnd && !isAdd) { // if all of fields is empty of null, the search will be by language.
      queryString.append("(").append(" exo:language='").append(language).append("'").append(" or exo:commentLanguage='").append(language).append("'").append(" or exo:responseLanguage='").append(language).append("'").append(")");
    }

    /*
     * if(text != null && text.length() > 0 ) { if(isAdd){ queryString.append(" or (  jcr:contains(., '").append(text).append("')") ; }else { queryString.append("jcr:contains(., '").append(text).append("')") ; } queryString.append(" and ( " ) .append(" exo:language='").append(language).append("'") .append(" or exo:commentLanguage='").append(language).append("'")
     * .append(" or exo:responseLanguage='").append(language).append("'") .append(")") ; if(isAdd) queryString.append(" ) " ) ; isAdd = true ; } else if(text == null && !isAnswerCommentLevelSearch && questionLanguageSearch.length() <= 0){ if(isAdd){ queryString.append(" and (exo:language='").append(language).append("')") ; }else { queryString.append("(exo:language='").append(language).append("')")
     * ; isAdd = true ; } }
     */

    if (!searchCategoryScoping.toString().equals("()")) {
      if (isAnd || isAdd) {
        queryString.append(" and ").append(searchCategoryScoping.toString());
      } else {
        queryString.append(searchCategoryScoping.toString());
        isAdd = true;
      }
    }

    queryString.append("]"); // close property constraint

    return queryString;
  }

  private StringBuilder buildCategoryAndQuestionQuery() {
    StringBuilder queryString;
    // Quick search
    queryString = new StringBuilder();
    queryString.append("/jcr:root").append(path).append("//*[");
    if (text != null && text.length() > 0) {
      queryString.append(" jcr:contains(., '").append(text).append("')");
    }

    if (!isAdmin) {
      queryString.append(" and ( ");
      queryString.append("not(@exo:isApproved) or @exo:isApproved='true'");
      if (userId != null && userId.length() > 0) {
        queryString.append(" or exo:author='" + userId + "'");
      }
      queryString.append(" ) ");
    }

    // search on viewing categories
    if (viewingCategories.size() > 0) {
      queryString.append(" and (");
      int i = 0;
      for (String catId : viewingCategories) {
        if (i > 0)
          queryString.append(" or ");
        // on questions
        queryString.append("@exo:categoryId='").append(catId).append("'");
        // on categories
        queryString.append(" or @exo:id='").append(catId).append("'");
        queryString.append(" and (");
        queryString.append(" @exo:userPrivate=''");
        // search restricted audience in category
        if (userMembers != null && userMembers.size() > 0) {
          for (String id : userMembers) {
            queryString.append(" or @exo:userPrivate='").append(id).append("'");
          }
        }
        queryString.append(" ) ");
        i++;
      }
      queryString.append(")");
    }

    // search restricted audience
    /*
     * if(userMembers != null && userMembers.size() > 0) { queryString.append(" and (") ; int k = 0 ; for(String id : userMembers) { if(k > 0) queryString.append(" or "); queryString.append("(@exo:userPrivate='").append(id).append("')"); k ++ ; } queryString.append(" )") ; }else { queryString.append(" and not(@exo:userPrivate)") ; }
     */

    // add condition for exo:dateResponse and exo:dateComment to search on answers & comments
    if (fromDate != null) {
      queryString.append(" and ((@exo:createdDate >= xs:dateTime('").append(ISO8601.format(fromDate)).append("')) " + "or (@exo:dateResponse >= xs:dateTime('").append(ISO8601.format(fromDate)).append("')) " + "or (@exo:dateComment >= xs:dateTime('").append(ISO8601.format(fromDate)).append("')))");
    }
    if (toDate != null) {
      queryString.append(" and ((@exo:createdDate <= xs:dateTime('").append(ISO8601.format(toDate)).append("')) " + "or (@exo:dateResponse <= xs:dateTime('").append(ISO8601.format(toDate)).append("')) " + "or (@exo:dateComment <= xs:dateTime('").append(ISO8601.format(toDate)).append("')))");
    }
    queryString.append("]");
    return queryString;
  }

  /**
  * This method check date time user input interface 
  * Sets the date from to.
  * 
  * @param fromDate the from date
  * @param toDate the to date
  * @param property the property
  * 
  * @return the string
  */

  public void setViewingCategories(List<String> viewingCategories) {
    this.viewingCategories = viewingCategories;
  }

  public List<String> getViewingCategories() {
    return viewingCategories;
  }

  public boolean isQuestionLevelSearch() throws Exception {
    return isQuestionLevelSearch;
  }

  public boolean isAnswerCommentLevelSearch() throws Exception {
    return isAnswerCommentLevelSearch;
  }

  public void setSearchOnDefaultLanguage(boolean isSearchOnDefaultLanguage) {
    this.isSearchOnDefaultLanguage = isSearchOnDefaultLanguage;
  }

  public boolean isSearchOnDefaultLanguage() {
    return isSearchOnDefaultLanguage;
  }

  public void setLanguageLevelSearch(boolean isLanguageLevelSearch) {
    this.isLanguageLevelSearch = isLanguageLevelSearch;
  }

  public boolean isLanguageLevelSearch() {
    return isLanguageLevelSearch;
  }

  public void setComment(String comment) {
    this.comment = comment;
  }

  public String getComment() {
    return comment;
  }
}
