/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 _NSDATAOBJ_H_
#define _NSDATAOBJ_H_

#include <oleidl.h>
#include <shldisp.h>

#include "mozilla/glue/WinUtils.h"
#include "mozilla/LazyIdleThread.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsIFile.h"
#include "nsIURI.h"
#include "nsIStreamListener.h"
#include "nsIChannel.h"
#include "nsCOMArray.h"
#include "nsITimer.h"
#include "nsIURI.h"
#include "nsString.h"
#include "nsWindowsHelpers.h"

class nsICookieJarSettings;
class nsIPrincipal;
class nsIReferrerInfo;
class nsIThread;
class nsITransferable;
class CEnumFormatEtc;

/*
 * This ole registered class is used to facilitate drag-drop of objects which
 * can be adapted by an object derived from CfDragDrop. The CfDragDrop is
 * associated with instances via SetDragDrop().
 */
class nsDataObj : public IDataObject, public IDataObjectAsyncCapability {
  RefPtr<mozilla::LazyIdleThread> mIOThread;

 public:  // construction, destruction
  explicit nsDataObj(nsIURI* uri = nullptr);

 protected:
  virtual ~nsDataObj();

 public:  // IUnknown methods - see iunknown.h for documentation
  STDMETHODIMP_(ULONG) AddRef() override;
  STDMETHODIMP QueryInterface(REFIID, void**) override;
  STDMETHODIMP_(ULONG) Release() override;

  // support for clipboard
  virtual void AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE);
  void SetTransferable(nsITransferable* aTransferable);

 public:  // IDataObject methods - these are general comments. see CfDragDrop
          // for overriding behavior
  // Store data in pSTM according to the format specified by pFE, if the
  // format is supported (supported formats are specified in CfDragDrop::
  // GetFormats) and return NOERROR; otherwise return DATA_E_FORMATETC. It
  // is the callers responsibility to free pSTM if NOERROR is returned.
  STDMETHODIMP GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM) override;

  // Similar to GetData except that the caller allocates the structure
  // referenced by pSTM.
  STDMETHODIMP GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) override;

  // Returns S_TRUE if this object supports the format specified by pSTM,
  // S_FALSE otherwise.
  STDMETHODIMP QueryGetData(LPFORMATETC pFE) override;

  // Set pCanonFE to the canonical format of pFE if one exists and return
  // NOERROR, otherwise return DATA_S_SAMEFORMATETC. A canonical format
  // implies an identical rendering.
  STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC pFE,
                                     LPFORMATETC pCanonFE) final;

  // Set this objects data according to the format specified by pFE and
  // the storage medium specified by pSTM and return NOERROR, if the format
  // is supported. If release is TRUE this object must release the storage
  // associated with pSTM.
  STDMETHODIMP SetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM,
                       BOOL release) override;

  // Set ppEnum to an IEnumFORMATETC object which will iterate all of the
  // data formats that this object supports. direction is either DATADIR_GET
  // or DATADIR_SET.
  STDMETHODIMP EnumFormatEtc(DWORD direction, LPENUMFORMATETC* ppEnum) final;

  // Set up an advisory connection to this object based on the format specified
  // by pFE, flags, and the pAdvise. Set pConn to the established advise
  // connection.
  STDMETHODIMP DAdvise(LPFORMATETC pFE, DWORD flags, LPADVISESINK pAdvise,
                       DWORD* pConn) final;

  // Turn off advising of a previous call to DAdvise which set pConn.
  STDMETHODIMP DUnadvise(DWORD pConn) final;

  // Set ppEnum to an IEnumSTATDATA object which will iterate over the
  // existing objects which have established advisory connections to this
  // object.
  STDMETHODIMP EnumDAdvise(LPENUMSTATDATA* ppEnum) final;

  // IDataObjectAsyncCapability methods
  STDMETHODIMP EndOperation(HRESULT hResult, IBindCtx* pbcReserved,
                            DWORD dwEffects) final;
  STDMETHODIMP GetAsyncMode(BOOL* pfIsOpAsync) final;
  STDMETHODIMP InOperation(BOOL* pfInAsyncOp) final;
  STDMETHODIMP SetAsyncMode(BOOL fDoOpAsync) final;
  STDMETHODIMP StartOperation(IBindCtx* pbcReserved) final;

 private:  // other methods
  // Gets the filename from the kFilePromiseURLMime flavour
  HRESULT GetDownloadDetails(nsIURI** aSourceURI, nsAString& aFilename);

  // help determine the kind of drag
  bool IsFlavourPresent(const char* inFlavour);

 protected:
  HRESULT GetFile(FORMATETC& aFE, STGMEDIUM& aSTG);
  HRESULT GetText(const nsACString& aDF, FORMATETC& aFE, STGMEDIUM& aSTG);

 private:
  enum class DibType { Bmp, Png };
  HRESULT GetDib(const nsACString& inFlavor, FORMATETC&, STGMEDIUM& aSTG,
                 DibType aDibType);

  HRESULT DropImage(FORMATETC& aFE, STGMEDIUM& aSTG);
  HRESULT DropFile(FORMATETC& aFE, STGMEDIUM& aSTG);
  HRESULT DropTempFile(FORMATETC& aFE, STGMEDIUM& aSTG);

  HRESULT GetUniformResourceLocator(FORMATETC& aFE, STGMEDIUM& aSTG,
                                    bool aIsUnicode);
  HRESULT ExtractUniformResourceLocatorA(FORMATETC& aFE, STGMEDIUM& aSTG);
  HRESULT ExtractUniformResourceLocatorW(FORMATETC& aFE, STGMEDIUM& aSTG);
  HRESULT GetFileDescriptor(FORMATETC& aFE, STGMEDIUM& aSTG, bool aIsUnicode);

 protected:
  HRESULT GetFileContents(FORMATETC& aFE, STGMEDIUM& aSTG);

 private:
  HRESULT GetPreferredDropEffect(FORMATETC& aFE, STGMEDIUM& aSTG);

  // Provide the structures needed for an internet shortcut by the shell
  HRESULT GetFileDescriptorInternetShortcutA(FORMATETC& aFE, STGMEDIUM& aSTG);
  HRESULT GetFileDescriptorInternetShortcutW(FORMATETC& aFE, STGMEDIUM& aSTG);
  HRESULT GetFileContentsInternetShortcut(FORMATETC& aFE, STGMEDIUM& aSTG);

  // IStream implementation
  HRESULT GetFileDescriptor_IStreamA(FORMATETC& aFE, STGMEDIUM& aSTG);
  HRESULT GetFileDescriptor_IStreamW(FORMATETC& aFE, STGMEDIUM& aSTG);
  HRESULT GetFileContents_IStream(FORMATETC& aFE, STGMEDIUM& aSTG);

  nsresult ExtractShortcutURL(nsString& outURL);
  nsresult ExtractShortcutTitle(nsString& outTitle);

  // munge our HTML data to win32's CF_HTML spec. Will null terminate
  nsresult BuildPlatformHTML(const char* inOurHTML, char** outPlatformHTML);

  // Used for the SourceURL part of CF_HTML
  nsCString mSourceURL;

 protected:
  BOOL FormatsMatch(const FORMATETC& source, const FORMATETC& target) const;

  ULONG m_cRef;  // the reference count

 private:
  nsTArray<nsCString> mDataFlavors;

  // the nsITransferable knows nothing about the nsDataObj
  RefPtr<nsITransferable> mTransferable;

 protected:
  CEnumFormatEtc* m_enumFE;  // Ownership Rules:
                             // nsDataObj owns and ref counts CEnumFormatEtc,

 private:
  nsCOMPtr<nsIFile> mCachedTempFile;
  RefPtr<nsDataObj> mKeepAlive;

  BOOL mIsAsyncMode;
  BOOL mIsInOperation;
  ///////////////////////////////////////////////////////////////////////////////
  // CStream class implementation
  // this class is used in Drag and drop with download sample
  // called from IDataObject::GetData
  class CStreamBase : public IStream {
    // IStream
    STDMETHODIMP Clone(IStream** ppStream) final;
    STDMETHODIMP Commit(DWORD dwFrags) final;
    STDMETHODIMP CopyTo(IStream* pDestStream, ULARGE_INTEGER nBytesToCopy,
                        ULARGE_INTEGER* nBytesRead,
                        ULARGE_INTEGER* nBytesWritten) final;
    STDMETHODIMP LockRegion(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes,
                            DWORD dwFlags) final;
    STDMETHODIMP Revert(void) final;
    STDMETHODIMP Seek(LARGE_INTEGER nMove, DWORD dwOrigin,
                      ULARGE_INTEGER* nNewPos) final;
    STDMETHODIMP SetSize(ULARGE_INTEGER nNewSize) final;
    STDMETHODIMP UnlockRegion(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes,
                              DWORD dwFlags) final;
    STDMETHODIMP Write(const void* pvBuffer, ULONG nBytesToRead,
                       ULONG* nBytesRead) final;

   protected:
    uint32_t mStreamRead;

    CStreamBase();
    virtual ~CStreamBase();
  };

  class CStream final : public CStreamBase, public nsIStreamListener {
    nsCOMPtr<nsIChannel> mChannel;
    FallibleTArray<uint8_t> mChannelData;
    nsresult mChannelResult;
    bool mChannelRead;

    virtual ~CStream();
    nsresult WaitForCompletion();

    // IUnknown
    STDMETHOD(QueryInterface)(REFIID refiid, void** ppvResult) final;

    // IStream
    STDMETHODIMP Read(void* pvBuffer, ULONG nBytesToRead,
                      ULONG* nBytesRead) final;
    STDMETHODIMP Stat(STATSTG* statstg, DWORD dwFlags) final;

   public:
    CStream();
    nsresult Init(nsIURI* pSourceURI, nsContentPolicyType aContentPolicyType,
                  nsIPrincipal* aRequestingPrincipal,
                  nsICookieJarSettings* aCookieJarSettings,
                  nsIReferrerInfo* aReferrerInfo);

    NS_DECL_ISUPPORTS
    NS_DECL_NSIREQUESTOBSERVER
    NS_DECL_NSISTREAMLISTENER
  };

  HRESULT CreateStream(IStream** outStream);

  // This class must be thread-safe.
  class AutoCloseEvent final {
    const nsAutoHandle mEvent;

    AutoCloseEvent(const AutoCloseEvent&) = delete;
    void operator=(const AutoCloseEvent&) = delete;
    ~AutoCloseEvent() = default;

   public:
    AutoCloseEvent();
    bool IsInited() const;
    void Signal() const;
    DWORD Wait(DWORD aMillisec) const;
    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AutoCloseEvent)
  };

  // This class must be thread-safe.
  class AutoSetEvent final {
    const RefPtr<AutoCloseEvent> mEvent;

    AutoSetEvent(const AutoSetEvent&) = delete;
    void operator=(const AutoSetEvent&) = delete;
    ~AutoSetEvent();

   public:
    explicit AutoSetEvent(mozilla::NotNull<AutoCloseEvent*> aEvent);
    void Signal() const;
    bool IsWaiting() const;
    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AutoSetEvent)
  };

  // This class must be thread-safe.
  class CMemStream final : public CStreamBase {
    static mozilla::glue::Win32SRWLock mLock;
    const nsAutoGlobalMem mGlobalMem;
    const RefPtr<AutoCloseEvent> mEvent;
    const uint32_t mTotalLength;
    RefPtr<IUnknown> mMarshaler;

    virtual ~CMemStream();
    void WaitForCompletion();

    // IStream
    STDMETHODIMP Read(void* pvBuffer, ULONG nBytesToRead,
                      ULONG* nBytesRead) final;
    STDMETHODIMP Stat(STATSTG* statstg, DWORD dwFlags) final;

   public:
    CMemStream(nsHGLOBAL aGlobalMem, uint32_t mTotalLength,
               already_AddRefed<AutoCloseEvent> aEvent);

    // IUnknown
    STDMETHOD(QueryInterface)(REFIID refiid, void** ppvResult) final;
    NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(CMemStream, final)
  };

 private:
  // Drag and drop helper data for implementing drag and drop image support
  typedef struct {
    FORMATETC fe;
    STGMEDIUM stgm;
  } DATAENTRY, *LPDATAENTRY;

  nsTArray<LPDATAENTRY> mDataEntryList;
  nsCOMPtr<nsITimer> mTimer;

  bool LookupArbitraryFormat(FORMATETC* aFormat, LPDATAENTRY* aDataEntry,
                             BOOL aAddorUpdate);
  bool CopyMediumData(STGMEDIUM* aMediumDst, STGMEDIUM* aMediumSrc,
                      LPFORMATETC aFormat, BOOL aSetData);
};

#endif  // _NSDATAOBJ_H_
