/*
 * Copyright (C) 2010 Google, Inc. All Rights Reserved.
 * Copyright (C) 2014-2017 Apple, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#pragma once

#include "HTTPHeaderMap.h"
#include <wtf/Optional.h>
#include <wtf/Seconds.h>
#include <wtf/persistence/PersistentDecoder.h>
#include <wtf/persistence/PersistentEncoder.h>
#include <wtf/text/WTFString.h>

#if PLATFORM(COCOA)
OBJC_CLASS NSDictionary;
#endif

namespace WebCore {

enum class NetworkLoadPriority : uint8_t {
    Low,
    Medium,
    High,
    Unknown,
};

class NetworkLoadMetrics {
public:
    NetworkLoadMetrics()
    {
        reset();
    }

    NetworkLoadMetrics isolatedCopy() const
    {
        NetworkLoadMetrics copy;

        copy.domainLookupStart = domainLookupStart;
        copy.domainLookupEnd = domainLookupEnd;
        copy.connectStart = connectStart;
        copy.secureConnectionStart = secureConnectionStart;
        copy.connectEnd = connectEnd;
        copy.requestStart = requestStart;
        copy.responseStart = responseStart;
        copy.responseEnd = responseEnd;
        copy.complete = complete;
        copy.protocol = protocol.isolatedCopy();

        copy.remoteAddress = remoteAddress.isolatedCopy();
        copy.connectionIdentifier = connectionIdentifier.isolatedCopy();
        copy.priority = priority;
        copy.tlsProtocol = tlsProtocol.isolatedCopy();
        copy.tlsCipher = tlsCipher.isolatedCopy();
        copy.requestHeaders = requestHeaders.isolatedCopy();

        copy.requestHeaderBytesSent = requestHeaderBytesSent;
        copy.requestBodyBytesSent = requestBodyBytesSent;
        copy.responseHeaderBytesReceived = responseHeaderBytesReceived;
        copy.responseBodyBytesReceived = responseBodyBytesReceived;
        copy.responseBodyDecodedSize = responseBodyDecodedSize;

        return copy;
    }

    void reset()
    {
        domainLookupStart = Seconds(-1);
        domainLookupEnd = Seconds(-1);
        connectStart = Seconds(-1);
        secureConnectionStart = Seconds(-1);
        connectEnd = Seconds(-1);
        requestStart = Seconds(0);
        responseStart = Seconds(0);
        responseEnd = Seconds(0);
        complete = false;
        protocol = String();
        clearNonTimingData();
    }

    void clearNonTimingData()
    {
        remoteAddress = String();
        connectionIdentifier = String();
        priority = NetworkLoadPriority::Unknown;
        tlsProtocol = String();
        tlsCipher = String();
        requestHeaders.clear();
        requestHeaderBytesSent = std::numeric_limits<uint32_t>::max();
        requestBodyBytesSent = std::numeric_limits<uint64_t>::max();
        responseHeaderBytesReceived = std::numeric_limits<uint32_t>::max();
        responseBodyBytesReceived = std::numeric_limits<uint64_t>::max();
        responseBodyDecodedSize = std::numeric_limits<uint64_t>::max();
    }

    bool operator==(const NetworkLoadMetrics& other) const
    {
        return domainLookupStart == other.domainLookupStart
            && domainLookupEnd == other.domainLookupEnd
            && connectStart == other.connectStart
            && secureConnectionStart == other.secureConnectionStart
            && connectEnd == other.connectEnd
            && requestStart == other.requestStart
            && responseStart == other.responseStart
            && responseEnd == other.responseEnd
            && complete == other.complete
            && protocol == other.protocol
            && remoteAddress == other.remoteAddress
            && connectionIdentifier == other.connectionIdentifier
            && priority == other.priority
            && tlsProtocol == other.tlsProtocol
            && tlsCipher == other.tlsCipher
            && requestHeaders == other.requestHeaders
            && requestHeaderBytesSent == other.requestHeaderBytesSent
            && requestBodyBytesSent == other.requestBodyBytesSent
            && responseHeaderBytesReceived == other.responseHeaderBytesReceived
            && responseBodyBytesReceived == other.responseBodyBytesReceived
            && responseBodyDecodedSize == other.responseBodyDecodedSize;
    }

    bool operator!=(const NetworkLoadMetrics& other) const
    {
        return !(*this == other);
    }

    bool isComplete() const { return complete; }
    void markComplete() { complete = true; }

    template<class Encoder> void encode(Encoder&) const;
    template<class Decoder> static bool decode(Decoder&, NetworkLoadMetrics&);

    // These should be treated as deltas to LoadTiming's fetchStart.
    // They should be in ascending order as listed here.
    Seconds domainLookupStart;     // -1 if no DNS.
    Seconds domainLookupEnd;       // -1 if no DNS.
    Seconds connectStart;          // -1 if reused connection.
    Seconds secureConnectionStart; // -1 if no secure connection.
    Seconds connectEnd;            // -1 if reused connection.
    Seconds requestStart;
    Seconds responseStart;
    Seconds responseEnd;

    // ALPN Protocol ID: https://w3c.github.io/resource-timing/#bib-RFC7301
    String protocol;

    String remoteAddress;
    String connectionIdentifier;
    NetworkLoadPriority priority;

    String tlsProtocol;
    String tlsCipher;

    // Whether or not all of the properties (0 or otherwise) have been set.
    bool complete { false };

    HTTPHeaderMap requestHeaders;

    uint32_t requestHeaderBytesSent;
    uint32_t responseHeaderBytesReceived;
    uint64_t requestBodyBytesSent;
    uint64_t responseBodyBytesReceived;
    uint64_t responseBodyDecodedSize;
};

#if PLATFORM(COCOA)
WEBCORE_EXPORT void copyTimingData(NSDictionary *timingData, NetworkLoadMetrics&);
#endif

#if PLATFORM(COCOA) && !HAVE(TIMINGDATAOPTIONS)
WEBCORE_EXPORT void setCollectsTimingData();
#endif

template<class Encoder>
void NetworkLoadMetrics::encode(Encoder& encoder) const
{
    encoder << domainLookupStart;
    encoder << domainLookupEnd;
    encoder << connectStart;
    encoder << secureConnectionStart;
    encoder << connectEnd;
    encoder << requestStart;
    encoder << responseStart;
    encoder << responseEnd;
    encoder << complete;
    encoder << protocol;
    encoder << remoteAddress;
    encoder << connectionIdentifier;
    encoder << priority;
    encoder << tlsProtocol;
    encoder << tlsCipher;
    encoder << requestHeaders;
    encoder << requestHeaderBytesSent;
    encoder << requestBodyBytesSent;
    encoder << responseHeaderBytesReceived;
    encoder << responseBodyBytesReceived;
    encoder << responseBodyDecodedSize;
}

template<class Decoder>
bool NetworkLoadMetrics::decode(Decoder& decoder, NetworkLoadMetrics& metrics)
{
    return decoder.decode(metrics.domainLookupStart)
        && decoder.decode(metrics.domainLookupEnd)
        && decoder.decode(metrics.connectStart)
        && decoder.decode(metrics.secureConnectionStart)
        && decoder.decode(metrics.connectEnd)
        && decoder.decode(metrics.requestStart)
        && decoder.decode(metrics.responseStart)
        && decoder.decode(metrics.responseEnd)
        && decoder.decode(metrics.complete)
        && decoder.decode(metrics.protocol)
        && decoder.decode(metrics.remoteAddress)
        && decoder.decode(metrics.connectionIdentifier)
        && decoder.decode(metrics.priority)
        && decoder.decode(metrics.tlsProtocol)
        && decoder.decode(metrics.tlsCipher)
        && decoder.decode(metrics.requestHeaders)
        && decoder.decode(metrics.requestHeaderBytesSent)
        && decoder.decode(metrics.requestBodyBytesSent)
        && decoder.decode(metrics.responseHeaderBytesReceived)
        && decoder.decode(metrics.responseBodyBytesReceived)
        && decoder.decode(metrics.responseBodyDecodedSize);
}

} // namespace WebCore

// NetworkLoadMetrics should not be stored by the WTF::Persistence::Decoder.
namespace WTF {
namespace Persistence {

template<> struct Coder<Optional<WebCore::NetworkLoadPriority>> {
    static NO_RETURN_DUE_TO_ASSERT void encode(Encoder&, const Optional<WebCore::NetworkLoadPriority>&)
    {
        ASSERT_NOT_REACHED();
    }

    static bool decode(Decoder&, Optional<WebCore::NetworkLoadPriority>&)
    {
        ASSERT_NOT_REACHED();
        return false;
    }
};

}} // namespace WTF::Persistence
