/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
//         www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard ([email protected])
//
// ProjectForge is dual-licensed.
//
// This community edition 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; version 3 of the License.
//
// This community edition 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.projectforge.core;

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

import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.FetchMode;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.projectforge.common.DateHelper;


/**
 * Stores the expressions and settings for creating a hibernate criteria object. This template is useful for avoiding the need of a
 * hibernate session in the stripes action classes.
 * @author Kai Reinhard ([email protected])
 */
public class QueryFilter
{
  private final List<Object> filterSettings = new ArrayList<Object>();

  private int maxResults = -1;

  private String name;

  private String alias;

  private BaseSearchFilter filter;

  private FetchMode fetchMode;

  private String associationPath = null;

  private Locale locale;

  /**
   * Creates new QueryFilter with a new SearchFilter as filter.
   */
  public QueryFilter()
  {
    this.filter = new BaseSearchFilter();
  }

  public QueryFilter(final BaseSearchFilter filter)
  {
    this.filter = filter;
  }

  private QueryFilter(final String name)
  {
    this.name = name;
  }

  public String getName()
  {
    return name;
  }

  public String getAlias()
  {
    return alias;
  }

  public BaseSearchFilter getFilter()
  {
    return filter;
  }

  /**
   * Locale is needed for lucene stemmers (hibernate search).
   * @return
   */
  public Locale getLocale()
  {
    if (locale == null) {
      return Locale.GERMAN;
    }
    return locale;
  }

  public void setLocale(final Locale locale)
  {
    this.locale = locale;
  }

  /**
   * If an error occured (e. g. lucene parse exception) this message will be returned.
   * @return
   */
  public String getErrorMessage()
  {
    return filter.getErrorMessage();
  }

  public void setErrorMessage(final String errorMessage)
  {
    filter.setErrorMessage(errorMessage);
  }

  public boolean hasErrorMessage()
  {
    return filter.hasErrorMessage();
  }

  public void clearErrorMessage()
  {
    filter.clearErrorMessage();
  }

  /**
   * @see org.hibernate.Criteria#add(Criterion)
   * @param criterion
   * @return
   */
  public QueryFilter add(final Criterion criterion)
  {
    filterSettings.add(criterion);
    return this;
  }

  /**
   * @see org.hibernate.Criteria#addOrder(Order)
   * @param order
   * @return
   */
  public QueryFilter addOrder(final Order order)
  {
    filterSettings.add(order);
    return this;
  }

  public void setFetchMode(final String associationPath, final FetchMode mode)
  {
    this.associationPath = associationPath;
    this.fetchMode = mode;
  }

  /**
   * Adds Expression.between for given time period.
   * @param dateField
   * @param year if <= 0 do nothing.
   * @param month if < 0 choose whole year, otherwise given month. (Calendar.MONTH);
   */
  public void setYearAndMonth(final String dateField, final int year, final int month)
  {
    if (year > 0) {
      final Calendar cal = DateHelper.getUTCCalendar();
      cal.set(Calendar.YEAR, year);
      java.sql.Date lo = null;
      java.sql.Date hi = null;
      if (month >= 0) {
        cal.set(Calendar.MONTH, month);
        cal.set(Calendar.DAY_OF_MONTH, 1);
        lo = new java.sql.Date(cal.getTimeInMillis());
        final int lastDayOfMonth = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
        cal.set(Calendar.DAY_OF_MONTH, lastDayOfMonth);
        hi = new java.sql.Date(cal.getTimeInMillis());
      } else {
        cal.set(Calendar.DAY_OF_YEAR, 1);
        lo = new java.sql.Date(cal.getTimeInMillis());
        final int lastDayOfYear = cal.getActualMaximum(Calendar.DAY_OF_YEAR);
        cal.set(Calendar.DAY_OF_YEAR, lastDayOfYear);
        hi = new java.sql.Date(cal.getTimeInMillis());
      }
      add(Restrictions.between(dateField, lo, hi));
    }
  }

  public Criteria buildCriteria(final Session session, final Class< ? > clazz)
  {
    final Criteria criteria = session.createCriteria(clazz);
    buildCriteria(criteria);
    return criteria;
  }

  private void buildCriteria(final Criteria criteria)
  {
    for (final Object obj : filterSettings) {
      if (obj instanceof Criterion) {
        criteria.add((Criterion) obj);
      } else if (obj instanceof Order) {
        criteria.addOrder((Order) obj);
      } else if (obj instanceof Alias) {
        final Alias alias = (Alias) obj;
        criteria.createAlias(alias.arg0, alias.arg1, alias.joinType);
      } else if (obj instanceof QueryFilter) {
        final QueryFilter filter = (QueryFilter) obj;
        Criteria subCriteria;
        if (StringUtils.isEmpty(filter.getAlias()) == true) {
          subCriteria = criteria.createCriteria(filter.getName());
        } else {
          subCriteria = criteria.createCriteria(filter.getName(), filter.getAlias());
        }
        filter.buildCriteria(subCriteria);
      }
    }
    if (associationPath != null) {
      criteria.setFetchMode(associationPath, fetchMode);
    }
    if (maxResults > 0) {
      criteria.setMaxResults(maxResults);
    }
  }

  /**
   * @see org.hibernate.Criteria#createAlias(String, String)
   */
  public QueryFilter createAlias(final String arg0, final String arg1)
  {
    filterSettings.add(new Alias(arg0, arg1));
    return this;
  }

  /**
   * @see org.hibernate.Criteria#createAlias(String, String, int)
   */
  public QueryFilter createAlias(final String arg0, final String arg1, final int joinType)
  {
    filterSettings.add(new Alias(arg0, arg1, joinType));
    return this;
  }

  public QueryFilter createCriteria(final String name)
  {
    final QueryFilter filter = new QueryFilter(name);
    filterSettings.add(filter);
    return filter;
  }

  public QueryFilter createCriteria(final String name, final String alias)
  {
    final QueryFilter filter = new QueryFilter(name);
    filter.alias = alias;
    filterSettings.add(filter);
    return filter;
  }

  /**
   * @see org.hibernate.Criteria#setMaxResults(int)
   * @param value
   * @return
   */
  public QueryFilter setMaxResults(final int value)
  {
    this.maxResults = value;
    return this;
  }

  public int getMaxResults()
  {
    return maxResults;
  }

  class Alias
  {
    String arg0;

    String arg1;

    int joinType = Criteria.INNER_JOIN;

    Alias(final String arg0, final String arg1)
    {
      this.arg0 = arg0;
      this.arg1 = arg1;
    }

    Alias(final String arg0, final String arg1, final int joinType)
    {
      this.arg0 = arg0;
      this.arg1 = arg1;
      this.joinType = joinType;
    }
  };
}