/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * 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.
 */

package com.squareup.okhttp.internal.http;

import java.net.URI;
import java.util.Date;
import java.util.List;
import java.util.Map;

/** Parsed HTTP request headers. */
public final class RequestHeaders {
  private final URI uri;
  private final RawHeaders headers;

  /** Don't use a cache to satisfy this request. */
  private boolean noCache;
  private int maxAgeSeconds = -1;
  private int maxStaleSeconds = -1;
  private int minFreshSeconds = -1;

  /**
   * This field's name "only-if-cached" is misleading. It actually means "do
   * not use the network". It is set by a client who only wants to make a
   * request if it can be fully satisfied by the cache. Cached responses that
   * would require validation (ie. conditional gets) are not permitted if this
   * header is set.
   */
  private boolean onlyIfCached;

  /**
   * True if the request contains an authorization field. Although this isn't
   * necessarily a shared cache, it follows the spec's strict requirements for
   * shared caches.
   */
  private boolean hasAuthorization;

  private long contentLength = -1;
  private String transferEncoding;
  private String userAgent;
  private String host;
  private String connection;
  private String acceptEncoding;
  private String contentType;
  private String ifModifiedSince;
  private String ifNoneMatch;
  private String proxyAuthorization;

  public RequestHeaders(URI uri, RawHeaders headers) {
    this.uri = uri;
    this.headers = headers;

    HeaderParser.CacheControlHandler handler = new HeaderParser.CacheControlHandler() {
      @Override public void handle(String directive, String parameter) {
        if ("no-cache".equalsIgnoreCase(directive)) {
          noCache = true;
        } else if ("max-age".equalsIgnoreCase(directive)) {
          maxAgeSeconds = HeaderParser.parseSeconds(parameter);
        } else if ("max-stale".equalsIgnoreCase(directive)) {
          maxStaleSeconds = HeaderParser.parseSeconds(parameter);
        } else if ("min-fresh".equalsIgnoreCase(directive)) {
          minFreshSeconds = HeaderParser.parseSeconds(parameter);
        } else if ("only-if-cached".equalsIgnoreCase(directive)) {
          onlyIfCached = true;
        }
      }
    };

    for (int i = 0; i < headers.length(); i++) {
      String fieldName = headers.getFieldName(i);
      String value = headers.getValue(i);
      if ("Cache-Control".equalsIgnoreCase(fieldName)) {
        HeaderParser.parseCacheControl(value, handler);
      } else if ("Pragma".equalsIgnoreCase(fieldName)) {
        if ("no-cache".equalsIgnoreCase(value)) {
          noCache = true;
        }
      } else if ("If-None-Match".equalsIgnoreCase(fieldName)) {
        ifNoneMatch = value;
      } else if ("If-Modified-Since".equalsIgnoreCase(fieldName)) {
        ifModifiedSince = value;
      } else if ("Authorization".equalsIgnoreCase(fieldName)) {
        hasAuthorization = true;
      } else if ("Content-Length".equalsIgnoreCase(fieldName)) {
        try {
          contentLength = Integer.parseInt(value);
        } catch (NumberFormatException ignored) {
        }
      } else if ("Transfer-Encoding".equalsIgnoreCase(fieldName)) {
        transferEncoding = value;
      } else if ("User-Agent".equalsIgnoreCase(fieldName)) {
        userAgent = value;
      } else if ("Host".equalsIgnoreCase(fieldName)) {
        host = value;
      } else if ("Connection".equalsIgnoreCase(fieldName)) {
        connection = value;
      } else if ("Accept-Encoding".equalsIgnoreCase(fieldName)) {
        acceptEncoding = value;
      } else if ("Content-Type".equalsIgnoreCase(fieldName)) {
        contentType = value;
      } else if ("Proxy-Authorization".equalsIgnoreCase(fieldName)) {
        proxyAuthorization = value;
      }
    }
  }

  public boolean isChunked() {
    return "chunked".equalsIgnoreCase(transferEncoding);
  }

  public boolean hasConnectionClose() {
    return "close".equalsIgnoreCase(connection);
  }

  public URI getUri() {
    return uri;
  }

  public RawHeaders getHeaders() {
    return headers;
  }

  public boolean isNoCache() {
    return noCache;
  }

  public int getMaxAgeSeconds() {
    return maxAgeSeconds;
  }

  public int getMaxStaleSeconds() {
    return maxStaleSeconds;
  }

  public int getMinFreshSeconds() {
    return minFreshSeconds;
  }

  public boolean isOnlyIfCached() {
    return onlyIfCached;
  }

  public boolean hasAuthorization() {
    return hasAuthorization;
  }

  public long getContentLength() {
    return contentLength;
  }

  public String getTransferEncoding() {
    return transferEncoding;
  }

  public String getUserAgent() {
    return userAgent;
  }

  public String getHost() {
    return host;
  }

  public String getConnection() {
    return connection;
  }

  public String getAcceptEncoding() {
    return acceptEncoding;
  }

  public String getContentType() {
    return contentType;
  }

  public String getIfModifiedSince() {
    return ifModifiedSince;
  }

  public String getIfNoneMatch() {
    return ifNoneMatch;
  }

  public String getProxyAuthorization() {
    return proxyAuthorization;
  }

  public void setChunked() {
    if (this.transferEncoding != null) {
      headers.removeAll("Transfer-Encoding");
    }
    headers.add("Transfer-Encoding", "chunked");
    this.transferEncoding = "chunked";
  }

  public void setContentLength(long contentLength) {
    if (this.contentLength != -1) {
      headers.removeAll("Content-Length");
    }
    headers.add("Content-Length", Long.toString(contentLength));
    this.contentLength = contentLength;
  }

  /**
   * Remove the Content-Length headers. Call this when dropping the body on a
   * request or response, such as when a redirect changes the method from POST
   * to GET.
   */
  public void removeContentLength() {
    if (contentLength != -1) {
      headers.removeAll("Content-Length");
      contentLength = -1;
    }
  }

  public void setUserAgent(String userAgent) {
    if (this.userAgent != null) {
      headers.removeAll("User-Agent");
    }
    headers.add("User-Agent", userAgent);
    this.userAgent = userAgent;
  }

  public void setHost(String host) {
    if (this.host != null) {
      headers.removeAll("Host");
    }
    headers.add("Host", host);
    this.host = host;
  }

  public void setConnection(String connection) {
    if (this.connection != null) {
      headers.removeAll("Connection");
    }
    headers.add("Connection", connection);
    this.connection = connection;
  }

  public void setAcceptEncoding(String acceptEncoding) {
    if (this.acceptEncoding != null) {
      headers.removeAll("Accept-Encoding");
    }
    headers.add("Accept-Encoding", acceptEncoding);
    this.acceptEncoding = acceptEncoding;
  }

  public void setContentType(String contentType) {
    if (this.contentType != null) {
      headers.removeAll("Content-Type");
    }
    headers.add("Content-Type", contentType);
    this.contentType = contentType;
  }

  public void setIfModifiedSince(Date date) {
    if (ifModifiedSince != null) {
      headers.removeAll("If-Modified-Since");
    }
    String formattedDate = HttpDate.format(date);
    headers.add("If-Modified-Since", formattedDate);
    ifModifiedSince = formattedDate;
  }

  public void setIfNoneMatch(String ifNoneMatch) {
    if (this.ifNoneMatch != null) {
      headers.removeAll("If-None-Match");
    }
    headers.add("If-None-Match", ifNoneMatch);
    this.ifNoneMatch = ifNoneMatch;
  }

  /**
   * Returns true if the request contains conditions that save the server from
   * sending a response that the client has locally. When the caller adds
   * conditions, this cache won't participate in the request.
   */
  public boolean hasConditions() {
    return ifModifiedSince != null || ifNoneMatch != null;
  }

  public void addCookies(Map<String, List<String>> allCookieHeaders) {
    for (Map.Entry<String, List<String>> entry : allCookieHeaders.entrySet()) {
      String key = entry.getKey();
      if (("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key))
          && !entry.getValue().isEmpty()) {
        headers.add(key, buildCookieHeader(entry.getValue()));
      }
    }
  }

  /**
   * Send all cookies in one big header, as recommended by
   * <a href="http://tools.ietf.org/html/rfc6265#section-4.2.1">RFC 6265</a>.
   */
  private String buildCookieHeader(List<String> cookies) {
    if (cookies.size() == 1) return cookies.get(0);
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < cookies.size(); i++) {
      if (i > 0) sb.append("; ");
      sb.append(cookies.get(i));
    }
    return sb.toString();
  }
}
