/* Copyright (c) 2006 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.
*/

using System;
using System.Globalization;
using System.Text;
using Google.GData.Client;

namespace Google.GData.Contacts {
    /// <summary>
    /// A subclass of FeedQuery, to create an Contacts query URI.
    /// Provides public properties that describe the different
    /// aspects of the URI, as well as a composite URI.
    /// The ContactsQuery supports the following GData parameters:
    /// The Contacts Data API supports the following standard Google Data API query parameters:
    /// Name              Description
    /// alt               The type of feed to return, such as atom (the default), rss, or json.
    /// max-results       The maximum number of entries to return. If you want to receive all of
    ///                   the contacts, rather than only the default maximum, you can specify a very 
    ///                   large number for max-results.
    /// start-index       The 1-based index of the first result to be retrieved (for paging).
    /// updated-min       The lower bound on entry update dates.
    /// 
    /// For more information about the standard parameters, see the Google Data APIs protocol reference document.
    /// In addition to the standard query parameters, the Contacts Data API supports the following parameters:
    /// 
    /// Name              Description
    /// orderby           Sorting criterion. The only supported value is lastmodified.
    /// showdeleted       Include deleted contacts in the returned contacts feed. 
    ///                   Deleted contacts are shown as entries that contain nothing but an 
    ///                   atom:id element and a gd:deleted element. 
    ///                   (Google retains placeholders for deleted contacts for 30 days after 
    ///                   deletion; during that time, you can request the placeholders 
    ///                   using the showdeleted query parameter.) Valid values are true or false.
    /// sortorder         Sorting order direction. Can be either ascending or descending.
    /// group	          Constrains the results to only the contacts belonging to the group specified. 
    ///                   Value of this parameter specifies group ID (see also: gContact:groupMembershipInfo).
    /// </summary> 
    public class GroupsQuery : FeedQuery {
        /// <summary>
        /// contacts group base URI 
        /// </summary>
        public const string groupsBaseUri = "https://www.google.com/m8/feeds/groups/";

        /// <summary>
        /// sort oder value for sorting by lastmodified
        /// </summary>
        public const string OrderByLastModified = "lastmodified";

        /// <summary>
        /// sort oder value for sorting ascending
        /// </summary>
        public const string SortOrderAscending = "ascending";

        /// <summary>
        /// sort oder value for sorting descending
        /// </summary>
        public const string SortOrderDescending = "ascending";

        /// <summary>
        /// base projection value
        /// </summary>
        public const string baseProjection = "base";
        /// <summary>
        /// thin projection value
        /// </summary>
        public const string thinProjection = "thin";
        /// <summary>
        /// property-key projection value
        /// </summary>
        public const string propertyProjection = "property-";
        /// <summary>
        /// full projection value
        /// </summary>
        public const string fullProjection = "full";

        private string orderBy;
        private bool showDeleted;
        private string sortOrder;

        /// <summary>
        /// base constructor
        /// </summary>
        public GroupsQuery()
            : base() {
        }

        /// <summary>
        /// base constructor, with initial queryUri
        /// </summary>
        /// <param name="queryUri">the query to use</param>
        public GroupsQuery(string queryUri)
            : base(queryUri) {
        }

        /// <summary>
        /// convenience method to create an URI based on a userID for a groups feed
        /// this returns a FULL projection by default
        /// </summary>
        /// <param name="userID"></param>
        /// <returns>string</returns>
        public static string CreateGroupsUri(string userID) {
            return CreateGroupsUri(userID, ContactsQuery.fullProjection);
        }

        /// <summary>
        /// convenience method to create an URI based on a userID for a groups feed
        /// </summary>
        /// <param name="userID">if the parameter is NULL, uses the default user</param>
        /// <param name="projection">the projection to use</param>
        /// <returns>string</returns>
        public static string CreateGroupsUri(string userID, string projection) {
            return ContactsQuery.groupsBaseUri + ContactsQuery.UserString(userID) + projection;
        }

        /// <summary>Sorting order direction. Can be either ascending or descending</summary> 
        /// <returns> </returns>
        public string SortOrder {
            get { return this.sortOrder; }
            set { this.sortOrder = value; }
        }

        /// <summary>Sorting criterion. The only supported value is lastmodified</summary>
        /// <returns> </returns>
        public string OrderBy {
            get { return this.orderBy; }
            set { this.orderBy = value; }
        }

        /// <summary>Include deleted contacts in the returned contacts feed. 
        /// Deleted contacts are shown as entries that contain nothing but
        /// an atom:id element and a gd:deleted element. (Google retains placeholders 
        /// for deleted contacts for 30 days after deletion; during that time, 
        /// you can request the placeholders using the showdeleted query
        /// parameter.) Valid values are true or false.</summary> 
        /// <returns> </returns>
        public bool ShowDeleted {
            get { return this.showDeleted; }
            set { this.showDeleted = value; }
        }

        /// <summary>
        /// helper to create the userstring for a query
        /// </summary>
        /// <param name="user">the user to encode, or NULL if default</param>
        /// <returns></returns>
        protected static string UserString(string user) {
            if (user == null) {
                return "default/";
            }
            return Utilities.UriEncodeReserved(user) + "/";
        }

        /// <summary>protected void ParseUri</summary> 
        /// <param name="targetUri">takes an incoming Uri string and parses all the properties out of it</param>
        /// <returns>throws a query exception when it finds something wrong with the input, otherwise returns a baseuri</returns>
        protected override Uri ParseUri(Uri targetUri) {
            base.ParseUri(targetUri);
            if (targetUri != null) {
                char[] deli = { '?', '&' };

                string source = HttpUtility.UrlDecode(targetUri.Query);
                TokenCollection tokens = new TokenCollection(source, deli);
                foreach (String token in tokens) {
                    if (token.Length > 0) {
                        char[] otherDeli = { '=' };
                        String[] parameters = token.Split(otherDeli, 2);
                        switch (parameters[0]) {
                            case "orderby":
                                this.OrderBy = parameters[1];
                                break;
                            case "sortorder":
                                this.SortOrder = parameters[1];
                                break;
                            case "showdeleted":
                                if (String.Compare("true", parameters[1], false, CultureInfo.InvariantCulture) == 0) {
                                    this.ShowDeleted = true;
                                }
                                break;
                        }
                    }
                }


            }
            return this.Uri;
        }

        /// <summary>Creates the partial URI query string based on all
        ///  set properties.</summary> 
        /// <returns> string => the query part of the URI </returns>
        protected override string CalculateQuery(string basePath) {
            string path = base.CalculateQuery(basePath);
            StringBuilder newPath = new StringBuilder(path, 2048);
            char paramInsertion = InsertionParameter(path);

            if (this.OrderBy != null && this.OrderBy.Length > 0) {
                newPath.Append(paramInsertion);
                newPath.AppendFormat(CultureInfo.InvariantCulture, "orderby={0}", Utilities.UriEncodeReserved(this.OrderBy));
                paramInsertion = '&';
            }

            if (this.SortOrder != null && this.SortOrder.Length > 0) {
                newPath.Append(paramInsertion);
                newPath.AppendFormat(CultureInfo.InvariantCulture, "sortorder={0}", Utilities.UriEncodeReserved(this.SortOrder));
                paramInsertion = '&';
            }
            if (this.ShowDeleted) {
                newPath.Append(paramInsertion);
                newPath.Append("showdeleted=true");
                paramInsertion = '&';
            }
            return newPath.ToString();
        }
    }

    /// <summary>
    /// A subclass of GroupsQuery, to create an Contacts query URI.
    /// Provides public properties that describe the different
    /// aspects of the URI, as well as a composite URI.
    /// The ContactsQuery supports the following GData parameters:
    /// Name              Description
    /// alt               The type of feed to return, such as atom (the default), rss, or json.
    /// max-results       The maximum number of entries to return. If you want to receive all of
    ///                   the contacts, rather than only the default maximum, you can specify a very 
    ///                   large number for max-results.
    /// start-index       The 1-based index of the first result to be retrieved (for paging).
    /// updated-min       The lower bound on entry update dates.
    /// 
    /// For more information about the standard parameters, see the Google Data APIs protocol reference document.
    /// In addition to the standard query parameters, the Contacts Data API supports the following parameters:
    /// 
    /// Name              Description
    /// orderby           Sorting criterion. The only supported value is lastmodified.
    /// showdeleted       Include deleted contacts in the returned contacts feed. 
    ///                   Deleted contacts are shown as entries that contain nothing but an 
    ///                   atom:id element and a gd:deleted element. 
    ///                   (Google retains placeholders for deleted contacts for 30 days after 
    ///                   deletion; during that time, you can request the placeholders 
    ///                   using the showdeleted query parameter.) Valid values are true or false.
    /// sortorder         Sorting order direction. Can be either ascending or descending.
    /// group	          Constrains the results to only the contacts belonging to the group specified. 
    ///                   Value of this parameter specifies group ID (see also: gContact:groupMembershipInfo).
    /// </summary> 
    public class ContactsQuery : GroupsQuery {
        /// <summary>
        /// contacts base URI 
        /// </summary>
        public const string contactsBaseUri = "https://www.google.com/m8/feeds/contacts/";

        private string group;

        /// <summary>
        /// base constructor
        /// </summary>
        public ContactsQuery()
            : base() {
        }

        /// <summary>
        /// base constructor, with initial queryUri
        /// </summary>
        /// <param name="queryUri">the query to use</param>
        public ContactsQuery(string queryUri)
            : base(queryUri) {
        }

        /// <summary>
        /// convenience method to create an URI based on a userID for a contacts feed
        /// this returns a FULL projection by default
        /// </summary>
        /// <param name="userID">if the parameter is NULL, uses the default user</param>
        /// <returns>string</returns>
        public static string CreateContactsUri(string userID) {
            return CreateContactsUri(userID, ContactsQuery.fullProjection);
        }

        /// <summary>
        /// convenience method to create an URI based on a userID for a contacts feed
        /// this returns a FULL projection by default
        /// </summary>
        /// <param name="userID">if the parameter is NULL, uses the default user</param>
        /// <param name="projection">the projection to use</param>
        /// <returns>string</returns>
        public static string CreateContactsUri(string userID, string projection) {
            return ContactsQuery.contactsBaseUri + UserString(userID) + projection;
        }

        /// <summary>Constrains the results to only the contacts belonging to the 
        /// group specified. Value of this parameter specifies group ID</summary> 
        /// <returns> </returns>
        public string Group {
            get { return this.group; }
            set { this.group = value; }
        }

        /// <summary>protected void ParseUri</summary> 
        /// <param name="targetUri">takes an incoming Uri string and parses all the properties out of it</param>
        /// <returns>throws a query exception when it finds something wrong with the input, otherwise returns a baseuri</returns>
        protected override Uri ParseUri(Uri targetUri) {
            base.ParseUri(targetUri);
            if (targetUri != null) {
                char[] deli = { '?', '&' };

                string source = HttpUtility.UrlDecode(targetUri.Query);
                TokenCollection tokens = new TokenCollection(source, deli);
                foreach (String token in tokens) {
                    if (token.Length > 0) {
                        char[] otherDeli = { '=' };
                        String[] parameters = token.Split(otherDeli, 2);
                        switch (parameters[0]) {
                            case "group":
                                this.Group = parameters[1];
                                break;
                        }
                    }
                }
            }
            return this.Uri;
        }

        /// <summary>Creates the partial URI query string based on all
        ///  set properties.</summary> 
        /// <returns> string => the query part of the URI </returns>
        protected override string CalculateQuery(string basePath) {
            string path = base.CalculateQuery(basePath);
            StringBuilder newPath = new StringBuilder(path, 2048);
            char paramInsertion = InsertionParameter(path);

            if (this.Group != null && this.Group.Length > 0) {
                newPath.Append(paramInsertion);
                newPath.AppendFormat(CultureInfo.InvariantCulture, "group={0}", Utilities.UriEncodeReserved(this.Group));
                paramInsertion = '&';
            }
            return newPath.ToString();
        }
    }
}

