/*
 *  Copyright (C) 2003, 2006, 2008 Apple Inc. All rights reserved.
 *  Copyright (C) 2005, 2006 Alexey Proskuryakov <ap@nypop.com>
 *  Copyright (C) 2011 Google Inc. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#pragma once

#include "ActiveDOMObject.h"
#include "BlobURL.h"
#include "EventListener.h"
#include "EventNames.h"
#include "FormData.h"
#include "HTTPHeaderNames.h"
#include "ResourceResponse.h"
#include "ScriptWrappable.h"
#include "ThreadableLoaderClient.h"
#include "Timer.h"
#include "XMLHttpRequestEventTarget.h"
#include "XMLHttpRequestProgressEventThrottle.h"
#include <wtf/OwnPtr.h>
#include <wtf/text/AtomicStringHash.h>
#include <wtf/text/StringBuilder.h>

namespace JSC {
class ArrayBuffer;
class ArrayBufferView;
}

namespace WebCore {

class Blob;
class Document;
class DOMFormData;
class ResourceRequest;
class SecurityOrigin;
class SharedBuffer;
class TextResourceDecoder;
class ThreadableLoader;
class URLSearchParams;

class XMLHttpRequest FINAL : public RefCounted<XMLHttpRequest>, public XMLHttpRequestEventTarget, private ThreadableLoaderClient, public ActiveDOMObject {
    WTF_MAKE_FAST_ALLOCATED;
public:
    enum class ResponseType { EmptyString, Arraybuffer, Blob, Document, Json, Text };

    static PassRefPtr<XMLHttpRequest> create(ScriptExecutionContext*);
    virtual ~XMLHttpRequest();

    // Keep it in 3bits.
    enum State : uint8_t {
        UNSENT = 0,
        OPENED = 1,
        HEADERS_RECEIVED = 2,
        LOADING = 3,
        DONE = 4
    };

    virtual EventTargetInterface eventTargetInterface() const FINAL;
    virtual ScriptExecutionContext* scriptExecutionContext() const FINAL;

    //using SendTypes = Variant<RefPtr<Document>, RefPtr<Blob>, RefPtr<JSC::ArrayBufferView>, RefPtr<JSC::ArrayBuffer>, RefPtr<DOMFormData>, String>;

    const KURL& url() const { return m_url; }
    String statusText() const;
    int status() const;
    State readyState() const { return m_state; }

    bool withCredentials() const { return m_includeCredentials; }
    void setWithCredentials(bool, ExceptionCode&);
    void open(const String& method, const String& url, ExceptionCode&);
    void open(const String& method, const KURL&, bool async, ExceptionCode&);
    void open(const String& method, const String&, bool async, const String& user, const String& password, ExceptionCode&);
    void send(ExceptionCode&);
    void send(Document&, ExceptionCode&);
    void send(Blob&, ExceptionCode&);
    void send(JSC::ArrayBufferView*, ExceptionCode&);
    void send(JSC::ArrayBuffer&, ExceptionCode&);
    void send(DOMFormData&, ExceptionCode&);
    void send(const URLSearchParams&, ExceptionCode&);
    void send(const String&, ExceptionCode&);
    void abort();
    void setRequestHeader(const String& name, const String& value, ExceptionCode&);
    void overrideMimeType(const String& override, ExceptionCode&);
    bool doneWithoutErrors() const { return !m_error && m_state == DONE; }
    String getAllResponseHeaders() const;
    String getResponseHeader(const String& name) const;
    String responseText(ExceptionCode&);
    String responseTextIgnoringResponseType() const { return m_responseBuilder.toStringPreserveCapacity(); }
    Document* responseXML(ExceptionCode&);
    Document* optionalResponseXML() const { return m_responseDocument.get(); }
    Blob* responseBlob();
    Blob* optionalResponseBlob() const { return m_responseBlob.get(); }
    unsigned timeout() const { return m_timeoutMilliseconds; }
    void setTimeout(unsigned timeout, ExceptionCode&);

    bool responseCacheIsValid() const { return m_responseCacheIsValid; }
    void didCacheResponseJSON();

    // Expose HTTP validation methods for other untrusted requests.
    static bool isAllowedHTTPMethod(const String&);
    static String uppercaseKnownHTTPMethod(const String&);

    void setResponseType(ResponseType, ExceptionCode&);
    ResponseType responseType() const;

    String responseURL() const;

    // response attribute has custom getter.
    JSC::ArrayBuffer* responseArrayBuffer();
    JSC::ArrayBuffer* optionalResponseArrayBuffer() const { return m_responseArrayBuffer.get(); }

    void setLastSendLineNumber(unsigned lineNumber) { m_lastSendLineNumber = lineNumber; }
    void setLastSendURL(const String& url) { m_lastSendURL = url; }

    XMLHttpRequestUpload* upload();
    XMLHttpRequestUpload* optionalUpload() const { return m_upload.get(); }

    using RefCounted<XMLHttpRequest>::ref;
    using RefCounted<XMLHttpRequest>::deref;

    size_t memoryCost() const;

    XMLHttpRequestProgressEventThrottle& progressEventThrottle() { return m_progressEventThrottle; }

private:
    friend class XMLHttpRequestUpload;
    XMLHttpRequest(ScriptExecutionContext*);

    void updateHasRelevantEventListener();

    // EventTarget.
    virtual void eventListenersDidChange() final;

    TextEncoding finalResponseCharset() const;

    // ActiveDOMObject
    virtual void contextDestroyed() FINAL;
    virtual void suspend(ReasonForSuspension) FINAL;
    virtual void resume() FINAL;
    virtual void stop() FINAL;
    virtual const char* activeDOMObjectName() const OVERRIDE { return "XMLHttpRequest"; }
    virtual bool virtualHasPendingActivity() const final;

    virtual void refEventTarget() FINAL { ref(); }
    virtual void derefEventTarget() FINAL { deref(); }

    Document* document() const;

#if ENABLE(DASHBOARD_SUPPORT)
    bool usesDashboardBackwardCompatibilityMode() const;
#endif

    virtual void didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent) FINAL;
    virtual void didReceiveResponse(unsigned long identifier, const ResourceResponse&) FINAL;
    virtual void didReceiveData(const char* data, int dataLength) FINAL;
    virtual void didFinishLoading(unsigned long identifier, double finishTime) FINAL;
    virtual void didFail(const ResourceError&) FINAL;

    enum class FinalMIMEType { Yes, No };
    String responseMIMEType(FinalMIMEType = FinalMIMEType::No) const;

    bool initSend(ExceptionCode&);
    void sendBytesData(const void*, size_t, ExceptionCode&);

    String getRequestHeader(const String& name) const;
    String getRequestHeader(HTTPHeaderName name) const;
    void setRequestHeaderInternal(const String& name, const String& value);
    void setRequestHeaderInternal(HTTPHeaderName name, const String& value);

    void changeState(State newState);
    void callReadyStateChangeListener();

    // Returns false when cancelling the loader within internalAbort() triggers an event whose callback creates a new loader.
    // In that case, the function calling internalAbort should exit.
    bool internalAbort();

    void clearResponse();
    void clearResponseBuffers();
    void clearRequest();

    void createRequest(ExceptionCode&, bool isBytesData = false);

    void genericError();
    void networkError();
    void abortError();

    void dispatchErrorEvents(const AtomicString&);

    void didReachTimeout();

    PassRefPtr<TextResourceDecoder> createDecoder() const;

    OwnPtr<XMLHttpRequestUpload> m_upload;

    KURL m_url;
    BlobURLHandle m_blobURLLifetimeExtension;
    String m_method;
    HTTPHeaderMap m_requestHeaders;
    RefPtr<FormData> m_requestEntityBody;
    String m_mimeTypeOverride;
    RefPtr<Blob> m_responseBlob;

    bool m_async;
    bool m_includeCredentials;
    State m_state;
    bool m_sendFlag;

    struct LoadingActivity {
        Ref<XMLHttpRequest> protectedThis; // Keep object alive while loading even if there is no longer a JS wrapper.
        Ref<ThreadableLoader> loader;
    };
    Optional<LoadingActivity> m_loadingActivity;

    ResourceResponse m_response;
    String m_responseEncoding;

    RefPtr<TextResourceDecoder> m_decoder;

    StringBuilder m_responseBuilder;
    mutable bool m_createdDocument;
    mutable RefPtr<Document> m_responseDocument;
    
    RefPtr<SharedBuffer> m_binaryResponseBuilder;
    mutable RefPtr<JSC::ArrayBuffer> m_responseArrayBuffer;

    bool m_error;

    bool m_uploadListenerFlag;
    bool m_uploadComplete;

    bool m_wasAbortedByClient;

    // Used for onprogress tracking
    long long m_receivedLength;

    unsigned m_lastSendLineNumber;
    String m_lastSendURL;
    ExceptionCode m_exceptionCode { 0 };

    XMLHttpRequestProgressEventThrottle m_progressEventThrottle;

    // An enum corresponding to the allowed string values for the responseType attribute.
    ResponseType m_responseType { ResponseType::EmptyString };
    bool m_responseCacheIsValid;
    mutable String m_allResponseHeaders;

    unsigned m_timeoutMilliseconds;
    double m_sendingTime;
    Timer<XMLHttpRequest> m_timeoutTimer;
    void timeoutTimerFired();

    bool m_wasDidSendDataCalledForTotalBytes { false };
    std::atomic<bool> m_hasRelevantEventListener;
};

inline XMLHttpRequest::ResponseType XMLHttpRequest::responseType() const
{
    return m_responseType;
}

} // namespace WebCore
