/*
 * Copyright (C) 2012 Square, 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.
 */
package com.squareup.okhttp.internal.framed;

import java.util.Arrays;

/**
 * Settings describe characteristics of the sending peer, which are used by the receiving peer.
 * Settings are {@link FramedConnection connection} scoped.
 */
public final class Settings {
  /**
   * From the SPDY/3 and HTTP/2 specs, the default initial window size for all
   * streams is 64 KiB. (Chrome 25 uses 10 MiB).
   */
  static final int DEFAULT_INITIAL_WINDOW_SIZE = 64 * 1024;

  /** Peer request to clear durable settings. */
  static final int FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS = 0x1;

  /** Sent by servers only. The peer requests this setting persisted for future connections. */
  static final int PERSIST_VALUE = 0x1;
  /** Sent by clients only. The client is reminding the server of a persisted value. */
  static final int PERSISTED = 0x2;

  /** spdy/3: Sender's estimate of max incoming kbps. */
  static final int UPLOAD_BANDWIDTH = 1;
  /** HTTP/2: Size in bytes of the table used to decode the sender's header blocks. */
  static final int HEADER_TABLE_SIZE = 1;
  /** spdy/3: Sender's estimate of max outgoing kbps. */
  static final int DOWNLOAD_BANDWIDTH = 2;
  /** HTTP/2: The peer must not send a PUSH_PROMISE frame when this is 0. */
  static final int ENABLE_PUSH = 2;
  /** spdy/3: Sender's estimate of millis between sending a request and receiving a response. */
  static final int ROUND_TRIP_TIME = 3;
  /** Sender's maximum number of concurrent streams. */
  static final int MAX_CONCURRENT_STREAMS = 4;
  /** spdy/3: Current CWND in Packets. */
  static final int CURRENT_CWND = 5;
  /** HTTP/2: Size in bytes of the largest frame payload the sender will accept. */
  static final int MAX_FRAME_SIZE = 5;
  /** spdy/3: Retransmission rate. Percentage */
  static final int DOWNLOAD_RETRANS_RATE = 6;
  /** HTTP/2: Advisory only. Size in bytes of the largest header list the sender will accept. */
  static final int MAX_HEADER_LIST_SIZE = 6;
  /** Window size in bytes. */
  static final int INITIAL_WINDOW_SIZE = 7;
  /** spdy/3: Size of the client certificate vector. Unsupported. */
  static final int CLIENT_CERTIFICATE_VECTOR_SIZE = 8;
  /** Flow control options. */
  static final int FLOW_CONTROL_OPTIONS = 10;

  /** Total number of settings. */
  static final int COUNT = 10;

  /** If set, flow control is disabled for streams directed to the sender of these settings. */
  static final int FLOW_CONTROL_OPTIONS_DISABLED = 0x1;

  /** Bitfield of which flags that values. */
  private int set;

  /** Bitfield of flags that have {@link #PERSIST_VALUE}. */
  private int persistValue;

  /** Bitfield of flags that have {@link #PERSISTED}. */
  private int persisted;

  /** Flag values. */
  private final int[] values = new int[COUNT];

  void clear() {
    set = persistValue = persisted = 0;
    Arrays.fill(values, 0);
  }

  Settings set(int id, int idFlags, int value) {
    if (id >= values.length) {
      return this; // Discard unknown settings.
    }

    int bit = 1 << id;
    set |= bit;
    if ((idFlags & PERSIST_VALUE) != 0) {
      persistValue |= bit;
    } else {
      persistValue &= ~bit;
    }
    if ((idFlags & PERSISTED) != 0) {
      persisted |= bit;
    } else {
      persisted &= ~bit;
    }

    values[id] = value;
    return this;
  }

  /** Returns true if a value has been assigned for the setting {@code id}. */
  boolean isSet(int id) {
    int bit = 1 << id;
    return (set & bit) != 0;
  }

  /** Returns the value for the setting {@code id}, or 0 if unset. */
  int get(int id) {
    return values[id];
  }

  /** Returns the flags for the setting {@code id}, or 0 if unset. */
  int flags(int id) {
    int result = 0;
    if (isPersisted(id)) result |= Settings.PERSISTED;
    if (persistValue(id)) result |= Settings.PERSIST_VALUE;
    return result;
  }

  /** Returns the number of settings that have values assigned. */
  int size() {
    return Integer.bitCount(set);
  }

  /** spdy/3 only. */
  int getUploadBandwidth(int defaultValue) {
    int bit = 1 << UPLOAD_BANDWIDTH;
    return (bit & set) != 0 ? values[UPLOAD_BANDWIDTH] : defaultValue;
  }

  /** HTTP/2 only. Returns -1 if unset. */
  int getHeaderTableSize() {
    int bit = 1 << HEADER_TABLE_SIZE;
    return (bit & set) != 0 ? values[HEADER_TABLE_SIZE] : -1;
  }

  /** spdy/3 only. */
  int getDownloadBandwidth(int defaultValue) {
    int bit = 1 << DOWNLOAD_BANDWIDTH;
    return (bit & set) != 0 ? values[DOWNLOAD_BANDWIDTH] : defaultValue;
  }

  /** HTTP/2 only. */
  // TODO: honor this setting in HTTP/2.
  boolean getEnablePush(boolean defaultValue) {
    int bit = 1 << ENABLE_PUSH;
    return ((bit & set) != 0 ? values[ENABLE_PUSH] : defaultValue ? 1 : 0) == 1;
  }

  /** spdy/3 only. */
  int getRoundTripTime(int defaultValue) {
    int bit = 1 << ROUND_TRIP_TIME;
    return (bit & set) != 0 ? values[ROUND_TRIP_TIME] : defaultValue;
  }

  // TODO: honor this setting in spdy/3 and HTTP/2.
  int getMaxConcurrentStreams(int defaultValue) {
    int bit = 1 << MAX_CONCURRENT_STREAMS;
    return (bit & set) != 0 ? values[MAX_CONCURRENT_STREAMS] : defaultValue;
  }

  /** spdy/3 only. */
  int getCurrentCwnd(int defaultValue) {
    int bit = 1 << CURRENT_CWND;
    return (bit & set) != 0 ? values[CURRENT_CWND] : defaultValue;
  }

  /** HTTP/2 only. */
  int getMaxFrameSize(int defaultValue) {
    int bit = 1 << MAX_FRAME_SIZE;
    return (bit & set) != 0 ? values[MAX_FRAME_SIZE] : defaultValue;
  }

  /** spdy/3 only. */
  int getDownloadRetransRate(int defaultValue) {
    int bit = 1 << DOWNLOAD_RETRANS_RATE;
    return (bit & set) != 0 ? values[DOWNLOAD_RETRANS_RATE] : defaultValue;
  }

  /** HTTP/2 only. */
  int getMaxHeaderListSize(int defaultValue) {
    int bit = 1 << MAX_HEADER_LIST_SIZE;
    return (bit & set) != 0 ? values[MAX_HEADER_LIST_SIZE] : defaultValue;
  }

  int getInitialWindowSize(int defaultValue) {
    int bit = 1 << INITIAL_WINDOW_SIZE;
    return (bit & set) != 0 ? values[INITIAL_WINDOW_SIZE] : defaultValue;
  }

  /** spdy/3 only. */
  int getClientCertificateVectorSize(int defaultValue) {
    int bit = 1 << CLIENT_CERTIFICATE_VECTOR_SIZE;
    return (bit & set) != 0 ? values[CLIENT_CERTIFICATE_VECTOR_SIZE] : defaultValue;
  }

  // TODO: honor this setting in spdy/3 and HTTP/2.
  boolean isFlowControlDisabled() {
    int bit = 1 << FLOW_CONTROL_OPTIONS;
    int value = (bit & set) != 0 ? values[FLOW_CONTROL_OPTIONS] : 0;
    return (value & FLOW_CONTROL_OPTIONS_DISABLED) != 0;
  }

  /**
   * Returns true if this user agent should use this setting in future spdy/3
   * connections to the same host.
   */
  boolean persistValue(int id) {
    int bit = 1 << id;
    return (persistValue & bit) != 0;
  }

  /** Returns true if this setting was persisted. */
  boolean isPersisted(int id) {
    int bit = 1 << id;
    return (persisted & bit) != 0;
  }

  /**
   * Writes {@code other} into this. If any setting is populated by this and
   * {@code other}, the value and flags from {@code other} will be kept.
   */
  void merge(Settings other) {
    for (int i = 0; i < COUNT; i++) {
      if (!other.isSet(i)) continue;
      set(i, other.flags(i), other.get(i));
    }
  }
}
