/*
 * 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 tv.twitch.volley;

import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/** Data and headers returned from {@link Network#performRequest(Request)}. */
public class NetworkResponse {

    /**
     * Creates a new network response.
     *
     * @param statusCode the HTTP status code
     * @param data Response body
     * @param headers Headers returned with this response, or null for none
     * @param notModified True if the server returned a 304 and the data was already in cache
     * @param networkTimeMs Round-trip network time to receive network response
     * @deprecated see {@link #NetworkResponse(int, byte[], boolean, long, List)}. This constructor
     *     cannot handle server responses containing multiple headers with the same name. This
     *     constructor may be removed in a future release of Volley.
     */
    @Deprecated
    public NetworkResponse(
            int statusCode,
            byte[] data,
            Map<String, String> headers,
            boolean notModified,
            long networkTimeMs) {
        this(statusCode, data, headers, toAllHeaderList(headers), notModified, networkTimeMs);
    }

    /**
     * Creates a new network response.
     *
     * @param statusCode the HTTP status code
     * @param data Response body
     * @param notModified True if the server returned a 304 and the data was already in cache
     * @param networkTimeMs Round-trip network time to receive network response
     * @param allHeaders All headers returned with this response, or null for none
     */
    public NetworkResponse(
            int statusCode,
            byte[] data,
            boolean notModified,
            long networkTimeMs,
            List<Header> allHeaders) {
        this(statusCode, data, toHeaderMap(allHeaders), allHeaders, notModified, networkTimeMs);
    }

    /**
     * Creates a new network response.
     *
     * @param statusCode the HTTP status code
     * @param data Response body
     * @param headers Headers returned with this response, or null for none
     * @param notModified True if the server returned a 304 and the data was already in cache
     * @deprecated see {@link #NetworkResponse(int, byte[], boolean, long, List)}. This constructor
     *     cannot handle server responses containing multiple headers with the same name. This
     *     constructor may be removed in a future release of Volley.
     */
    @Deprecated
    public NetworkResponse(
            int statusCode, byte[] data, Map<String, String> headers, boolean notModified) {
        this(statusCode, data, headers, notModified, /* networkTimeMs= */ 0);
    }

    /**
     * Creates a new network response for an OK response with no headers.
     *
     * @param data Response body
     */
    public NetworkResponse(byte[] data) {
        this(
                HttpURLConnection.HTTP_OK,
                data,
                /* notModified= */ false,
                /* networkTimeMs= */ 0,
                Collections.<Header>emptyList());
    }

    /**
     * Creates a new network response for an OK response.
     *
     * @param data Response body
     * @param headers Headers returned with this response, or null for none
     * @deprecated see {@link #NetworkResponse(int, byte[], boolean, long, List)}. This constructor
     *     cannot handle server responses containing multiple headers with the same name. This
     *     constructor may be removed in a future release of Volley.
     */
    @Deprecated
    public NetworkResponse(byte[] data, Map<String, String> headers) {
        this(
                HttpURLConnection.HTTP_OK,
                data,
                headers,
                /* notModified= */ false,
                /* networkTimeMs= */ 0);
    }

    private NetworkResponse(
            int statusCode,
            byte[] data,
            Map<String, String> headers,
            List<Header> allHeaders,
            boolean notModified,
            long networkTimeMs) {
        this.statusCode = statusCode;
        this.data = data;
        this.headers = headers;
        if (allHeaders == null) {
            this.allHeaders = null;
        } else {
            this.allHeaders = Collections.unmodifiableList(allHeaders);
        }
        this.notModified = notModified;
        this.networkTimeMs = networkTimeMs;
    }

    /** The HTTP status code. */
    public final int statusCode;

    /** Raw data from this response. */
    public final byte[] data;

    /**
     * Response headers.
     *
     * <p>This map is case-insensitive. It should not be mutated directly.
     *
     * <p>Note that if the server returns two headers with the same (case-insensitive) name, this
     * map will only contain the last one. Use {@link #allHeaders} to inspect all headers returned
     * by the server.
     */
    public final Map<String, String> headers;

    /** All response headers. Must not be mutated directly. */
    public final List<Header> allHeaders;

    /** True if the server returned a 304 (Not Modified). */
    public final boolean notModified;

    /** Network roundtrip time in milliseconds. */
    public final long networkTimeMs;

    private static Map<String, String> toHeaderMap(List<Header> allHeaders) {
        if (allHeaders == null) {
            return null;
        }
        if (allHeaders.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
        // Later elements in the list take precedence.
        for (Header header : allHeaders) {
            headers.put(header.getName(), header.getValue());
        }
        return headers;
    }

    private static List<Header> toAllHeaderList(Map<String, String> headers) {
        if (headers == null) {
            return null;
        }
        if (headers.isEmpty()) {
            return Collections.emptyList();
        }
        List<Header> allHeaders = new ArrayList<>(headers.size());
        for (Map.Entry<String, String> header : headers.entrySet()) {
            allHeaders.add(new Header(header.getKey(), header.getValue()));
        }
        return allHeaders;
    }
}
