/**
 * Copyright (c) 2011-2021, JFXtras
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *    Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *    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.
 *    Neither the name of the organization nor the
 *       names of its 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 JFXTRAS 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 jfxtras.icalendarfx.properties;

import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.Temporal;
import java.util.Collection;
import java.util.List;

import jfxtras.icalendarfx.parameters.TimeZoneIdentifierParameter;
import jfxtras.icalendarfx.parameters.VParameterElement;
import jfxtras.icalendarfx.parameters.ValueParameter;
import jfxtras.icalendarfx.properties.PropBaseDateTime;
import jfxtras.icalendarfx.properties.PropDateTime;
import jfxtras.icalendarfx.properties.VPropertyBase;
import jfxtras.icalendarfx.properties.ValueType;
import jfxtras.icalendarfx.properties.component.relationship.RecurrenceId;
import jfxtras.icalendarfx.properties.component.time.DateTimeEnd;
import jfxtras.icalendarfx.properties.component.time.DateTimeStart;

/**
 * Abstract class for all non-UTC date-time classes
 * Contains the time zone identifier parameter
 * 
 * @author David Bal
 *
 * @param <U> - implementation class
 * @see DateTimeStart
 * @see DateTimeEnd
 * @see RecurrenceId
 */
public abstract class PropBaseDateTime<T,U> extends VPropertyBase<T,U> implements PropDateTime<T>
{
    // reference to property value if T is not instance of Collection, or an element if T is a Collection
    private Object myElement;
    
    /**
     * TZID
     * Time Zone Identifier
     * To specify the identifier for the time zone definition for
     * a time component in the property value.
     * 
     * Examples:
     * DTSTART;TZID=America/New_York:19980119T020000
     */
    @Override
    public TimeZoneIdentifierParameter getTimeZoneIdentifier() { return timeZoneIdentifier; }
    private TimeZoneIdentifierParameter timeZoneIdentifier;
    @Override
    public void setTimeZoneIdentifier(TimeZoneIdentifierParameter timeZoneIdentifier)
    {
        if ((myElement == null) || (myElement instanceof ZonedDateTime))
        {
        	orderChild(this.timeZoneIdentifier, timeZoneIdentifier);
            this.timeZoneIdentifier = timeZoneIdentifier;
        } else
        {
            throw new DateTimeException(VParameterElement.TIME_ZONE_IDENTIFIER.name() + " can't be set for date-time of type " + getValue().getClass().getSimpleName());
        }
    }
    public void setTimeZoneIdentifier(String value)
    {
    	setTimeZoneIdentifier(TimeZoneIdentifierParameter.parse(value));
	}
    public void setTimeZoneIdentifier(ZoneId zone)
    {
    	setTimeZoneIdentifier(new TimeZoneIdentifierParameter(zone));
	}
    public U withTimeZoneIdentifier(TimeZoneIdentifierParameter timeZoneIdentifier)
    {
    	setTimeZoneIdentifier(timeZoneIdentifier);
    	return (U) this;
	}
    public U withTimeZoneIdentifier(ZoneId zone)
    {
    	setTimeZoneIdentifier(zone);
    	return (U) this;
	}
    public U withTimeZoneIdentifier(String content)
    {
    	setTimeZoneIdentifier(content);
    	return (U) this;
	}        
    
    /*
     * CONSTRUCTORS
     */
    protected PropBaseDateTime()
    {
        super();
    }
    
    public PropBaseDateTime(T temporal)
    {
        super(temporal);
    }
    
    public PropBaseDateTime(PropBaseDateTime<T,U> source)
    {
        super(source);
    }
    
    /**
     * append time zone to front of time for parsing in DATE_ZONED_DATE_TIME parse method
     * @see ValueType
     */
    @Override
	protected String modifiedValue()
    {
        String timeZone = (getTimeZoneIdentifier() != null) ? "[" + getTimeZoneIdentifier().getValue().toString() + "]" : "";
    	String modifiedValue = super.modifiedValue();
		return (modifiedValue != null) ? timeZone + modifiedValue : null;
	}

    @Override
    public void setValue(T value)
    {
        super.setValue(value);
        if (value instanceof Collection)
        {
            Collection<?> collection = (Collection<?>) value;
            myElement = (collection.isEmpty()) ? null : collection.iterator().next();
        } else if (value instanceof Temporal)
        {
            myElement = value;
        } else
        {
            throw new DateTimeException("Unsupported type:" + value.getClass().getSimpleName());            
        }

        if (myElement != null)
        {
            if (myElement instanceof ZonedDateTime)
            {
                ZoneId zone = ((ZonedDateTime) myElement).getZone();
                if (! zone.equals(ZoneId.of("Z")))
                {
                    if (getValueType() != null && getValueType().getValue() == ValueType.DATE)
                	{
                    	setValueType((ValueParameter) null); // reset value type if previously set to DATE
                	}
                    if (getTimeZoneIdentifier() == null)
                    {
                    	setTimeZoneIdentifier(new TimeZoneIdentifierParameter(zone));
                    }
                }
            } else if ((myElement instanceof LocalDateTime) || (myElement instanceof LocalDate))
            {
                if (getTimeZoneIdentifier() != null)
                {
                    throw new DateTimeException("Only ZonedDateTime is permitted when specifying a Time Zone Identifier (" + myElement.getClass().getSimpleName() + ")");                            
                }
                if (getValueType() == null)
                {
	                if (myElement instanceof LocalDate)
	                {
	                    setValueType(ValueType.DATE); // must set value parameter to force output of VALUE=DATE
	                } else
	                {
	                    if (getValueType() != null && getValueType().getValue() == ValueType.DATE) setValueType((ValueParameter) null); // reset value type if previously set to DATE
	                }
                }
            } else
            {
                throw new DateTimeException("Unsupported Temporal type:" + value.getClass().getSimpleName());            
            }
        }
    }
    
    @Override
    public List<String> errors()
    {
        List<String> errors = super.errors();
        if (getValue() != null && getValueType() != null)
        {
            if (getValue() instanceof LocalDate)
            {
                if (getValueType().getValue() != ValueType.DATE)
                {
                    errors.add(name() + "'s value (" + getValue() + ") doesn't match its value type (" + getValueType().getValue()
                            + ").  For that value, the required value type is " + ValueType.DATE);
                }
            } else if (getValue() instanceof LocalDateTime || getValue() instanceof ZonedDateTime)
            {
                if (getValueType().getValue() != ValueType.DATE_TIME)
                {
                    errors.add(name() + "'s value (" + getValue() + ") doesn't match its value type (" + getValueType().getValue()
                            + ").  For that value, the required value type is " + ValueType.DATE_TIME);
                }                
            }
        }
        return errors;
    }

}
