/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.
 */
package org.apache.cxf.jaxrs.impl;

import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.ws.rs.core.Link;
import javax.ws.rs.core.Link.Builder;
import javax.ws.rs.core.UriBuilder;

import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.jaxrs.utils.HttpUtils;

public class LinkBuilderImpl implements Builder {
    private static final String DOUBLE_QUOTE = "\"";
    private UriBuilder ub;
    private URI baseUri;
    private Map<String, String> params = new HashMap<String, String>(6);
    
    @Override
    public Link build(Object... values) {
        URI resolvedLinkUri = getResolvedUri(values);
        return new LinkImpl(resolvedLinkUri, new HashMap<String, String>(params));
    }

    @Override
    public Link buildRelativized(URI requestUri, Object... values) {
        URI resolvedLinkUri = getResolvedUri(values);
        URI relativized = HttpUtils.relativize(requestUri, resolvedLinkUri);
        return new LinkImpl(relativized, new HashMap<String, String>(params));
    }

    private URI getResolvedUri(Object... values) {
        if (ub == null) {
            ub = new UriBuilderImpl();
            if (baseUri != null) {
                ub.uri(baseUri);    
            }
            
        }
        URI uri = ub.build(values);
                
        if (!uri.isAbsolute() && baseUri != null && baseUri.isAbsolute()) {
            UriBuilder linkUriBuilder = UriBuilder.fromUri(baseUri);
            return HttpUtils.resolve(linkUriBuilder, uri);    
        } else {
            return uri;
        }
    }
    
    @Override
    public Builder link(Link link) {
        ub = UriBuilder.fromLink(link);
        params.putAll(link.getParams());
        return this;
    }

    @Override
    public Builder link(String link) {
        
        link = link.trim();
        if (link.length() > 1 && link.startsWith("<")) {
            int index = link.lastIndexOf(">", link.length());
            if (index != -1) {
                String uri = link.substring(1, index);
                ub = UriBuilder.fromUri(uri);
                if (index + 1 == link.length()) {
                    link = "";
                } else {
                    link = link.substring(index + 1);
                }
            }
        }
        
        String[] tokens = StringUtils.split(link, ";");
        for (String token : tokens) {
            String theToken = token.trim();
            if (!theToken.isEmpty()) {
                int i = theToken.indexOf('=');
                if (i != -1) {
                    String name = theToken.substring(0, i);
                    String value = stripQuotes(theToken.substring(i + 1));
                    params.put(name, value);
                }
            }
        }
        return this;
    }

    @Override
    public Builder param(String name, String value) {
        checkNotNull(name);
        checkNotNull(value);
        params.put(name, value);
        return this;
    }

    @Override
    public Builder rel(String rel) {
        String exisingRel = params.get(Link.REL);
        String newRel = exisingRel == null ? rel : exisingRel + " " + rel; 
        return param(Link.REL, newRel);
    }

    @Override
    public Builder title(String title) {
        return param(Link.TITLE, title);
    }

    @Override
    public Builder type(String type) {
        return param(Link.TYPE, type);
    }

    @Override
    public Builder uri(URI uri) {
        ub = UriBuilder.fromUri(uri);
        return this;
    }

    @Override
    public Builder uri(String uri) {
        ub = UriBuilder.fromUri(uri);
        return this;
    }

    @Override
    public Builder uriBuilder(UriBuilder builder) {
        this.ub = builder;
        return this;
    }

    private String stripQuotes(String value) {
        return value.replaceAll(DOUBLE_QUOTE, "");
    }

    private void checkNotNull(String value) {
        if (value == null) {
            throw new IllegalArgumentException(value);
        }
    }
    
    static class LinkImpl extends Link {
        private static final Set<String> MAIN_PARAMETERS = 
            new HashSet<String>(Arrays.asList(Link.REL, Link.TITLE, Link.TYPE));
        
        private URI uri;
        private Map<String, String> params;
        LinkImpl(URI uri, Map<String, String> params) {
            this.uri = uri;
            this.params = params;
        }
        
        @Override
        public Map<String, String> getParams() {
            return Collections.unmodifiableMap(params);
        }

        @Override
        public String getRel() {
            return params.get(Link.REL);
        }

        @Override
        public List<String> getRels() {
            String rel = getRel();
            if (rel == null) {
                return Collections.<String>emptyList();
            } else {
                String[] values = rel.split(" ");
                List<String> rels = new ArrayList<String>(values.length);
                for (String val : values) {
                    rels.add(val.trim());
                }
                return rels;
            }
        }

        @Override
        public String getTitle() {
            return params.get(Link.TITLE);
        }

        @Override
        public String getType() {
            return params.get(Link.TYPE);
        }

        @Override
        public URI getUri() {
            return uri;
        }

        @Override
        public UriBuilder getUriBuilder() {
            return UriBuilder.fromUri(uri);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("<").append(uri.toString()).append(">");
            String rel = getRel();
            if (rel != null) {
                sb.append(";").append(Link.REL).append("=\"").append(rel).append("\"");
            }
            String title = getTitle();
            if (title != null) {
                sb.append(";").append(Link.TITLE).append("=\"").append(title).append("\"");
            }
            String type = getType();
            if (type != null) {
                sb.append(";").append(Link.TYPE).append("=\"").append(type).append("\"");
            }
            for (Map.Entry<String, String> entry : params.entrySet()) {
                if (!MAIN_PARAMETERS.contains(entry.getKey())) {
                    sb.append(";").append(entry.getKey()).append("=\"")
                        .append(entry.getValue()).append("\"");
                }
            }
            return sb.toString();
        } 
    
        @Override
        public int hashCode() {
            return uri.hashCode() + 37 * params.hashCode();
        }
        
        @Override
        public boolean equals(Object o) {
            if (o instanceof Link) {
                Link other = (Link)o;
                return uri.equals(other.getUri()) 
                    && getParams().equals(other.getParams());
            } else {
                return false;
            }
        }
    }

    @Override
    public Builder baseUri(URI uri) {
        this.baseUri = uri;
        return this;
    }

    @Override
    public Builder baseUri(String uri) {
        baseUri = URI.create(uri);
        return this;   
    }
}
