/**
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig licenses this file to you 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.jasig.schedassist.impl.caldav;

import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.TimeZone;
import net.fortuna.ical4j.model.TimeZoneRegistry;
import net.fortuna.ical4j.model.TimeZoneRegistryFactory;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.component.VTimeZone;
import net.fortuna.ical4j.model.parameter.Role;
import net.fortuna.ical4j.model.property.Attendee;
import net.fortuna.ical4j.model.property.DtEnd;
import net.fortuna.ical4j.model.property.DtStart;
import net.fortuna.ical4j.model.property.Organizer;
import net.fortuna.ical4j.model.property.Status;
import net.fortuna.ical4j.model.property.Transp;
import net.fortuna.ical4j.model.property.Uid;

import org.apache.commons.lang.Validate;
import org.jasig.schedassist.IAffiliationSource;
import org.jasig.schedassist.model.AppointmentRole;
import org.jasig.schedassist.model.AvailableBlock;
import org.jasig.schedassist.model.DefaultEventUtilsImpl;
import org.jasig.schedassist.model.ICalendarAccount;
import org.jasig.schedassist.model.IScheduleOwner;
import org.jasig.schedassist.model.IScheduleVisitor;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;

/**
 * Subclass of {@link DefaultEventUtilsImpl} specific for 
 * caldav.
 * 
 * The explicitSetTimeZone field is of significance. If set to true, one must also set
 * the timeZone property. The {@link InitializingBean#afterPropertiesSet()} implementation
 * on this class should be called to verify state (this is automatically called by the Spring IoC
 * container; only call this method explicitly specifically if not being constructed by Spring).
 * 
 * When true and the timezone can be resolved, the behavior of {@link #wrapEventInCalendar(VEvent)} and
 * {@link #constructAvailableAppointment(AvailableBlock, IScheduleOwner, IScheduleVisitor, String)} will change.
 * 
 * The explicitSetTimeZone and timeZone properties are encouraged when integrating with an Oracle Communications Suite 
 * environment.
 * 
 * @author Nicholas Blair, npblair@wisc.edu
 */
public class CaldavEventUtilsImpl extends DefaultEventUtilsImpl implements InitializingBean {

	private boolean explicitSetTimeZone = false;
	private String timeZone;
	private TimeZone _timeZone;
	/**
	 * 
	 * @param affiliationSource
	 */
	public CaldavEventUtilsImpl(IAffiliationSource affiliationSource) {
		super(affiliationSource);
	}
	/**
	 * @return the explicitSetTimeZone
	 */
	public boolean isExplicitSetTimeZone() {
		return explicitSetTimeZone;
	}
	/**
	 * @param explicitSetTimeZone the explicitSetTimeZone to set
	 */
	@Value("${caldav.explicitSetTimeZone:false}")
	public void setExplicitSetTimeZone(boolean explicitSetTimeZone) {
		this.explicitSetTimeZone = explicitSetTimeZone;
	}
	/**
	 * @return the timeZone
	 */
	public String getTimeZone() {
		return timeZone;
	}
	/**
	 * @param timeZone the timeZone to set
	 */
	@Value("${caldav.systemTimeZone:America/Chicago}")
	public void setTimeZone(String timeZone) {
		this.timeZone = timeZone;
	}

	/* (non-Javadoc)
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		if(isExplicitSetTimeZone()) {
			Validate.notEmpty(this.timeZone, "timeZone field cannot be empty if explicitSetTimeZone is true");
			TimeZoneRegistry registry = TimeZoneRegistryFactory.getInstance().createRegistry();
			_timeZone = registry.getTimeZone(this.timeZone);
			if(null == _timeZone) {
				throw new IllegalStateException("no timezone found for " + timeZone);
			}
		}
	}
	/**
	 * Calls the super implementation, and adds an {@link Organizer} and an {@link Uid}.
	 * If the explicitSetTimeZone field is true and the corresponding timeZone can be resolved, 
	 * the {@link DtStart} and {@link DtEnd} are modified to include the corresponding TZID parameter.
	 * 
	 * @see #constructOrganizer(ICalendarAccount)
	 * @see #generateNewUid()
	 *  (non-Javadoc)
	 * @see org.jasig.schedassist.model.DefaultEventUtilsImpl#constructAvailableAppointment(org.jasig.schedassist.model.AvailableBlock, org.jasig.schedassist.model.IScheduleOwner, org.jasig.schedassist.model.IScheduleVisitor, java.lang.String)
	 */
	@Override
	public VEvent constructAvailableAppointment(AvailableBlock block,
			IScheduleOwner owner, IScheduleVisitor visitor,
			String eventDescription) {
		VEvent event = super.constructAvailableAppointment(block, owner, visitor,
				eventDescription);
		if(isExplicitSetTimeZone() && _timeZone != null) {
			DtStart start = event.getStartDate();
			start.setTimeZone(_timeZone);
			
			DtEnd end = event.getEndDate();
			end.setTimeZone(_timeZone);
		}
		return event;
	}
	
	/* (non-Javadoc)
	 * @see org.jasig.schedassist.model.DefaultEventUtilsImpl#willEventCauseConflict(org.jasig.schedassist.model.ICalendarAccount, net.fortuna.ical4j.model.component.VEvent)
	 */
	@Override
	public boolean willEventCauseConflict(ICalendarAccount calendarAccount,
			VEvent event) {
		if(event == null) {
			return false;
		}
		
		Status status = event.getStatus();
		if(status != null && Status.VEVENT_CANCELLED.equals(status)) {
			return false;
		}
		
		Transp transp = event.getTransparency();
		if(null == transp) {
			return true;
		} else {
			return Transp.OPAQUE.equals(transp);
		}	
	}

	/* (non-Javadoc)
	 * @see org.jasig.schedassist.model.DefaultEventUtilsImpl#constructSchedulingAssistantAttendee(org.jasig.schedassist.model.ICalendarAccount, org.jasig.schedassist.model.AppointmentRole)
	 */
	@Override
	public Attendee constructSchedulingAssistantAttendee(
			ICalendarAccount calendarAccount, AppointmentRole role) {
		Attendee attendee = super.constructSchedulingAssistantAttendee(calendarAccount, role);
		if(AppointmentRole.VISITOR.equals(role)) {
			attendee.getParameters().add(Role.REQ_PARTICIPANT);
		} else if (AppointmentRole.OWNER.equals(role)) {
			attendee.getParameters().add(Role.CHAIR);
		}
		return attendee;
	}
	/* (non-Javadoc)
	 * @see org.jasig.schedassist.model.DefaultEventUtilsImpl#convertBlockToReflectionEvent(org.jasig.schedassist.model.AvailableBlock)
	 */
	@Override
	protected VEvent convertBlockToReflectionEvent(AvailableBlock block) {
		VEvent reflection = super.convertBlockToReflectionEvent(block);
		reflection.getProperties().add(this.generateNewUid());
		return reflection;
	}
	/**
	 * If the explicitSetTimeZone field is true and the corresponding timeZone can be resolved, 
	 * the corresponding {@link VTimeZone} is added to the returned {@link Calendar}.
	 * 
	 * (non-Javadoc)
	 * @see org.jasig.schedassist.model.DefaultEventUtilsImpl#wrapEventInCalendar(net.fortuna.ical4j.model.component.VEvent)
	 */
	@Override
	public Calendar wrapEventInCalendar(VEvent event) {
		Calendar calendar = super.wrapEventInCalendar(event);
		if(isExplicitSetTimeZone() && this._timeZone != null) {
			calendar.getComponents().add(this._timeZone.getVTimeZone());
		}
		
		return calendar;
	}

}
