/*
 * Copyright 2013 The Netty Project
 *
 * The Netty Project 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.jboss.netty.handler.codec.spdy;

import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpVersion;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Provides the constants for the standard SPDY HTTP header names and commonly
 * used utility methods that access a {@link SpdyHeadersFrame}.
 */
public abstract class SpdyHeaders implements Iterable<Map.Entry<String, String>> {

    public static final SpdyHeaders EMPTY_HEADERS = new SpdyHeaders() {

        @Override
        public List<String> getAll(String name) {
            return Collections.emptyList();
        }

        @Override
        public List<Map.Entry<String, String>> entries() {
            return Collections.emptyList();
        }

        @Override
        public boolean contains(String name) {
            return false;
        }

        @Override
        public boolean isEmpty() {
            return true;
        }

        @Override
        public Set<String> names() {
            return Collections.emptySet();
        }

        @Override
        public SpdyHeaders add(String name, Object value) {
            throw new UnsupportedOperationException("read only");
        }

        @Override
        public SpdyHeaders add(String name, Iterable<?> values) {
            throw new UnsupportedOperationException("read only");
        }

        @Override
        public SpdyHeaders set(String name, Object value) {
            throw new UnsupportedOperationException("read only");
        }

        @Override
        public SpdyHeaders set(String name, Iterable<?> values) {
            throw new UnsupportedOperationException("read only");
        }

        @Override
        public SpdyHeaders remove(String name) {
            throw new UnsupportedOperationException("read only");
        }

        @Override
        public SpdyHeaders clear() {
            throw new UnsupportedOperationException("read only");
        }

        @Override
        public Iterator<Map.Entry<String, String>> iterator() {
            return entries().iterator();
        }

        @Override
        public String get(String name) {
            return null;
        }
    };

    /**
     * SPDY HTTP header names
     */
    public static final class HttpNames {
        /**
         * {@code ":host"}
         */
        public static final String HOST = ":host";
        /**
         * {@code ":method"}
         */
        public static final String METHOD = ":method";
        /**
         * {@code ":path"}
         */
        public static final String PATH = ":path";
        /**
         * {@code ":scheme"}
         */
        public static final String SCHEME = ":scheme";
        /**
         * {@code ":status"}
         */
        public static final String STATUS = ":status";
        /**
         * {@code ":version"}
         */
        public static final String VERSION = ":version";

        private HttpNames() { }
    }

    /**
     * Returns the header value with the specified header name.  If there are
     * more than one header value for the specified header name, the first
     * value is returned.
     *
     * @return the header value or {@code null} if there is no such header
     */
    public static String getHeader(SpdyHeadersFrame frame, String name) {
        return frame.headers().get(name);
    }

    /**
     * Returns the header value with the specified header name.  If there are
     * more than one header value for the specified header name, the first
     * value is returned.
     *
     * @return the header value or the {@code defaultValue} if there is no such
     *         header
     */
    public static String getHeader(SpdyHeadersFrame frame, String name, String defaultValue) {
        String value = frame.headers().get(name);
        if (value == null) {
            return defaultValue;
        }
        return value;
    }

    /**
     * Sets a new header with the specified name and value.  If there is an
     * existing header with the same name, the existing header is removed.
     */
    public static void setHeader(SpdyHeadersFrame frame, String name, Object value) {
        frame.headers().set(name, value);
    }

    /**
     * Sets a new header with the specified name and values.  If there is an
     * existing header with the same name, the existing header is removed.
     */
    public static void setHeader(SpdyHeadersFrame frame, String name, Iterable<?> values) {
        frame.headers().set(name, values);
    }

    /**
     * Adds a new header with the specified name and value.
     */
    public static void addHeader(SpdyHeadersFrame frame, String name, Object value) {
        frame.headers().add(name, value);
    }

    /**
     * Removes the SPDY host header.
     */
    public static void removeHost(SpdyHeadersFrame frame) {
        frame.headers().remove(HttpNames.HOST);
    }

    /**
     * Returns the SPDY host header.
     */
    public static String getHost(SpdyHeadersFrame frame) {
        return frame.headers().get(HttpNames.HOST);
    }

    /**
     * Set the SPDY host header.
     */
    public static void setHost(SpdyHeadersFrame frame, String host) {
        frame.headers().set(HttpNames.HOST, host);
    }

    /**
     * Removes the HTTP method header.
     */
    public static void removeMethod(int spdyVersion, SpdyHeadersFrame frame) {
        frame.headers().remove(HttpNames.METHOD);
    }

    /**
     * Returns the {@link HttpMethod} represented by the HTTP method header.
     */
    public static HttpMethod getMethod(int spdyVersion, SpdyHeadersFrame frame) {
        try {
            return HttpMethod.valueOf(frame.headers().get(HttpNames.METHOD));
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Sets the HTTP method header.
     */
    public static void setMethod(int spdyVersion, SpdyHeadersFrame frame, HttpMethod method) {
        frame.headers().set(HttpNames.METHOD, method.getName());
    }

    /**
     * Removes the URL scheme header.
     */
    public static void removeScheme(int spdyVersion, SpdyHeadersFrame frame) {
        frame.headers().remove(HttpNames.SCHEME);
    }

    /**
     * Returns the value of the URL scheme header.
     */
    public static String getScheme(int spdyVersion, SpdyHeadersFrame frame) {
        return frame.headers().get(HttpNames.SCHEME);
    }

    /**
     * Sets the URL scheme header.
     */
    public static void setScheme(int spdyVersion, SpdyHeadersFrame frame, String scheme) {
        frame.headers().set(HttpNames.SCHEME, scheme);
    }

    /**
     * Removes the HTTP response status header.
     */
    public static void removeStatus(int spdyVersion, SpdyHeadersFrame frame) {
        frame.headers().remove(HttpNames.STATUS);
    }

    /**
     * Returns the {@link HttpResponseStatus} represented by the HTTP response status header.
     */
    public static HttpResponseStatus getStatus(int spdyVersion, SpdyHeadersFrame frame) {
        try {
            String status = frame.headers().get(HttpNames.STATUS);
            int space = status.indexOf(' ');
            if (space == -1) {
                return HttpResponseStatus.valueOf(Integer.parseInt(status));
            } else {
                int code = Integer.parseInt(status.substring(0, space));
                String reasonPhrase = status.substring(space + 1);
                HttpResponseStatus responseStatus = HttpResponseStatus.valueOf(code);
                if (responseStatus.getReasonPhrase().equals(reasonPhrase)) {
                    return responseStatus;
                } else {
                    return new HttpResponseStatus(code, reasonPhrase);
                }
            }
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Sets the HTTP response status header.
     */
    public static void setStatus(int spdyVersion, SpdyHeadersFrame frame, HttpResponseStatus status) {
        frame.headers().set(HttpNames.STATUS, status.toString());
    }

    /**
     * Removes the URL path header.
     */
    public static void removeUrl(int spdyVersion, SpdyHeadersFrame frame) {
        frame.headers().remove(HttpNames.PATH);
    }

    /**
     * Returns the value of the URL path header.
     */
    public static String getUrl(int spdyVersion, SpdyHeadersFrame frame) {
        return frame.headers().get(HttpNames.PATH);
    }

    /**
     * Sets the URL path header.
     */
    public static void setUrl(int spdyVersion, SpdyHeadersFrame frame, String path) {
        frame.headers().set(HttpNames.PATH, path);
    }

    /**
     * Removes the HTTP version header.
     */
    public static void removeVersion(int spdyVersion, SpdyHeadersFrame frame) {
        frame.headers().remove(HttpNames.VERSION);
    }

    /**
     * Returns the {@link HttpVersion} represented by the HTTP version header.
     */
    public static HttpVersion getVersion(int spdyVersion, SpdyHeadersFrame frame) {
        try {
            return HttpVersion.valueOf(frame.headers().get(HttpNames.VERSION));
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Sets the HTTP version header.
     */
    public static void setVersion(int spdyVersion, SpdyHeadersFrame frame, HttpVersion httpVersion) {
        frame.headers().set(HttpNames.VERSION, httpVersion.getText());
    }

    @Override
    public Iterator<Map.Entry<String, String>> iterator() {
        return entries().iterator();
    }

    /**
     * Returns the header value with the specified header name.  If there is
     * more than one header value for the specified header name, the first
     * value is returned.
     *
     * @return the header value or {@code null} if there is no such header
     */
    public abstract String get(String name);

    /**
     * Returns the header values with the specified header name.
     *
     * @return the {@link List} of header values.  An empty list if there is no
     *         such header.
     */
    public abstract List<String> getAll(String name);

    /**
     * Returns all header names and values that this frame contains.
     *
     * @return the {@link List} of the header name-value pairs.  An empty list
     *         if there is no header in this message.
     */
    public abstract List<Map.Entry<String, String>> entries();

    /**
     * Returns {@code true} if and only if there is a header with the specified
     * header name.
     */
    public abstract boolean contains(String name);

    /**
     * Returns the {@link Set} of all header names that this frame contains.
     */
    public abstract Set<String> names();

    /**
     * Adds a new header with the specified name and value.
     */
    public abstract SpdyHeaders add(String name, Object value);

    /**
     * Adds a new header with the specified name and values.  If there is an
     * existing header with the same name, the existing header is removed.
     */
    public abstract SpdyHeaders add(String name, Iterable<?> values);

    /**
     * Sets a new header with the specified name and value.  If there is an
     * existing header with the same name, the existing header is removed.
     */
    public abstract SpdyHeaders set(String name, Object value);

    /**
     * Sets a new header with the specified name and values.  If there is an
     * existing header with the same name, the existing header is removed.
     */
    public abstract SpdyHeaders set(String name, Iterable<?> values);

    /**
     * Removes the header with the specified name.
     */
    public abstract SpdyHeaders remove(String name);

    /**
     * Removes all headers from this frame.
     */
    public abstract SpdyHeaders clear();

    /**
     * Checks if no header exists.
     */
    public abstract boolean isEmpty();
}
