// ------------------------------------------------------------------------------
// Copyright (c) 2014 Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// ------------------------------------------------------------------------------

package com.microsoft.live;

import java.util.Iterator;
import java.util.LinkedList;

import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;

/**
 * Class for building URIs. The most useful benefit of this class is its query parameter
 * management. It stores all the query parameters in a LinkedList, so parameters can 
 * be looked up, removed, and added easily.
 */
class UriBuilder {
    
    public static class QueryParameter {
        private final String key;
        private final String value;
        
        /**
         * Constructs a query parameter with no value (e.g., download).
         * 
         * @param key
         */
        public QueryParameter(String key) {
            assert key != null;
            
            this.key = key;
            this.value = null;
        }
        
        public QueryParameter(String key, String value) {
            assert key != null;
            assert value != null;
            
            this.key = key;
            this.value = value;
        }
        
        public String getKey() {
            return this.key;
        }
        
        public String getValue() {
            return this.value;
        }
        
        public boolean hasValue() {
            return this.value != null;
        }
        
        @Override
        public String toString() {
            if (this.hasValue()) {
                return this.key + "=" + this.value;
            }
            
            return this.key;
        }
    }
    
    private static final String EQUAL = "=";
    private static final String AMPERSAND = "&";
    private static final char FORWARD_SLASH = '/';
    
    private String scheme;
    private String host;
    private StringBuilder path;
    
    private final LinkedList<QueryParameter> queryParameters;
    
    /**
     * Constructs a new UriBuilder from the given Uri.
     * 
     * @return a new Uri Builder based off the given Uri.
     */
    public static UriBuilder newInstance(Uri uri) {
        return new UriBuilder().scheme(uri.getScheme())
                               .host(uri.getHost())
                               .path(uri.getPath())
                               .query(uri.getQuery());
    }
    
    public UriBuilder() {
        this.queryParameters = new LinkedList<QueryParameter>();
    }
    
    /**
     * Appends a new query parameter to the UriBuilder's query string.
     * 
     * (e.g., appendQueryParameter("k1", "v1") when UriBuilder's query string is
     * k2=v2&k3=v3 results in k2=v2&k3=v3&k1=v1).
     * 
     * @param key Key of the new query parameter.
     * @param value Value of the new query parameter.
     * @return this UriBuilder object. Useful for chaining.
     */
    public UriBuilder appendQueryParameter(String key, String value) {
        assert key != null;
        assert value != null;
        
        this.queryParameters.add(new QueryParameter(key, value));
        
        return this;
    }

    /**
     * Appends the given query string on to the existing UriBuilder's query parameters.
     * 
     * (e.g., UriBuilder's queryString k1=v1&k2=v2 and given queryString k3=v3&k4=v4, results in
     * k1=v1&k2=v2&k3=v3&k4=v4).
     * 
     * @param queryString Key-Value pairs separated by & and = (e.g., k1=v1&k2=v2&k3=k3).
     * @return this UriBuilder object. Useful for chaining.
     */
    public UriBuilder appendQueryString(String queryString) {
        if (queryString == null) {
            return this;
        }
        
        String[] pairs = TextUtils.split(queryString, UriBuilder.AMPERSAND); 
        for(String pair : pairs) {
            String[] splitPair = TextUtils.split(pair, UriBuilder.EQUAL);
            if (splitPair.length == 2) {
                String key = splitPair[0];
                String value = splitPair[1];
                
                this.queryParameters.add(new QueryParameter(key, value));
            } else if (splitPair.length == 1){
                String key = splitPair[0];
            
                this.queryParameters.add(new QueryParameter(key));
            } else {
                Log.w("com.microsoft.live.UriBuilder", "Invalid query parameter: " + pair);
            }
        }
        
        return this;
    }

    /**
     * Appends the given path to the UriBuilder's current path.
     * 
     * @param path The path to append onto this UriBuilder's path.
     * @return this UriBuilder object. Useful for chaining.
     */
    public UriBuilder appendToPath(String path) {
        assert path != null;
        
        if (this.path == null) {
            this.path = new StringBuilder(path);
        } else {
            boolean endsWithSlash = TextUtils.isEmpty(this.path) ? false :
                    this.path.charAt(this.path.length() - 1) == UriBuilder.FORWARD_SLASH;
            boolean pathIsEmpty = TextUtils.isEmpty(path);
            boolean beginsWithSlash =
                    pathIsEmpty ? false : path.charAt(0) == UriBuilder.FORWARD_SLASH;
            
            if (endsWithSlash && beginsWithSlash) {
                if (path.length() > 1) {
                    this.path.append(path.substring(1));
                    
                }
            } else if (!endsWithSlash && !beginsWithSlash) {
                if (!pathIsEmpty) {
                    this.path.append(UriBuilder.FORWARD_SLASH).append(path);
                }
            } else {
                this.path.append(path);
            }
        }
        
        return this;
    }

    /**
     * Builds the Uri by converting into a android.net.Uri object.
     * 
     * @return a new android.net.Uri defined by what was given to the builder.
     */
    public Uri build() {
        return new Uri.Builder().scheme(this.scheme)
                                .authority(this.host)
                                .path(this.path == null ? "" : this.path.toString())
                                .encodedQuery(TextUtils.join("&", this.queryParameters))
                                .build();
    }

    /**
     * Sets the host part of the Uri.
     * 
     * @return this UriBuilder object. Useful for chaining.
     */
    public UriBuilder host(String host) {
        assert host != null;
        this.host = host;
        
        return this;
    }
    
    /**
     * Sets the path and removes any previously existing path.
     * 
     * @param path The path to set on this UriBuilder.
     * @return this UriBuilder object. Useful for chaining.
     */
    public UriBuilder path(String path) {
        assert path != null;
        this.path = new StringBuilder(path);
        
        return this;
    }

    /**
     * Takes a query string and puts it in the Uri Builder's query string removing
     * any existing query parameters.
     * 
     * @param queryString Key-Value pairs separated by & and = (e.g., k1=v1&k2=v2&k3=k3).
     * @return this UriBuilder object. Useful for chaining.
     */
    public UriBuilder query(String queryString) {
        this.queryParameters.clear();
        
        return this.appendQueryString(queryString);
    }
    
    /**
     * Removes all query parameters from the UriBuilder that has the given key.
     * 
     * (e.g., removeQueryParametersWithKey("k1") when UriBuilder's query string of k1=v1&k2=v2&k1=v3
     * results in k2=v2).
     * 
     * @param key Query parameter's key to remove
     * @return this UriBuilder object. Useful for chaining.
     */
    public UriBuilder removeQueryParametersWithKey(String key) {
        // There could be multiple query parameters with this key and
        // we want to remove all of them.
        Iterator<QueryParameter> it = this.queryParameters.iterator();
        
        while (it.hasNext()) {
            QueryParameter qp = it.next();
            if (qp.getKey().equals(key)) {
                it.remove();
            }
        }
        
        return this;
    }

    /**
     * Sets the scheme part of the Uri.
     * 
     * @return this UriBuilder object. Useful for chaining.
     */
    public UriBuilder scheme(String scheme) {
        assert scheme != null;
        this.scheme = scheme;
        
        return this;
    }

    /**
     * Returns the URI in string format (e.g., http://foo.com/bar?k1=v2).
     */
    @Override
    public String toString() {
        return this.build().toString();
    }
}
