/*
 * Copyright 2006 Open Source Applications Foundation
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.unitedinternet.cosmo.dao.hibernate;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;

import org.hibernate.HibernateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate5.SessionFactoryUtils;
import org.springframework.stereotype.Repository;
import org.unitedinternet.cosmo.calendar.query.CalendarFilter;
import org.unitedinternet.cosmo.calendar.query.CalendarFilterEvaluater;
import org.unitedinternet.cosmo.dao.CalendarDao;
import org.unitedinternet.cosmo.dao.query.ItemFilterProcessor;
import org.unitedinternet.cosmo.dao.query.hibernate.CalendarFilterConverter;
import org.unitedinternet.cosmo.model.CollectionItem;
import org.unitedinternet.cosmo.model.ContentItem;
import org.unitedinternet.cosmo.model.EntityFactory;
import org.unitedinternet.cosmo.model.ICalendarItem;
import org.unitedinternet.cosmo.model.Item;
import org.unitedinternet.cosmo.model.NoteItem;
import org.unitedinternet.cosmo.model.User;
import org.unitedinternet.cosmo.model.filter.EventStampFilter;
import org.unitedinternet.cosmo.model.filter.ItemFilter;
import org.unitedinternet.cosmo.model.filter.NoteItemFilter;
import org.unitedinternet.cosmo.model.hibernate.EntityConverter;
import org.unitedinternet.cosmo.model.hibernate.HibCollectionItem;
import org.unitedinternet.cosmo.model.hibernate.HibNoteItem;

import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.TimeZone;
import net.fortuna.ical4j.model.TimeZoneRegistryFactory;

/**
 *
 */
@Repository
public class CalendarDaoImpl implements CalendarDao {

    private static final Logger LOG = LoggerFactory.getLogger(CalendarDaoImpl.class);

    @Autowired
    private EntityFactory entityFactory;

    @Autowired
    private ItemFilterProcessor itemFilterProcessor = null;

    @Autowired
    private EntityConverter entityConverter;

    @PersistenceContext
    private EntityManager em;

    public CalendarDaoImpl() {
        // Default
    }

    /*
     * Note that this method is used for CalDav REPORT and it needs to be properly implemented
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Override
    public Set<ICalendarItem> findCalendarItems(CollectionItem collection, CalendarFilter filter) {
        try {
            CalendarFilterConverter filterConverter = new CalendarFilterConverter();
            try {
                if (collection instanceof HibCollectionItem) {
                    /*
                     * Translate CalendarFilter to ItemFilter and execute filter. This does not make sense for external
                     * collections which are
                     */
                    ItemFilter itemFilter = filterConverter.translateToItemFilter(collection, filter);
                    Set results = itemFilterProcessor.processFilter(itemFilter);
                    Set<ICalendarItem> toReturn = (Set<ICalendarItem>) results;

                    /*
                     * Trigger the loading of lazy members and then clear the session so that Hibernate objects become
                     * GC eligible.
                     */
                    collection.getChildren();
                    for (ICalendarItem item : toReturn) {
                        item.getParents();
                        item.getStamps().size();
                        if (item instanceof HibNoteItem) {
                            HibNoteItem note = (HibNoteItem) item;
                            if (note.getModifications().size() > 0) {
                                for (NoteItem modif : note.getModifications()) {
                                    modif.getStamps().size();
                                }
                            }
                        }
                    }
                    this.em.clear();
                    return toReturn;
                }
            } catch (Exception e) {
                /* Set this log message to debug because all iPad requests trigger it and log files get polluted. */
                LOG.debug("Illegal filter item. Only VCALENDAR is supported so far.", e);
            }
            /*
             * Use brute-force method if CalendarFilter can't be translated to an ItemFilter (slower but at least gets
             * the job done). // TODO Check to see if this branch is really used in CalDAV clients.
             */
            Set<ICalendarItem> results = new HashSet<ICalendarItem>();
            Set<Item> itemsToProcess = collection.getChildren();
            CalendarFilterEvaluater evaluater = new CalendarFilterEvaluater();

            // Evaluate filter against all calendar items
            for (Item child : itemsToProcess) {

                // only care about calendar items
                if (child instanceof ICalendarItem) {

                    ICalendarItem content = (ICalendarItem) child;
                    Calendar calendar = entityConverter.convertContent(content);

                    if (calendar != null && evaluater.evaluate(calendar, filter)) {
                        results.add(content);
                    }
                }
            }

            return results;
        } catch (HibernateException e) {
            this.em.clear();
            throw SessionFactoryUtils.convertHibernateAccessException(e);
        }
    }

    @Override
    public Set<Item> findEvents(CollectionItem collection, Date rangeStart, Date rangeEnd, String timezoneId,
            boolean expandRecurringEvents) {

        // Create a NoteItemFilter that filters by parent
        NoteItemFilter itemFilter = new NoteItemFilter();
        itemFilter.setParent(collection);

        // and EventStamp by timeRange
        EventStampFilter eventFilter = new EventStampFilter();

        if (timezoneId != null) {
            TimeZone timeZone = TimeZoneRegistryFactory.getInstance().createRegistry().getTimeZone(timezoneId);
            eventFilter.setTimezone(timeZone);
        }

        eventFilter.setTimeRange(rangeStart, rangeEnd);
        eventFilter.setExpandRecurringEvents(expandRecurringEvents);
        itemFilter.getStampFilters().add(eventFilter);

        try {
            return itemFilterProcessor.processFilter(itemFilter);
        } catch (HibernateException e) {
            this.em.clear();
            throw SessionFactoryUtils.convertHibernateAccessException(e);
        }
    }

    @Override
    public ContentItem findEventByIcalUid(String uid, CollectionItem calendar) {
        if (!(calendar instanceof HibCollectionItem)) {
            // External collections cannot be queried.
            return null;
        }
        try {
            TypedQuery<ContentItem> query = this.em.createNamedQuery("event.by.calendar.icaluid", ContentItem.class);
            query.setParameter("calendar", calendar);
            query.setParameter("uid", uid);
            List<ContentItem> resultList = query.getResultList();
            if (!resultList.isEmpty()) {
                return resultList.get(0);
            }
            return null;
        } catch (HibernateException e) {
            this.em.clear();
            throw SessionFactoryUtils.convertHibernateAccessException(e);
        }
    }

    public ItemFilterProcessor getItemFilterProcessor() {
        return itemFilterProcessor;
    }

    public void setItemFilterProcessor(ItemFilterProcessor itemFilterProcessor) {
        this.itemFilterProcessor = itemFilterProcessor;
    }

    /**
     * Initializes the DAO, sanity checking required properties and defaulting optional properties.
     */
    public void init() {
        if (itemFilterProcessor == null) {
            throw new IllegalStateException("itemFilterProcessor is required");
        }

        if (entityFactory == null) {
            throw new IllegalStateException("entityFactory is required");
        }

        entityConverter = new EntityConverter(this.entityFactory);
    }

    @Override
    public Set<ICalendarItem> findCalendarEvents(Calendar calendar, User cosmoUser) {
        return entityConverter.convertCalendar(calendar);
    }

    /**
     * @param entityFactory
     *            the entityFactory to set
     */
    public void setEntityFactory(EntityFactory entityFactory) {
        this.entityFactory = entityFactory;
    }

}