/**
 * Copyright (c) 2012, Ben Fortuna
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  o Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 *  o Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 *  o Neither the name of Ben Fortuna nor the names of any other contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package net.fortuna.ical4j.model;

import java.io.Serializable;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.StringTokenizer;

import net.fortuna.ical4j.model.parameter.Value;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;

/**
 * $Id$ [23-Apr-2004]
 *
 * Defines a list of iCalendar dates. If no value type is specified a list
 * defaults to DATE-TIME instances.
 * @author Ben Fortuna
 */
public class DateList implements List, Serializable {

	private static final long serialVersionUID = -3700862452550012357L;

	private final Value type;
    
    private final List dates;

    private TimeZone timeZone;
    
    private boolean utc;

    /**
     * Default constructor.
     */
    public DateList() {
    	this(false);
    }

    public DateList(final boolean unmodifiable) {
    
        this.type = Value.DATE_TIME;
        if (unmodifiable) {
        	dates = Collections.EMPTY_LIST;
        }
        else {
            dates = new ArrayList();
        }
    }

    /**
     * @param aType the type of dates contained by the instance
     */
    public DateList(final Value aType) {
        this(aType, null);
    }
    
    /**
     * Default constructor.
     * 
     * @param aType
     *            specifies the type of dates (either date or date-time)
     * @param timezone the timezone to apply to dates contained by the instance
     */
    public DateList(final Value aType, final TimeZone timezone) {
        if (aType == null) {
            this.type = Value.DATE_TIME;
        } else {
            this.type = aType;
        }
        this.timeZone = timezone;
        dates = new ArrayList();
    }

    /**
     * @param aValue a string representation of a date list
     * @param aType the date types contained in the instance
     * @throws ParseException where the specified string is not a valid date list
     */
    public DateList(final String aValue, final Value aType) throws ParseException {
        this(aValue, aType, null);
    }
    
    /**
     * Parses the specified string representation to create a list of dates.
     * 
     * @param aValue
     *            a string representation of a list of dates
     * @param aType
     *            specifies the type of dates (either date or date-time)
     * @param timezone the timezone to apply to contained dates
     * @throws ParseException
     *             if an invalid date representation exists in the date list
     *             string
     */
    public DateList(final String aValue, final Value aType, final TimeZone timezone)
            throws ParseException {
    	
        this(aType, timezone);
        final StringTokenizer t = new StringTokenizer(aValue, ",");
        while (t.hasMoreTokens()) {
            if (Value.DATE.equals(type)) {
                add((Object) new Date(t.nextToken()));
            }
            else {
                add((Object) new DateTime(t.nextToken(), timezone));
            }
        }
    }
    
    /**
     * Constructs a new date list of the specified type containing
     * the dates in the specified list.
     * @param list a list of dates to include in the new list
     * @param type the type of the new list
     */
    public DateList(final DateList list, final Value type) {
        if (!Value.DATE.equals(type) && !Value.DATE_TIME.equals(type)) {
            throw new IllegalArgumentException(
                    "Type must be either DATE or DATE-TIME");
        }
        
        this.type = type;
        dates = new ArrayList();
        
        if (Value.DATE.equals(type)) {
            for (final Iterator i = list.iterator(); i.hasNext();) {
                add(new Date((Date) i.next()));
            }
        }
        else {
            for (final Iterator i = list.iterator(); i.hasNext();) {
                add(new DateTime((Date) i.next()));
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public final String toString() {
        final StringBuffer b = new StringBuffer();
        for (final Iterator i = iterator(); i.hasNext();) {
            /*
             * if (type != null && Value.DATE.equals(type)) {
             * b.append(DateFormat.getInstance().format((Date) i.next())); }
             * else { b.append(DateTimeFormat.getInstance().format((Date)
             * i.next(), isUtc())); }
             */
            b.append(i.next());
            if (i.hasNext()) {
                b.append(',');
            }
        }
        return b.toString();
    }

    /**
     * Add a date to the list. The date will be updated to reflect the
     * timezone of this list.
     * @param date the date to add
     * @return true
     * @see java.util.List#add(Object)
     */
    public final boolean add(final Date date) {
        if (date instanceof DateTime) {
            if (isUtc()) {
                ((DateTime) date).setUtc(true);
            }
            else {
                ((DateTime) date).setTimeZone(getTimeZone());
            }
        }
        else if (!Value.DATE.equals(getType())) {
            final DateTime dateTime = new DateTime(date);
            dateTime.setTimeZone(getTimeZone());
            return add((Object) dateTime);
        }
        return add((Object) date);
    }
    
    /**
     * Overrides superclass to throw an <code>IllegalArgumentException</code>
     * Where argument is not a <code>net.fortuna.ical4j.model.Date</code>.
     * @param date the date to add
     * @return true if the object was added, otherwise false
     * @see java.util.List#add(Object)
     */
    public final boolean add(final Object date) {
        if (!(date instanceof Date)) {
            throw new IllegalArgumentException("Argument not a " + Date.class.getName());
        }
        return dates.add(date);
    }

    /**
     * Remove a date from the list.
     * 
     * @param date
     *            the date to remove
     * @return true if the list contained the specified date
     * @see List#remove(java.lang.Object)
     */
    public final boolean remove(final Date date) {
        return remove((Object) date);
    }

    /**
     * Returns the VALUE parameter specifying the type of dates (ie. date or
     * date-time) stored in this date list.
     * 
     * @return Returns a Value parameter.
     */
    public final Value getType() {
        return type;
    }

    /**
     * Indicates whether this list is in local or UTC format. This property will
     * have no affect if the type of the list is not DATE-TIME.
     * 
     * @return Returns true if in UTC format, otherwise false.
     */
    public final boolean isUtc() {
        return utc;
    }

    /**
     * Sets whether this list is in UTC or local time format.
     * 
     * @param utc
     *            The utc to set.
     */
    public final void setUtc(final boolean utc) {
        if (!Value.DATE.equals(type)) {
            for (final Iterator i = iterator(); i.hasNext();) {
                ((DateTime) i.next()).setUtc(utc);
            }
        }
        this.timeZone = null;
        this.utc = utc;
    }
    
    /**
     * Applies the specified timezone to all dates in the list.
     * All dates added to this list will also have this timezone
     * applied.
     * @param timeZone a timezone to apply to contained dates
     */
    public final void setTimeZone(final TimeZone timeZone) {
        if (!Value.DATE.equals(type)) {
            for (final Iterator i = iterator(); i.hasNext();) {
                ((DateTime) i.next()).setTimeZone(timeZone);
            }
        }
        this.timeZone = timeZone;
        this.utc = false;
    }

    /**
     * @return Returns the timeZone.
     */
    public final TimeZone getTimeZone() {
        return timeZone;
    }

	public final void add(int arg0, Object arg1) {
		dates.add(arg0, arg1);
	}

	public final boolean addAll(Collection arg0) {
		return dates.addAll(arg0);
	}

	public final boolean addAll(int arg0, Collection arg1) {
		return dates.addAll(arg0, arg1);
	}

	public final void clear() {
		dates.clear();
	}

	public final boolean contains(Object o) {
		return dates.contains(o);
	}

	public final boolean containsAll(Collection arg0) {
		return dates.containsAll(arg0);
	}

	public final Object get(int index) {
		return dates.get(index);
	}

	public final int indexOf(Object o) {
		return dates.indexOf(o);
	}

	public final boolean isEmpty() {
		return dates.isEmpty();
	}

	public final Iterator iterator() {
		return dates.iterator();
	}

	public final int lastIndexOf(Object o) {
		return dates.lastIndexOf(o);
	}

	public final ListIterator listIterator() {
		return dates.listIterator();
	}

	public final ListIterator listIterator(int index) {
		return dates.listIterator(index);
	}

	public final Object remove(int index) {
		return dates.remove(index);
	}

	public final boolean remove(Object o) {
		return dates.remove(o);
	}

	public final boolean removeAll(Collection arg0) {
		return dates.removeAll(arg0);
	}

	public final boolean retainAll(Collection arg0) {
		return dates.retainAll(arg0);
	}

	public final Object set(int arg0, Object arg1) {
		return dates.set(arg0, arg1);
	}

	public final int size() {
		return dates.size();
	}

	public final List subList(int fromIndex, int toIndex) {
		return dates.subList(fromIndex, toIndex);
	}

	public final Object[] toArray() {
		return dates.toArray();
	}

	public final Object[] toArray(Object[] arg0) {
		return dates.toArray(arg0);
	}
	
	public final boolean equals(Object obj) {
		if (!getClass().isAssignableFrom(obj.getClass())) {
			return false;
		}
		final DateList rhs = (DateList) obj;
		return new EqualsBuilder().append(dates, rhs.dates)
			.append(type, rhs.type)
			.append(timeZone, rhs.timeZone)
			.append(utc, utc)
			.isEquals();
	}
	
	public final int hashCode() {
		return new HashCodeBuilder().append(dates)
			.append(type)
			.append(timeZone)
			.append(utc)
			.toHashCode();
	}
}
