/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef WebSocket_h__
#define WebSocket_h__

#include "mozilla/Util.h"

#include "nsWrapperCache.h"
#include "nsIWebSocketListener.h"
#include "nsISupports.h"

#include "mozilla/ErrorResult.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/BindingUtils.h"

// Need this for BinaryType.
#include "mozilla/dom/WebSocketBinding.h"

#include "jsfriendapi.h"
#include "nsISupportsUtils.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsIPrincipal.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIDOMEventListener.h"
#include "nsDOMEventTargetHelper.h"
#include "nsAutoPtr.h"
#include "nsIDOMDOMStringList.h"
#include "nsIInterfaceRequestor.h"
#include "nsIWebSocketChannel.h"
#include "nsIWebSocketListener.h"
#include "nsIObserver.h"
#include "nsIRequest.h"
#include "nsWeakReference.h"

#define DEFAULT_WS_SCHEME_PORT  80
#define DEFAULT_WSS_SCHEME_PORT 443

namespace mozilla {
namespace dom {

#define IMPL_EVENT_HANDLER(_lowercase)                                  \
  inline JSObject* GetOn##_lowercase(JSContext* aCx)                    \
  {                                                                     \
    JS::Value val;                                                      \
    nsresult rv = GetOn##_lowercase(aCx, &val);                         \
    return NS_SUCCEEDED(rv) ? JSVAL_TO_OBJECT(val) : nullptr;           \
  }                                                                     \
  void SetOn##_lowercase(JSContext* aCx, JSObject* aCallback,           \
                         ErrorResult& aRv)                              \
  {                                                                     \
    aRv = SetOn##_lowercase(aCx, OBJECT_TO_JSVAL(aCallback));           \
  }                                                                     \
  NS_IMETHOD GetOn##_lowercase(JSContext* cx, JS::Value* aVal);         \
  NS_IMETHOD SetOn##_lowercase(JSContext* cx, const JS::Value& aVal);

class WebSocket : public nsDOMEventTargetHelper,
                  public nsIInterfaceRequestor,
                  public nsIWebSocketListener,
                  public nsIObserver,
                  public nsSupportsWeakReference,
                  public nsIRequest
{
friend class CallDispatchConnectionCloseEvents;
friend class nsAutoCloseWS;

public:
  enum {
    CONNECTING = 0,
    OPEN       = 1,
    CLOSING    = 2,
    CLOSED     = 3
  };

public:
  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_INHERITED(WebSocket,
                                                                   nsDOMEventTargetHelper)
  NS_DECL_NSIINTERFACEREQUESTOR
  NS_DECL_NSIWEBSOCKETLISTENER
  NS_DECL_NSIOBSERVER
  NS_DECL_NSIREQUEST

  // nsIDOMEventTarget
  NS_IMETHOD AddEventListener(const nsAString& aType,
                              nsIDOMEventListener *aListener,
                              bool aUseCapture,
                              bool aWantsUntrusted,
                              uint8_t optional_argc);
  NS_IMETHOD RemoveEventListener(const nsAString& aType,
                                 nsIDOMEventListener* aListener,
                                 bool aUseCapture);

  virtual void DisconnectFromOwner();

  // nsWrapperCache
  nsPIDOMWindow* GetParentObject() { return GetOwner(); }

  JSObject* WrapObject(JSContext *cx,
                       JSObject *scope,
                       bool *triedToWrap);

public: // static helpers:

  // Determine if preferences allow WebSocket
  static bool PrefEnabled();

public: // WebIDL interface:

  // Constructor:
  static already_AddRefed<WebSocket> Constructor(JSContext *aCx,
                                                 nsISupports* aGlobal,
                                                 const nsAString& aUrl,
                                                 ErrorResult& rv);

  static already_AddRefed<WebSocket> Constructor(JSContext *aCx,
                                                 nsISupports* aGlobal,
                                                 const nsAString& aUrl,
                                                 const nsAString& aProtocol,
                                                 ErrorResult& rv);

  static already_AddRefed<WebSocket> Constructor(JSContext *aCx,
                                                 nsISupports* aGlobal,
                                                 const nsAString& aUrl,
                                                 const Sequence<nsString>& aProtocols,
                                                 ErrorResult& rv);

  // webIDL: readonly attribute DOMString url
  void GetUrl(nsAString& aResult);

  // webIDL: readonly attribute unsigned short readyState;
  uint16_t ReadyState() const { return mReadyState; }

  // webIDL: readonly attribute unsigned long bufferedAmount;
  uint32_t BufferedAmount() const { return mOutgoingBufferedAmount; }

  // webIDL: attribute Function? onopen;
  IMPL_EVENT_HANDLER(open)

  // webIDL: attribute Function? onerror;
  IMPL_EVENT_HANDLER(error)

  // webIDL: attribute Function? onclose;
  IMPL_EVENT_HANDLER(close)

  // webIDL: readonly attribute DOMString extensions;
  void GetExtensions(nsAString& aResult);

  // webIDL: readonly attribute DOMString protocol;
  void GetProtocol(nsAString& aResult);

  // webIDL: void close(optional unsigned short code, optional DOMString reason):
  void Close(const Optional<uint16_t>& aCode,
             const Optional<nsAString>& aReason,
             ErrorResult& aRv);

  // webIDL: attribute Function? onmessage;
  IMPL_EVENT_HANDLER(message)

  // webIDL: attribute DOMString binaryType;
  dom::BinaryType BinaryType() const { return mBinaryType; }
  void SetBinaryType(dom::BinaryType aData) { mBinaryType = aData; }

  // webIDL: void send(DOMString|Blob|ArrayBufferView data);
  void Send(const nsAString& aData,
            ErrorResult& aRv);
  void Send(nsIDOMBlob* aData,
            ErrorResult& aRv);
  void Send(ArrayBuffer& aData,
            ErrorResult& aRv);
  void Send(ArrayBufferView& aData,
            ErrorResult& aRv);

private: // constructor && distructor
  WebSocket();
  virtual ~WebSocket();

protected:
  nsresult Init(JSContext* aCx,
                nsIPrincipal* aPrincipal,
                nsPIDOMWindow* aOwnerWindow,
                const nsAString& aURL,
                nsTArray<nsString>& aProtocolArray);

  void Send(nsIInputStream* aMsgStream,
            const nsACString& aMsgString,
            uint32_t aMsgLength,
            bool aIsBinary,
            ErrorResult& aRv);

  nsresult ParseURL(const nsString& aURL);
  nsresult EstablishConnection();

  // These methods when called can release the WebSocket object
  nsresult FailConnection(uint16_t reasonCode,
                          const nsACString& aReasonString = EmptyCString());
  nsresult CloseConnection(uint16_t reasonCode,
                           const nsACString& aReasonString = EmptyCString());
  nsresult Disconnect();

  nsresult ConsoleError();
  nsresult PrintErrorOnConsole(const char* aBundleURI,
                               const PRUnichar* aError,
                               const PRUnichar** aFormatStrings,
                               uint32_t aFormatStringsLen);

  nsresult DoOnMessageAvailable(const nsACString& aMsg,
                                bool isBinary);

  // ConnectionCloseEvents: 'error' event if needed, then 'close' event.
  // - These must not be dispatched while we are still within an incoming call
  //   from JS (ex: close()).  Set 'sync' to false in that case to dispatch in a
  //   separate new event.
  nsresult ScheduleConnectionCloseEvents(nsISupports* aContext,
                                         nsresult aStatusCode,
                                         bool sync);
  // 2nd half of ScheduleConnectionCloseEvents, sometimes run in its own event.
  void DispatchConnectionCloseEvents();

  // These methods actually do the dispatch for various events.
  nsresult CreateAndDispatchSimpleEvent(const nsString& aName);
  nsresult CreateAndDispatchMessageEvent(const nsACString& aData,
                                         bool isBinary);
  nsresult CreateAndDispatchCloseEvent(bool aWasClean,
                                       uint16_t aCode,
                                       const nsString& aReason);

  // if there are "strong event listeners" (see comment in WebSocket.cpp) or
  // outgoing not sent messages then this method keeps the object alive
  // when js doesn't have strong references to it.
  void UpdateMustKeepAlive();
  // ATTENTION, when calling this method the object can be released
  // (and possibly collected).
  void DontKeepAliveAnyMore();

  nsresult UpdateURI();

protected: //data

  nsCOMPtr<nsIWebSocketChannel> mChannel;

  // related to the WebSocket constructor steps
  nsString mOriginalURL;
  nsString mEffectiveURL;   // after redirects
  bool mSecure; // if true it is using SSL and the wss scheme,
                // otherwise it is using the ws scheme with no SSL

  bool mKeepingAlive;
  bool mCheckMustKeepAlive;
  bool mOnCloseScheduled;
  bool mFailed;
  bool mDisconnected;

  // Set attributes of DOM 'onclose' message
  bool      mCloseEventWasClean;
  nsString  mCloseEventReason;
  uint16_t  mCloseEventCode;

  nsCString mAsciiHost;  // hostname
  uint32_t  mPort;
  nsCString mResource; // [filepath[?query]]
  nsString  mUTF16Origin;

  nsCOMPtr<nsIURI> mURI;
  nsCString mRequestedProtocolList;
  nsCString mEstablishedProtocol;
  nsCString mEstablishedExtensions;

  uint16_t mReadyState;

  nsCOMPtr<nsIPrincipal> mPrincipal;

  uint32_t mOutgoingBufferedAmount;

  dom::BinaryType mBinaryType;

  // Web Socket owner information:
  // - the script file name, UTF8 encoded.
  // - source code line number where the Web Socket object was constructed.
  // - the ID of the inner window where the script lives. Note that this may not
  //   be the same as the Web Socket owner window.
  // These attributes are used for error reporting.
  nsCString mScriptFile;
  uint32_t mScriptLine;
  uint64_t mInnerWindowID;

private:
  WebSocket(const WebSocket& x) MOZ_DELETE;   // prevent bad usage
  WebSocket& operator=(const WebSocket& x) MOZ_DELETE;
};

} //namespace dom
} //namespace mozilla

#endif
