/* Copyright (c) 2006-2008 Google Inc.
 *
 * 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.
*/
/* Change history
* Oct 13 2008  Joe Feser       joseph.feser@gmail.com
* Converted ArrayLists and other .NET 1.1 collections to use Generics
* Combined IExtensionElement and IExtensionElementFactory interfaces
* 
*/
using System;
using System.Xml;
using System.IO; 
using System.Collections;
using Google.GData.Client;
using Google.GData.Extensions;

namespace Google.GData.Calendar {


    // a few new subelements

    //////////////////////////////////////////////////////////////////////
    /// <summary>subelements definition for the event entry thar are calendar specific
    /// </summary> 
    //////////////////////////////////////////////////////////////////////
    public class GCalNameTable
    {
        /// <summary>syncEvent</summary>
        public const string XmlSyncEventElement = "syncEvent"; 
        /// <summary>sequence element</summary>
        public const string XmlSequenceElement = "sequence"; 
        /// <summary>uid element</summary>
        public const string XmlUidElement = "uid"; 
    }
    //end of public class GCalEventTable


     /// <summary>
    /// Indicates whether this is a sync scenario where we allow setting the gCal:uid, the gCal:sequence, 
    /// and the organizer of an event. This element makes sense only when inserting and updating
    ///  events. This element should primarily be used in a sync scenario.
    /// </summary>
    public class GCalSyncEvent : SimpleAttribute
    {
        /// <summary>
        /// default calendar access level constructor
        /// </summary>        
        public GCalSyncEvent()
            : base(GCalNameTable.XmlSyncEventElement, GDataParserNameTable.gCalPrefix, 
              GDataParserNameTable.NSGCal)
        {
        }

        /// <summary>
        /// default calendar acccess level
        ///  constructor with an initial value
        /// </summary>
        /// <param name="initValue"></param>
        public GCalSyncEvent(string initValue)
            : base(GCalNameTable.XmlSyncEventElement, GDataParserNameTable.gCalPrefix, 
            GDataParserNameTable.NSGCal, initValue)
        {
        }
    }

     /// <summary>
    /// Indicates the globally unique identifier (UID) of the event as defined in Section 4.8.4.7 of RFC 2445.
    /// </summary>
    public class GCalUid : SimpleAttribute
    {
        /// <summary>
        /// default calendar access level constructor
        /// </summary>        
        public GCalUid()
            : base(GCalNameTable.XmlUidElement, GDataParserNameTable.gCalPrefix, 
              GDataParserNameTable.NSGCal)
        {
        }

        /// <summary>
        /// default calendar acccess level
        ///  constructor with an initial value
        /// </summary>
        /// <param name="initValue"></param>
        public GCalUid(string initValue)
            : base(GCalNameTable.XmlUidElement, GDataParserNameTable.gCalPrefix, 
            GDataParserNameTable.NSGCal, initValue)
        {
        }
    }


    /// <summary>
    /// Indicates the revision sequence number of the event as defined in Section 4.8.7.4 of RFC 2445. Must be non-negative.
    /// </summary>
    public class GCalSequence : SimpleAttribute
    {
        /// <summary>
        /// default calendar access level constructor
        /// </summary>        
        public GCalSequence()
            : base(GCalNameTable.XmlSequenceElement, GDataParserNameTable.gCalPrefix, 
              GDataParserNameTable.NSGCal)
        {
        }

        /// <summary>
        /// default calendar acccess level
        ///  constructor with an initial value
        /// </summary>
        /// <param name="initValue"></param>
        public GCalSequence(string initValue)
            : base(GCalNameTable.XmlSequenceElement, GDataParserNameTable.gCalPrefix, 
            GDataParserNameTable.NSGCal, initValue)
        {
        }
    }


    //////////////////////////////////////////////////////////////////////
    /// <summary>
    /// Entry API customization class for defining entries in an Event feed.
    /// </summary>
    //////////////////////////////////////////////////////////////////////
    public class EventEntry : AbstractEntry
    {


        /// <summary>
        /// Category used to label entries that contain Event extension data.
        /// </summary>
        public static AtomCategory EVENT_CATEGORY =
        new AtomCategory(GDataParserNameTable.Event, new AtomUri(BaseNameTable.gKind));

        /// <summary>
        /// Constructs a new EventEntry instance with the appropriate category
        /// to indicate that it is an event.
        /// </summary>
        public EventEntry()
        : base()
        {
            Categories.Add(EVENT_CATEGORY);
            addEventEntryExtensions();
        }

        /// <summary>
        /// Constructs a new EventEntry instance with provided data.
        /// </summary>
        /// <param name="title">The descriptive title of the event ("What" in the UI)</param>
        public EventEntry(string title) : this()
        {
            this.Title.Text = title;
        }

        /// <summary>
        /// Constructs a new EventEntry instance with provided data.
        /// </summary>
        /// <param name="title">The descriptive title of the event ("What" in the UI)</param>
        /// <param name="description">A longer description of the event 
        /// ("Description" in the UI)</param>
        public EventEntry(string title, string description) : this(title)
        {
            this.Content.Content = description;
        }

        /// <summary>
        /// Constructs a new EventEntry instance with provided data.
        /// </summary>
        /// <param name="title">The descriptive title of the event ("What" in the UI)</param>
        /// <param name="description">A longer description of the event 
        /// ("Description" in the UI)</param>
        /// <param name="location">The location of the event ("Where" in the UI)</param>
        public EventEntry(string title, string description, string location)
            : this(title, description)
        {
            Where eventLocation = new Where();
            eventLocation.ValueString = location;
            this.Locations.Add(eventLocation);
            
        }

        /// <summary>
        ///  helper method to add extensions to the evententry
        /// </summary>
        private void addEventEntryExtensions()
        {
            this.AddExtension(new Reminder());
            this.AddExtension(new Where());
            this.AddExtension(new Who());
            this.AddExtension(new When());
            this.AddExtension(new OriginalEvent());
            this.AddExtension(new SendNotifications());
            this.AddExtension(new Transparency());
            this.AddExtension(new Visibility());
            this.AddExtension(new EventStatus());
            this.AddExtension(new RecurrenceException());
            this.AddExtension(new Comments());
            this.AddExtension(new ExtendedProperty());
            this.AddExtension(new Recurrence());
            this.AddExtension(new GCalSequence());
            this.AddExtension(new GCalUid());
            this.AddExtension(new GCalSyncEvent());
        }

        /// <summary>
        /// Constructs a new EventStatus  instance
        /// </summary>
        public class EventStatus : EnumConstruct
        {
            /// <summary>
            ///  default constructor
            /// </summary>
            public EventStatus()
            : base(GDataParserNameTable.XmlEventStatusElement)
            {
            }

            /// <summary>
            ///  EventStatus constructor 
            /// </summary>
            /// <param name="value">indicates the default status</param>
            public EventStatus(string value)
            : base(GDataParserNameTable.XmlEventStatusElement, value)
            {
            }
            /// <summary>string constant for a confirmed event</summary>
            public const string CONFIRMED_VALUE = BaseNameTable.gNamespacePrefix + "event.confirmed";
            /// <summary>string constant for a tentative accepted event</summary>
            public const string TENTATIVE_VALUE = BaseNameTable.gNamespacePrefix + "event.tentative";
            /// <summary>string constant for a cancelled event</summary>
            public const string CANCELED_VALUE = BaseNameTable.gNamespacePrefix + "event.canceled";

            /// <summary>EventStatus constant for a confirmed event</summary>
            public static EventStatus CONFIRMED = new EventStatus(CONFIRMED_VALUE);
            /// <summary>EventStatus constant for a tentative event</summary>
            public static EventStatus TENTATIVE = new EventStatus(TENTATIVE_VALUE);
            /// <summary>EventStatus constant for a Cancelled  event</summary>
            public static EventStatus CANCELED = new EventStatus(CANCELED_VALUE);
        }

        /// <summary>
        /// Visibility class indicates the visibility of an eventNode
        /// </summary>
        public class Visibility : EnumConstruct
        {
            /// <summary>
            ///  default constructor
            /// </summary>
            public Visibility()
            : base(GDataParserNameTable.XmlVisibilityElement)
            {
            }

            /// <summary>
            ///  Visibility constructor with a string to indicate default value
            /// </summary>
            /// <param name="value">the default visibility value</param>
            public Visibility(string value)
            : base(GDataParserNameTable.XmlVisibilityElement, value)
            {
            }

            /// <summary>string constant for the default visibility value</summary>
            public const string DEFAULT_VALUE = BaseNameTable.gNamespacePrefix + "event.default";
            /// <summary>string constant for the public visibility value</summary>
            public const string PUBLIC_VALUE = BaseNameTable.gNamespacePrefix + "event.public";
            /// <summary>string constant for the confidential visibility value</summary>
            public const string CONFIDENTIAL_VALUE = BaseNameTable.gNamespacePrefix + "event.confidential";
            /// <summary>string constant for the private visibility value</summary>
            public const string PRIVATE_VALUE = BaseNameTable.gNamespacePrefix + "event.private";

            /// <summary>object constant for the private visibility value</summary>
            public static Visibility DEFAULT = new Visibility(DEFAULT_VALUE);
            /// <summary>object constant for the private visibility value</summary>
            public static Visibility PUBLIC = new Visibility(PUBLIC_VALUE);
            /// <summary>object constant for the private visibility value</summary>
            public static Visibility CONFIDENTIAL = new Visibility(CONFIDENTIAL_VALUE);
            /// <summary>object constant for the private visibility value</summary>
            public static Visibility PRIVATE = new Visibility(PRIVATE_VALUE);
        }

        /// <summary>
        ///  the Transparency of an event class
        /// </summary>
        public class Transparency : EnumConstruct
        {
            /// <summary>
            ///  default constructor
            /// </summary>
            public Transparency()
            : base(GDataParserNameTable.XmlTransparencyElement)
            {
            }

            /// <summary>
            ///  constructor with a default string value
            /// </summary>
            /// <param name="value">transparency value</param>
            public Transparency(string value)
            : base(GDataParserNameTable.XmlTransparencyElement, value)
            {
            }

            /// <summary>string constant for the opaque transparency value</summary>
            public const string OPAQUE_VALUE = BaseNameTable.gNamespacePrefix + "event.opaque";
            /// <summary>string constant for the transparent trancparency value</summary>
            public const string TRANSPARENT_VALUE = BaseNameTable.gNamespacePrefix + "event.transparent";

            /// <summary>object constant for the opaque transparency value</summary>
            public static Transparency OPAQUE = new Transparency(OPAQUE_VALUE);
            /// <summary>object constant for the transparent transparency value</summary>
            public static Transparency TRANSPARENT = new Transparency(TRANSPARENT_VALUE);
        }

        /// <summary>
        ///  indicates if an eventupdate should reissue notifications
        /// false by default
        /// </summary>
        public class SendNotifications : EnumConstruct
        {
            /// <summary>
            ///  default constructor
            /// </summary>
            public SendNotifications()
            : base(GDataParserNameTable.XmlSendNotificationsElement,
                   GDataParserNameTable.gCalPrefix,
                   GDataParserNameTable.NSGCal)
            {
            }

            /// <summary>
            ///  constructor with a default string value
            /// </summary>
            /// <param name="value">transparency value</param>
            public SendNotifications(string value)
            : base(GDataParserNameTable.XmlSendNotificationsElement,
                   GDataParserNameTable.gCalPrefix,
                   GDataParserNameTable.NSGCal, value)
            {
            }
        }

         /// <summary>
        ///  indicates if this new entry should be a quickadd
        /// false by default
        /// </summary>
        public class QuickAddElement : EnumConstruct
        {
            /// <summary>
            ///  default constructor
            /// </summary>
            public QuickAddElement()
            : base(GDataParserNameTable.XmlQuickAddElement,
                   GDataParserNameTable.gCalPrefix,
                   GDataParserNameTable.NSGCal)
            {
            }

            /// <summary>
            ///  constructor with a default string value
            /// </summary>
            /// <param name="value">transparency value</param>
            public QuickAddElement(string value)
            : base(GDataParserNameTable.XmlQuickAddElement, value)
            {
            }
        }



#region EventEntry Attributes

        private ExtensionCollection<When> times;
        private ExtensionCollection<Where> locations;
        private ExtensionCollection<Who> participants;
#endregion

#region Public Methods
        /// <summary>
        ///  property accessor for the WhenCollection
        /// </summary>
        public ExtensionCollection<When> Times
        {
            get 
            {
                if (this.times == null)
                {
                    this.times = new ExtensionCollection<When>(this);
                }
                return this.times;
            }
        }

        /// <summary>
        ///  property accessor for the WhereCollection
        /// </summary>
        public ExtensionCollection<Where> Locations
        {
            get 
            {
                if (this.locations == null)
                {
                    this.locations = new ExtensionCollection<Where>(this);
                }
                return this.locations;
            }
        }

        /// <summary>
        ///  property accessor for the whos in the event
        /// </summary>
        public ExtensionCollection<Who> Participants
        {
            get 
            {
                if (this.participants == null)
                {
                    this.participants = new ExtensionCollection<Who>(this); 
                }
                return this.participants;
            }
        }


        /// <summary>
        ///  property accessor for the Eventnotifications
        ///  set this to True for notfications to be send 
        /// </summary>
        public bool Notifications
        {
            get 
            { 
                SendNotifications n = FindExtension(GDataParserNameTable.XmlSendNotificationsElement,
                                     GDataParserNameTable.NSGCal) as SendNotifications;

                if (n == null)
                {
                    return false;
                }
                return n.Value == Utilities.XSDTrue; 
            }
            set
            {
                SendNotifications n = FindExtension(GDataParserNameTable.XmlSendNotificationsElement,
                                     GDataParserNameTable.NSGCal) as SendNotifications;
                if (value == true)
                {
                    if (n == null)
                    {
                        n = new SendNotifications(); 
                        ExtensionElements.Add(n);
                    }
                    n.Value = Utilities.XSDTrue;
                }
                else 
                {
                    if (n != null)
                    {
                        DeleteExtensions(GDataParserNameTable.XmlSendNotificationsElement,
                                     GDataParserNameTable.NSGCal);
                    }
                }
            }
        }


        /// <summary>
        ///  property accessor QuickAdd
        /// To create an event using Google Calendar's quick add feature, set the event 
        /// entry's content to the quick add string you'd like to use. Then add a 
        /// gCal:quickadd element with a value attribute set to true
        /// </summary>
        public bool QuickAdd
        {
            get 
            { 
                QuickAddElement q = FindExtension(GDataParserNameTable.XmlQuickAddElement,
                                     GDataParserNameTable.NSGCal) as QuickAddElement;

                if (q == null)
                {
                    return false;
                }
                return q.Value == Utilities.XSDTrue; 
            }
            set
            {
                QuickAddElement q = FindExtension(GDataParserNameTable.XmlQuickAddElement,
                                     GDataParserNameTable.NSGCal) as QuickAddElement;
                if (value == true)
                {
                    if (q == null)
                    {
                        q = new QuickAddElement(); 
                        ExtensionElements.Add(q);
                    }
                    q.Value = Utilities.XSDTrue;
                }
                else 
                {
                    if (q != null)
                    {
                        DeleteExtensions(GDataParserNameTable.XmlQuickAddElement,
                                     GDataParserNameTable.NSGCal);
                    }
                }
            }
        }



        /// <summary>
        ///  property accessor for the EventStatus
        /// </summary>
        public EventStatus Status
        {
            get 
            { 
                return FindExtension(GDataParserNameTable.XmlEventStatusElement,
                                     GDataParserNameTable.gNamespace) as EventStatus;
            }
            set
            {
                ReplaceExtension(GDataParserNameTable.XmlEventStatusElement,
                                     GDataParserNameTable.gNamespace, value);
            }
        }


        /// <summary>
        ///  property accessor for the Event Visibility 
        /// </summary>
        public Visibility EventVisibility
        {
            get 
            { 
                return FindExtension(GDataParserNameTable.XmlVisibilityElement,
                                     GDataParserNameTable.gNamespace) as Visibility;
            }
            set
            {
                ReplaceExtension(GDataParserNameTable.XmlVisibilityElement,
                                     GDataParserNameTable.gNamespace, value);
            }
        }

        /// <summary>
        ///  property accessor for the EventTransparency
        /// </summary>
        public Transparency EventTransparency
        {
            get 
            { 
                return FindExtension(GDataParserNameTable.XmlTransparencyElement,
                                     GDataParserNameTable.gNamespace) as Transparency;
            }
            set
            {
                ReplaceExtension(GDataParserNameTable.XmlTransparencyElement,
                                     GDataParserNameTable.gNamespace, value);
            }
        }

        /// <summary>
        ///  property accessor for the Recurrence
        /// </summary>
        public Recurrence Recurrence
        {
            get 
            { 
                return FindExtension(GDataParserNameTable.XmlRecurrenceElement,
                                     GDataParserNameTable.gNamespace) as Recurrence;
            }
            set
            {
                ReplaceExtension(GDataParserNameTable.XmlRecurrenceElement,
                                     GDataParserNameTable.gNamespace, value);
            }
        }

      /// <summary>
      ///  property accessor for the RecurrenceException
      /// </summary>
      public RecurrenceException RecurrenceException
      {
            get 
            { 
                return FindExtension(GDataParserNameTable.XmlRecurrenceExceptionElement,
                                     GDataParserNameTable.gNamespace) as RecurrenceException;
            }
            set
            {
                ReplaceExtension(GDataParserNameTable.XmlRecurrenceExceptionElement,
                                     GDataParserNameTable.gNamespace, value);
            }
      }

        /// <summary>
        /// property accessor for the OriginalEvent
        /// </summary>
        public OriginalEvent OriginalEvent
        {
            get 
            { 
                return FindExtension(GDataParserNameTable.XmlOriginalEventElement,
                                     GDataParserNameTable.gNamespace) as OriginalEvent;
            }
            set
            {
                ReplaceExtension(GDataParserNameTable.XmlOriginalEventElement,
                                     GDataParserNameTable.gNamespace, value);
            }
        }

        /// <summary>
        ///  property accessor for the Comments
        /// </summary>
        public Comments Comments
        {
            get 
            { 
                return FindExtension(GDataParserNameTable.XmlCommentsElement,
                                 GDataParserNameTable.gNamespace) as Comments;
            }
            set
            {
                ReplaceExtension(GDataParserNameTable.XmlCommentsElement,
                                     GDataParserNameTable.gNamespace, value);
            }
        }


        /// <summary>
        /// returns the first reminder of the Times collection
        /// </summary>
        /// <returns>When object for the reminder or NULL</returns>
        protected When GetFirstReminder()
        {
            return this.Times != null && this.Times.Count > 0 ? this.Times[0] : null; 
        }


        /// <summary>
        /// returns the FIRST reminder for backwards compatibility
        /// if set, will REMOVE all reminders, but this one (array of one)
        /// </summary>
        public Reminder Reminder 
        {
            get 
            {
                if (this.Reminders != null && this.Reminders.Count > 0)
                {
                    return this.Reminders[0];
                }
                return null;
            }
            set 
            {
                this.Reminders.Clear();
                if (value != null)
                {
                    this.Reminders.Add(value);
                }
            }
        }

        /// <summary>
        /// property accessor for the Reminder
        /// </summary>
        public ExtensionCollection<Reminder> Reminders
        {
            get 
            { 
                // if we are a recurrent event, reminder is on the entry/toplevel
                if (this.Recurrence != null)
                {
                    //TODO could not get the generic overload to work. we for now just copy the list a few times.
                    ExtensionList list = new ExtensionList(this);

                    FindExtensions(GDataParserNameTable.XmlReminderElement,
                                      BaseNameTable.gNamespace, list);

                    ExtensionCollection<Reminder> collection = new ExtensionCollection<Reminder>(this);
                    foreach (Reminder var in list)
                    {
                        collection.Add(var);
                    }

                    return collection;
                } 
                else
                {
                    // in the non recurrent case, it's on the first when element
                    When w = GetFirstReminder(); 
                    if (w != null)
                    {
                        return w.Reminders; 
                    }
                }
                return null; 
            }
        }

        /// <summary>
        /// as eventId is a commonly used part in the calendar world, we expose it
        /// here. In general the EventId is the last part of the AtomId
        /// </summary>
        public string EventId 
        {
            get 
            {
                string[] elements = this.Id.AbsoluteUri.Split(new char[] {'/'});
                if (elements != null && elements.Length > 0)
                {
                    return elements[elements.Length - 1];
                }
                return null;
            }
        }


        /// <summary>
        /// Property to retrieve/set an associated WebContentLink
        /// </summary>
        public WebContentLink WebContentLink
        {
            get
            {
                return this.Links.FindService(WebContentLink.WEB_CONTENT_REL, 
                    null) as WebContentLink;
            }
            set
            {
                this.Links.Add(value);
            }
        }


        /// <summary>
        ///  property accessor for the SyncEvent element
        /// </summary>
        public GCalSyncEvent  SyncEvent
        {
            get 
            { 
                return FindExtension(GCalNameTable.XmlSyncEventElement,
                                     GDataParserNameTable.NSGCal) as GCalSyncEvent;
            }
            set
            {
                ReplaceExtension(GCalNameTable.XmlSyncEventElement,
                                     GDataParserNameTable.NSGCal, value);
            }
        }

        /// <summary>
        ///  property accessor for the uid element
        /// </summary>
        public GCalUid  Uid
        {
            get 
            { 
                return FindExtension(GCalNameTable.XmlUidElement,
                                     GDataParserNameTable.NSGCal) as GCalUid;
            }
            set
            {
                ReplaceExtension(GCalNameTable.XmlUidElement,
                                     GDataParserNameTable.NSGCal, value);
            }
        }
                /// <summary>
        ///  property accessor for the SyncEvent element
        /// </summary>
        public GCalSequence  Sequence
        {
            get 
            { 
                return FindExtension(GCalNameTable.XmlSequenceElement,
                                     GDataParserNameTable.NSGCal) as GCalSequence;
            }
            set
            {
                ReplaceExtension(GCalNameTable.XmlSequenceElement,
                                     GDataParserNameTable.NSGCal, value);
            }
        }



#endregion

        /// <summary>
        /// this is the subclassing method for AtomBase derived 
        /// classes to overload what childelements should be created
        /// needed to create CustomLink type objects, like WebContentLink etc
        /// </summary>
        /// <param name="reader">The XmlReader that tells us what we are working with</param>
        /// <param name="parser">the parser is primarily used for nametable comparisons</param>
        /// <returns>AtomBase</returns>
        public override AtomBase CreateAtomSubElement(XmlReader reader, AtomFeedParser parser)
        {
            Object localname = reader.LocalName;

            if ((localname.Equals(parser.Nametable.Link)))
            {
                if (reader.GetAttribute(GDataParserNameTable.XmlAttributeRel) == 
                    WebContentLink.WEB_CONTENT_REL)
                {
                    return new WebContentLink(false);
                }
            }
            return base.CreateAtomSubElement(reader, parser);
            
        }
    }
}



