/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 */
#include "common.h"
#include <shlobj.h>
#pragma warning(disable : 4675)
#include <hash_map>
#include <hash_set>
#include "com_sun_glass_ui_win_WinSystemClipboard.h"
#include "com_sun_glass_ui_win_WinDndClipboard.h"
#include "GlassApplication.h"
#include "GlassClipboard.h"
#include "GlassDnD.h"
#include "Pixels.h"

// Helper LEAVE_MAIN_THREAD for GlassClipboard
#define LEAVE_MAIN_THREAD_WITH_p  \
    IDataObject * p;  \
    LEAVE_MAIN_THREAD;  \
    ARG(p) = getPtr(env, obj);

jfieldID fidPtr = 0;
static jfieldID fidName = 0;
static jmethodID midFosSerialize = 0;
jmethodID midContentChanged = 0;
jmethodID midActionPerformed = 0;
#define GALLOCFLG (GMEM_DDESHARE | GMEM_MOVEABLE | GMEM_ZEROINIT)



bool operator < (const FORMATETC &fr, const FORMATETC &fl) {
    if (fr.cfFormat != fl.cfFormat)
        return fr.cfFormat < fl.cfFormat;
    if (fr.dwAspect != fl.dwAspect)
        return fr.dwAspect < fl.dwAspect;
    if (fr.lindex != fl.lindex)
        return fr.lindex < fl.lindex;
    if (fr.ptd != fl.ptd)
        return fr.ptd < fl.ptd;
    return fr.tymed < fl.tymed;
}

bool operator == (const FORMATETC &fr, const FORMATETC &fl) {
    return  fr.cfFormat == fl.cfFormat
         && fr.dwAspect == fl.dwAspect
         && fr.lindex   == fl.lindex
         && fr.ptd      == fl.ptd
         && fr.tymed    == fl.tymed;
}

size_t hash_value(const FORMATETC &fr)
{
    size_t _Val = size_t(fr.cfFormat) << 21;
    _Val += size_t(fr.dwAspect);
    _Val <<= 5;
    _Val += size_t(fr.lindex);
    _Val <<= 7;
    _Val += size_t(fr.ptd);
    _Val >>= 13;
    _Val += size_t(fr.tymed);
    return _Val ^ _HASH_SEED;
}

struct Mime2oscfstrPair {
    LPCWSTR mime;
    LPCWSTR osString;
};

static LPCWSTR PASTE_SUCCEEDED = L"ms-staff/paste-succeeded";
static LPCWSTR PREFERRED_DROP_EFFECT_MIME = L"ms-staff/preferred-drop-effect";
static LPCWSTR PERFORMED_DROP_EFFECT_MIME = L"ms-staff/performed-drop-effect";
static LPCWSTR GLASS_IMAGE = L"application/x-java-rawimage";
static LPCWSTR GLASS_URI_LIST = L"text/uri-list";

Mime2oscfstrPair pairs[] = {
    {L"text/html", L"HTML Format"},
    {L"text/rtf", L"Rich Text Format"},
    {GLASS_URI_LIST, CFSTR_INETURL},
    {PASTE_SUCCEEDED, CFSTR_PASTESUCCEEDED},
    {PERFORMED_DROP_EFFECT_MIME, CFSTR_PERFORMEDDROPEFFECT},
    {PREFERRED_DROP_EFFECT_MIME, CFSTR_PREFERREDDROPEFFECT},
    {L"ms-staff/file-descriptor", CFSTR_FILEDESCRIPTORA},
    {L"ms-staff/file-descriptor-unicode", CFSTR_FILEDESCRIPTORW},
    {L"ms-staff/file-content", CFSTR_FILECONTENTS},
};

inline size_t hash_value(const _bstr_t &_Str) {
    return stdext::hash_value((const wchar_t *)_Str);
}


typedef stdext::hash_map<_bstr_t, CLIPFORMAT> MIME2OSCF;
typedef stdext::hash_map<CLIPFORMAT, _bstr_t> OSCF2MIME;
typedef stdext::hash_map<FORMATETC, _bstr_t> FMC2MIME;
typedef stdext::hash_map<FORMATETC, STGMEDIUM> FMC2DATA;
typedef stdext::hash_set<_bstr_t> HASH_STR_SET;

MIME2OSCF mime2oscf;
OSCF2MIME oscf2mime;

void addPair(LPCWSTR mime, const CLIPFORMAT &cf) {
    mime2oscf[mime] = cf;
    oscf2mime[cf] = mime;
}

CLIPFORMAT getClipboardFormat(LPCWSTR mime)
{
    MIME2OSCF::const_iterator i = mime2oscf.find(mime);
    if (mime2oscf.end() == i) {
        CLIPFORMAT cf = ::RegisterClipboardFormat(mime);
        addPair(mime, cf);
        return cf;
    }
    return i->second;
}

_bstr_t getMime(CLIPFORMAT cf)
{
    static _bstr_t empty;
    OSCF2MIME::const_iterator i = oscf2mime.find(cf);
    if (oscf2mime.end() == i) {
        const size_t LEN = 512;
        TCHAR str[LEN] = {0};

        int res = ::GetClipboardFormatName(cf, str, LEN);

        if (res > 0 && res <= LEN) {
            return _bstr_t(str);
        } else {
            STRACE(_T("Error calling GetClipboardFormatName: ret=%i    LEN=%i    lastError=%08x"), res, LEN, ::GetLastError());
            return empty;
        }
    }
    return i->second;
}

#define CF_JAVA_BITMAP CF_DIB

int create_mime_staff()
{
    addPair(L"text/plain", CF_UNICODETEXT);
    addPair(GLASS_IMAGE, CF_JAVA_BITMAP);
    addPair(L"application/x-java-file-list", CF_HDROP);
    Mime2oscfstrPair *p = pairs;
    for(int i=0; i<sizeof(pairs)/sizeof(*pairs); ++i, ++p) {
        addPair(p->mime, ::RegisterClipboardFormat(p->osString));
    }
    return 1;
}

static int _init_mime_staff = create_mime_staff();

static jint ACTIONS[] = {
    com_sun_glass_ui_win_WinSystemClipboard_ACTION_COPY,
    com_sun_glass_ui_win_WinSystemClipboard_ACTION_MOVE,
    com_sun_glass_ui_win_WinSystemClipboard_ACTION_REFERENCE
};

static DROPEFFECT DFS[] = {
    DROPEFFECT_COPY,
    DROPEFFECT_MOVE,
    DROPEFFECT_LINK
};

DROPEFFECT getDROPEFFECT(jint actions)
{
    DROPEFFECT ret = DROPEFFECT_NONE;
    for (size_t i = 0; i < sizeof(ACTIONS)/sizeof(*ACTIONS); ++i) {
        if (actions & ACTIONS[i]) {
            ret |= DFS[i];
        }
    }
    return ret;
}

jint getACTION(DROPEFFECT df)
{
    jint  ret = com_sun_glass_ui_win_WinSystemClipboard_ACTION_NONE;
    for (size_t i = 0; i < sizeof(DFS)/sizeof(*DFS); ++i) {
        if (df & DFS[i]) {
            ret |= ACTIONS[i];
        }
    }
    return ret;
}

HRESULT PopMemory(
    IN JNIEnv *env,
    IN CLIPFORMAT cf,
    IN jlong lindex,
    IN IDataObject *p,
    IN OUT jbyteArray *pret)
{
    OLE_DECL
    FORMATETC fmt = {
        cf,
        NULL,
        DVASPECT_CONTENT,
        LONG(lindex),
        TYMED_HGLOBAL};
    STGMEDIUM data = {0};
    OLE_HR = p->GetData(&fmt, &data);
    if( SUCCEEDED(OLE_HR) ) {
        //ordinal treatment with direct conversion
        if (TYMED_HGLOBAL==data.tymed && NULL!=data.hGlobal) {
            jbyte *pdata = (jbyte *)::GlobalLock(data.hGlobal);
            if (NULL != pdata) {
                jsize cdata = jsize(::GlobalSize(data.hGlobal));
                jsize offset = 0L;
                if (CF_HDROP == cf) {
                    offset = sizeof(DROPFILES);
                    cdata -= offset;
                    DROPFILES *dropfiles = (DROPFILES *)pdata;
                    if( !dropfiles->fWide ) {
                        //ASCII file names aren't supported
                        cdata = 0;
                    }
                }
                if (0 != cdata) {
                    *pret = env->NewByteArray(cdata);
                    if (NULL != *pret) {
                        env->SetByteArrayRegion(*pret, 0, cdata, pdata + offset);
                    }
                }
                GlobalUnlock(data.hGlobal);
            }
        }
        ReleaseStgMedium(&data);
    }
    OLE_RETURN_HR
}

#define HIMETRIC_INCH   2540    // HIMETRIC units per inch
#define	BSWAP_32(x)	(((DWORD)(x) << 24) | \
    (((DWORD)(x) << 8) & 0xff0000) | \
    (((DWORD)(x) >> 8) & 0xff00) | \
    ((DWORD)(x)  >> 24))

HRESULT PopImage(
    IN JNIEnv *env,
    IN IDataObject *p,
    IN OUT jbyteArray *pret)
{
    //image extractor
    STRACE(_T("image extractor"));

    OLE_TRY
    IStoragePtr spStorage;
    OLE_HRT( ::StgCreateDocfile(
        NULL,
        STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_CREATE,
        NULL,
        &spStorage))

    IViewObject2Ptr view;
    OLE_HRT(::OleCreateStaticFromData(
        p,
        IID_IViewObject2,
        OLERENDER_DRAW,
        NULL,
        NULL,
        spStorage,
        (LPVOID *)&view))

    if (view) {
        SIZEL size = {0};
        IOleObjectPtr obj(view);
        //This method retrieves the display size in HIMETRIC units (0.01 millimeter per unit)
        OLE_HRT(obj->GetExtent(
            DVASPECT_CONTENT,
            &size))

        //Below OLE_HRT macro is forbidden because we have not auto-dispose wrappers over
        //the system handlers.
        HDC hMemoryDC = ::CreateCompatibleDC(NULL);
        if (!hMemoryDC) {
            OLE_REPORT_ERR(_T("CreateCompatibleDC"))
        } else {
            int cxPerInch = GetDeviceCaps(hMemoryDC, LOGPIXELSX);
            int cyPerInch = GetDeviceCaps(hMemoryDC, LOGPIXELSY);
            size.cx = MulDiv(size.cx, cxPerInch, HIMETRIC_INCH);
            size.cy = MulDiv(size.cy, cyPerInch, HIMETRIC_INCH);

            BITMAPINFO    bitmapInfo = {0};
            bitmapInfo.bmiHeader.biWidth = size.cx;
            bitmapInfo.bmiHeader.biHeight = -size.cy;
            bitmapInfo.bmiHeader.biPlanes = 1;
            bitmapInfo.bmiHeader.biBitCount = 32;
            bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
            bitmapInfo.bmiHeader.biCompression = BI_RGB;

            jbyte *pPoints = NULL;
            HBITMAP hBM = ::CreateDIBSection(
                hMemoryDC,
                (BITMAPINFO *)&bitmapInfo,
                DIB_RGB_COLORS,
                (void **)&pPoints,
                NULL,
                0);
            if (!hBM) {
                OLE_REPORT_ERR(_T("CreateDIBSection"))
            } else {
                HBITMAP hOldBM = SelectBitmap(hMemoryDC, hBM);
                if (!hOldBM) {
                    OLE_REPORT_ERR(_T("SelectBitmap"))
                } else {
                    RECTL rc = {0, 0, size.cx, size.cy};
                    OLE_HR = view->Draw(
                        DVASPECT_CONTENT,
                        -1,
                        NULL,
                        NULL,
                        NULL,
                        hMemoryDC,
                        &rc,
                        &rc,
                        NULL,
                        0);
                    if (FAILED(OLE_HR)) {
                        STRACE(_T("view->Draw Error:%08x"), OLE_HR);
                    } else {
                        jsize cdata = jsize(size.cx) * jsize(size.cy) * 4 + 8;
                        *pret = env->NewByteArray(cdata);
                        if (NULL != *pret) {
                            DWORD w = BSWAP_32(size.cx);
                            DWORD h = BSWAP_32(size.cy);
                            env->SetByteArrayRegion(*pret, 0, 4, (jbyte *)&w);
                            env->SetByteArrayRegion(*pret, 4, 4, (jbyte *)&h);
                            env->SetByteArrayRegion(*pret, 8, cdata - 8, pPoints);
                        }
                    }
                    SelectBitmap(hMemoryDC, hOldBM);
                }
                ::DeleteObject(hBM);
            }
            ::DeleteDC(hMemoryDC);
        }
        STRACE(_T("IViewObject size: %08x %08x"), size.cx, size.cy);
    }
    OLE_CATCH
    OLE_RETURN_HR
}

HRESULT PushImage(
    IN JNIEnv *env,
    IN jbyteArray data,
    IN OUT STGMEDIUM *psm)
{
    OLE_TRY
    jint cdata = env->GetArrayLength(data);
    if (cdata < 8) {
        OLE_HRT(E_INVALIDARG)
    }
    jint w, h;
    env->GetByteArrayRegion(data, 0, 4, (jbyte *)&w);
    env->GetByteArrayRegion(data, 4, 4, (jbyte *)&h);
    w = BSWAP_32(w);
    h = BSWAP_32(h);
    int numPixels = w*h;
    OLE_HRT(checkJavaException(env))
    if (cdata < (numPixels*4 + 8)) {
        OLE_HRT(E_INVALIDARG)
    }
    jbyte *pBytes;
    Bitmap bitmap(w, h, (void **)&pBytes);
    OLE_CHECK_NOTNULL((HBITMAP)bitmap)
    env->GetByteArrayRegion(data, 8, numPixels*4, pBytes);
    OLE_HRT(checkJavaException(env))

    psm->hGlobal = bitmap.GetGlobalDIB();
    psm->tymed = TYMED_HGLOBAL;
    OLE_CATCH
    OLE_RETURN_HR
}


class ClipboardData : public IUnknownImpl<IDataObject>
{
public:
    ClipboardData(JNIEnv *env, jobject clipboard, jstring name)
    : m_name(env, name),
      m_jclipboard(env->NewGlobalRef(clipboard))
    {
        STRACE(_T("{Clipboard %s"), (LPCWSTR)m_name);
    }

    virtual ~ClipboardData()
    {
        if (m_jclipboard) {
            JNIEnv* env = GetEnv();
            env->DeleteGlobalRef(m_jclipboard);
            m_jclipboard = NULL;
        }
        for(FMC2DATA::iterator i = m_fmc2data.begin(); m_fmc2data.end() != i; ++i) {
            ReleaseStgMedium(&i->second);
        }
        STRACE(_T("}Clipboard %s"), (LPCWSTR)m_name);
    }

    HRESULT pushCommit(JNIEnv *env, jobjectArray keys, jint supportedActions) {
        jint ckeys = env->GetArrayLength(keys);
        for(jsize i = 0; i < ckeys; ++i) {
            JString mime(env, (jstring)env->GetObjectArrayElement(keys, i));
            CLIPFORMAT cf = getClipboardFormat(mime);
            FORMATETC fmt = {
                cf,
                NULL,
                DVASPECT_CONTENT,
                -1L,
                TYMED_HGLOBAL};
            STGMEDIUM data = {0};
            m_fmc2data[fmt] = data;
            m_fmc2mime[fmt] = (LPCWSTR)mime;
        }
        if (com_sun_glass_ui_win_WinSystemClipboard_ACTION_ANY != supportedActions) {
            static FORMATETC fmt = {
                getClipboardFormat(PREFERRED_DROP_EFFECT_MIME),
                NULL,
                DVASPECT_CONTENT,
                -1L,
                TYMED_HGLOBAL};
            STGMEDIUM data = {TYMED_HGLOBAL, 0};
            data.hGlobal = ::GlobalAlloc(GALLOCFLG, sizeof(DROPEFFECT));
            if (NULL == data.hGlobal) {
                return E_OUTOFMEMORY;
            }
            *((DROPEFFECT *)::GlobalLock(data.hGlobal)) = getDROPEFFECT(supportedActions);
            ::GlobalUnlock(data.hGlobal);
            m_fmc2data[fmt] = data;
            m_fmc2mime[fmt] = PREFERRED_DROP_EFFECT_MIME;
        }
        return S_OK;
    }

    HRESULT checkMedium(FORMATETC *pformatetcIn, STGMEDIUM **ppmedium, _bstr_t *pmime) {
        if (NULL == pformatetcIn) {
            return E_POINTER;
        }
        FMC2DATA::iterator i = m_fmc2data.find(*pformatetcIn);
        if (m_fmc2data.end() == i) {
            FORMATETC fmt = {
                pformatetcIn->cfFormat,
                NULL,
                DVASPECT_CONTENT,
                pformatetcIn->lindex,
                TYMED_HGLOBAL};
            i = m_fmc2data.find(fmt);
            if (m_fmc2data.end() == i) {
                STRACE(_T("Decline Clipboard request for CF=%08x"), pformatetcIn->cfFormat);
                return DV_E_FORMATETC;
            }
        }
        *ppmedium = &i->second;
        *pmime = m_fmc2mime[i->first];
        STRACE(_T("Accept Clipboard request for CF=%08x"), pformatetcIn->cfFormat);
        return S_OK;
    }

public:
    STDMETHOD(GetData)(FORMATETC *pformatetcIn, STGMEDIUM *pmedium)
    {
        if (NULL == pmedium || NULL == pformatetcIn) {
            return E_POINTER;
        }
        OLE_TRY
        STGMEDIUM *psm = NULL;
        _bstr_t mime;
        if (FAILED(OLE_HR = checkMedium(pformatetcIn, &psm, &mime))) {
            //that kind of fail is OK!
            OLE_RETURN_HR
        }
        if (0 == psm->tymed) {
            JNIEnv *env = GetEnv();
            //callback java
            jbyteArray data = (jbyteArray)env->CallObjectMethod(m_jclipboard, midFosSerialize,
                jstring(JLString(env, CreateJString(env, (LPCWSTR)mime))),
                jlong(pformatetcIn->lindex));
            OLE_HRT(checkJavaException(env))
            OLE_CHECK_NOTNULL(data)

            if (CF_JAVA_BITMAP == pformatetcIn->cfFormat) {
                OLE_HRT(PushImage(env, data, psm))
            } else {
                jint cdata = env->GetArrayLength(data);
                psm->hGlobal = ::GlobalAlloc(GALLOCFLG, cdata*sizeof(jbyte)
                    + ((pformatetcIn->cfFormat == CF_HDROP) ? sizeof(DROPFILES) : 0));
                OLE_CHECK_NOTNULL(psm->hGlobal);
                jbyte *dataout = (jbyte *)::GlobalLock(psm->hGlobal);
                if (CF_HDROP == pformatetcIn->cfFormat) {
                    DROPFILES *dropfiles = (DROPFILES *)dataout;
                    dropfiles->pFiles = sizeof(DROPFILES);
                    dropfiles->fWide = TRUE;
                    dataout += sizeof(DROPFILES);
                }
                env->GetByteArrayRegion(data, 0, cdata, dataout);
                ::GlobalUnlock(psm->hGlobal);
                psm->tymed = TYMED_HGLOBAL;
            }
        }
        *pmedium = *psm;
        //[POSTPONED RELEASE]
        pmedium->pUnkForRelease = this;
        AddRef();
        OLE_CATCH
        OLE_RETURN_HR
    }

    STDMETHOD(GetDataHere)(FORMATETC *pformatetcIn, STGMEDIUM *pmedium)
    {
        if (NULL == pformatetcIn || NULL == pmedium) {
            return E_POINTER;
        }
        STGMEDIUM sm = {0};
        OLE_TRY
        OLE_HRT(GetData(pformatetcIn, &sm))
        IUnknown *p = sm.pUnkForRelease;
        sm.pUnkForRelease = NULL;
        OLE_HRT(CopyStgMedium(&sm, pmedium))
        sm.pUnkForRelease = p;
        ReleaseStgMedium(&sm);
        OLE_CATCH
        OLE_RETURN_HR
    }

    STDMETHOD(QueryGetData)(FORMATETC* pformatetc)
    {
        STGMEDIUM *psm = NULL;
        _bstr_t mime;
        return checkMedium(pformatetc, &psm, &mime);
    }

    STDMETHOD(GetCanonicalFormatEtc)(FORMATETC *pformatectIn, FORMATETC *pformatetcOut)
    {
        STGMEDIUM *psm = NULL;
        _bstr_t mime;
        HRESULT hr = checkMedium(pformatectIn, &psm, &mime);
        if (S_OK == hr) {
            *pformatetcOut = *pformatectIn;
            hr = DATA_S_SAMEFORMATETC;
        }
        return hr;
    }

    STDMETHOD(SetData)(FORMATETC *pformatetc, STGMEDIUM *pmedium, BOOL fRelease)
    {
        STGMEDIUM sm = {0};
        OLE_TRY
        if (!fRelease) {
            OLE_HRT(CopyStgMedium(pmedium, &sm))
            pmedium = &sm;
        }
        m_fmc2data[*pformatetc] = *pmedium;
        static CLIPFORMAT cf = getClipboardFormat(PERFORMED_DROP_EFFECT_MIME);
        if (pformatetc->cfFormat == cf) {
            OLE_CHECK_NOTNULL(pmedium->hGlobal)
            DROPEFFECT *pDF = (DROPEFFECT *)::GlobalLock(pmedium->hGlobal);
            if (NULL != pDF) {
                GetEnv()->CallVoidMethod(m_jclipboard, midActionPerformed, getACTION(*pDF));
                OLE_HRT(checkJavaException(GetEnv()));
            }
            GlobalUnlock(pmedium->hGlobal);
        }
        OLE_CATCH
        OLE_RETURN_HR
    }

    STDMETHOD(EnumFormatEtc)(DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc)
    {
        if (DATADIR_SET == dwDirection) {
            return E_NOTIMPL;
        }
        *ppenumFormatEtc = new ClipboardEnumFORMATETC(this);
        return S_OK;
    }

    STDMETHOD(DAdvise)(FORMATETC *pformatetc, DWORD advf, IAdviseSink *pAdvSink,
        DWORD *pdwConnection)
    {
        OLE_TRY
        if (!bool(m_spDataAdviseHolder)) {
            OLE_HRT(CreateDataAdviseHolder(&m_spDataAdviseHolder))
        }
        OLE_HRT(m_spDataAdviseHolder->Advise((IDataObject*)this, pformatetc, advf, pAdvSink, pdwConnection))
        OLE_CATCH
        OLE_RETURN_HR
    }

    STDMETHOD(DUnadvise)(DWORD dwConnection)
    {
        OLE_TRY
        if (!bool(m_spDataAdviseHolder))
            return OLE_E_NOCONNECTION;
        OLE_HRT(m_spDataAdviseHolder->Unadvise(dwConnection))
        OLE_CATCH
        OLE_RETURN_HR
    }

    STDMETHOD(EnumDAdvise)(IEnumSTATDATA **ppenumAdvise)
    {
        if (ppenumAdvise == NULL)
            return E_POINTER;
        *ppenumAdvise = NULL;
        if (m_spDataAdviseHolder)
            return m_spDataAdviseHolder->EnumAdvise(ppenumAdvise);
        return E_FAIL;
    }

protected:
    IDataAdviseHolderPtr m_spDataAdviseHolder;
    JString m_name;
    jobject m_jclipboard;
    FMC2MIME m_fmc2mime;
    FMC2DATA m_fmc2data;

    class ClipboardEnumFORMATETC: public IUnknownImpl<IEnumFORMATETC>
    {
    private:
        ClipboardEnumFORMATETC(ClipboardData *owner, FMC2DATA::iterator pos)
        : m_owner(owner),
          m_pos(pos)
        {
            m_owner->AddRef();
        }

    public:
        ClipboardEnumFORMATETC(ClipboardData *owner)
        : m_owner(owner)
        {
            m_owner->AddRef();
            Reset();
        }


        virtual ~ClipboardEnumFORMATETC() {
            m_owner->Release();
        }

        STDMETHOD(Next)(ULONG celt, FORMATETC *rgelt, ULONG *pceltFetched) {
            ULONG i = 0;
            for(; i < celt && m_owner->m_fmc2data.end() != m_pos ; ++i, ++m_pos) {
                *rgelt++ = m_pos->first;
            }
            if (pceltFetched) {
                *pceltFetched = i;
            }
            return (i == celt)
                ? S_OK
                : S_FALSE;

        }

        STDMETHOD(Skip)(ULONG celt) {
            ULONG i = 0;
            for(; i < celt && m_owner->m_fmc2data.end() != m_pos ; ++i, ++m_pos) ;
            return (i == celt)
                ? S_OK
                : S_FALSE;
        }

        STDMETHOD(Reset)() {
            m_pos = m_owner->m_fmc2data.begin();
            return S_OK;
        }

        STDMETHOD(Clone)(IEnumFORMATETC **ppenum) {
            *ppenum = new ClipboardEnumFORMATETC(m_owner, m_pos);
            return S_OK;
        }
   protected:
        ClipboardData *m_owner;
        FMC2DATA::iterator m_pos;
    };
};

extern "C" {

/*
* Class:     com_sun_glass_ui_win_WinSystemClipboard
* Method:    initIDs
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinSystemClipboard_initIDs
    (JNIEnv *env, jclass cls)
{
    fidPtr = env->GetFieldID(cls, "ptr", "J");
    fidName = env->GetFieldID(cls, "name", "Ljava/lang/String;");
    midFosSerialize = env->GetMethodID(cls, "fosSerialize", "(Ljava/lang/String;J)[B");
    midContentChanged = env->GetMethodID(cls, "contentChanged", "()V");
    midActionPerformed = env->GetMethodID(cls, "actionPerformed", "(I)V");
}

/*
* Class:     com_sun_glass_ui_win_WinSystemClipboard
* Method:    isOwner
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_win_WinSystemClipboard_isOwner
    (JNIEnv *env, jobject obj)
{
    ENTER_MAIN_THREAD_AND_RETURN(jboolean)
    {
        if (NULL == p) {
            return false;
        }
        return S_OK == ::OleIsCurrentClipboard(p);
    }
    LEAVE_MAIN_THREAD_WITH_p;

    return PERFORM_AND_RETURN();
}

/*
 * Class:     com_sun_glass_ui_win_WinSystemClipboard
 * Method:    create
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinSystemClipboard_create
  (JNIEnv *env, jobject obj)
{
    ENTER_MAIN_THREAD()
    {
        GlassApplication::GetInstance()->RegisterClipboardViewer(obj);
    }
    DECL_jobject(obj);
    LEAVE_MAIN_THREAD;

    ARG(obj) = obj;
    PERFORM();
}

void OLE_CoPump()
{
    MSG msg;
    while( ::PeekMessage(&msg,NULL,0,0,PM_REMOVE) ) {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }
}

/*
* Class:     com_sun_glass_ui_win_WinSystemClipboard
* Method:    dispose
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinSystemClipboard_dispose
    (JNIEnv *env, jobject obj)
{
    ENTER_MAIN_THREAD()
    {
        GlassApplication::GetInstance()->UnregisterClipboardViewer();
        if (NULL != p) {
            OLE_TRY
            OLE_HRT( ::OleIsCurrentClipboard(p) )
            if (S_OK == OLE_HR) {
                for(int i=0; i < 1000; ++i) {
                    OLE_HR = ::OleFlushClipboard();
                    if(CLIPBRD_E_CANT_OPEN==OLE_HR) {
                        OLE_CoPump();
                        continue;
                    }
                    break;
                }
            }
            OLE_CATCH
            p->Release();
            STRACE(_T("System Clipboard Closed"));
        }
    }
    LEAVE_MAIN_THREAD_WITH_p;
    PERFORM();
}


/*
 * Class:     com_sun_glass_ui_win_WinSystemClipboard
 * Method:    push
 * Signature: ([Ljava/lang/Object;)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinSystemClipboard_push
  (JNIEnv *env, jobject obj, jobjectArray keys, jint supportedActions)
{
    ENTER_MAIN_THREAD()
    {
        OLE_TRY
            if (NULL != p) {
                //We need to create new object here due to [POSTPONED RELEASE] algorithm
                //in data provider.
                p->Release();
            }
            JNIEnv* env = GetEnv();
            ClipboardData *pcd = new ClipboardData(
                env,
                obj,
                JLString(env, (jstring)env->GetObjectField(obj, fidName))
                );
            setPtr(env, obj, pcd);
            OLE_HRT( pcd->pushCommit(env, keys, supportedActions) )
            OLE_HRT( ::OleSetClipboard(pcd) )
        OLE_CATCH
    }
    DECL_jobject(obj);
    DECL_JREF(jobjectArray, keys);
    jint supportedActions;
    LEAVE_MAIN_THREAD_WITH_p;

    ARG(obj) = obj;
    ARG(keys) = keys;
    ARG(supportedActions) = supportedActions;
    PERFORM();
}

/*
 * Class:     com_sun_glass_ui_win_WinSystemClipboard
 * Method:    pop
 * Signature: ()Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_win_WinSystemClipboard_pop
  (JNIEnv *env, jobject obj)
{
    ENTER_MAIN_THREAD_AND_RETURN(jboolean)
    {
        if (p) {
            p->Release();
        }
        p = SUCCEEDED( ::OleGetClipboard(&p) ) ? p : NULL;
        JNIEnv* env = GetEnv();
        setPtr(env, obj, p);
        return NULL != p;
    }
    DECL_jobject(obj);
    LEAVE_MAIN_THREAD_WITH_p;
    ARG(obj) = obj;
    return PERFORM_AND_RETURN();
}

/*
* Class:     com_sun_glass_ui_win_WinSystemClipboard
* Method:    popBytes
* Signature: (Ljava/lang/String;J)[B
*/
JNIEXPORT jbyteArray JNICALL Java_com_sun_glass_ui_win_WinSystemClipboard_popBytes
    (JNIEnv *env, jobject obj, jstring jmime, jlong lindex)
{
    ENTER_MAIN_THREAD_AND_RETURN(jbyteArray)
    {
        //So we are here if we are not the owners of the clipboard
        jbyteArray ret = NULL;
        if (NULL != p) {
            OLE_TRY
            JNIEnv* env = GetEnv();
            JString mime(env, jmime);
            if ( 0 == _tcscmp(mime, GLASS_IMAGE) ) {
                //custom conversion for image
                if ( OLE_S_STATIC == ::OleQueryCreateFromData(p)) {
                    OLE_HRT(PopImage(env, p, &ret))
                }
            } else {
                //We don't like to report error here.
                //Fail is ordinal here.
                OLE_HR = PopMemory(
                    env,
                    getClipboardFormat(mime),
                    lindex,
                    p,
                    &ret);
            }
            OLE_CATCH
        }
        return ret;
    }
    DECL_JREF(jstring, jmime);
    jlong lindex;
    LEAVE_MAIN_THREAD_WITH_p;

    ARG(jmime) = jmime;
    ARG(lindex) = lindex;
    return PERFORM_AND_RETURN();
}

/*
* Class:     com_sun_glass_ui_win_WinSystemClipboard
* Method:    mimesFromSystem
* Signature: ()Ljava/util/Set;
*/
JNIEXPORT jobjectArray JNICALL Java_com_sun_glass_ui_win_WinSystemClipboard_popMimesFromSystem
    (JNIEnv *env, jobject obj)
{
    ENTER_MAIN_THREAD_AND_RETURN(jobjectArray)
    {
        //So we are here if we are not the owners of the clipboard
        jobjectArray ret = NULL;
        if (NULL != p) {
            OLE_TRY
            IEnumFORMATETCPtr pos;
            OLE_HRT(p->EnumFormatEtc(DATADIR_GET, &pos))
            FORMATETC fmc;
            HASH_STR_SET mimes;
            while( S_OK == pos->Next(1, &fmc, NULL) ) {
                if (TYMED_HGLOBAL & fmc.tymed) {
                    _bstr_t mime = getMime(fmc.cfFormat);
                    if (mime.length()) {
                        mimes.insert(mime);
                    }
                    if (CF_HDROP == fmc.cfFormat) {
                        //we can convert it to the URL list
                        mimes.insert(GLASS_URI_LIST);
                    }
                }
            }
            if (OLE_S_STATIC==::OleQueryCreateFromData(p)) {
                //we can convert it to the image
                mimes.insert(GLASS_IMAGE);
            }
            jsize cmimes = jsize(mimes.size());
            if (cmimes) {
                JNIEnv * env = GetEnv();
                ret = env->NewObjectArray(
                    cmimes,
                    JLClass(env, env->FindClass("java/lang/String")),
                    NULL);
                if (ret) {
                    jsize index = 0;
                    for (HASH_STR_SET::const_iterator i = mimes.begin(); mimes.end() != i; ++i, ++index) {
                        env->SetObjectArrayElement(ret, index,
                            jstring(JLString(env,
                                CreateJString(env,(LPCWSTR)*i)
                            ))
                        );
                    }
                }
            }
            OLE_CATCH
        }
        return ret;
    }
    LEAVE_MAIN_THREAD_WITH_p;

    return PERFORM_AND_RETURN();
}

//The basic procedure for a delete-on-paste operation is as follows:
//1. The source marks the screen display of the selected data.
//2. The source creates a data object. It indicates a cut operation by adding the
//   CFSTR_PREFERREDDROPEFFECT format with a data value of DROPEFFECT_MOVE.
//3. The source places the data object on the Clipboard using OleSetClipboard.
//4. The target retrieves the data object from the Clipboard using OleGetClipboard.
//5. The target extracts the CFSTR_PREFERREDDROPEFFECT data. If it is set to only
//   DROPEFFECT_MOVE, the target can either do an optimized move or simply copy the data.
//6. If the target does not do an optimized move, it calls the IDataObject::SetData
//   method with the CFSTR_PERFORMEDDROPEFFECT format set to DROPEFFECT_MOVE.
//7. When the paste is complete, the target calls the IDataObject::SetData method
//   with the CFSTR_PASTESUCCEEDED format set to DROPEFFECT_MOVE.
//8. When the source's IDataObject::SetData method is called with
//   the CFSTR_PASTESUCCEEDED format set to DROPEFFECT_MOVE, it must check to see
//   if it also received the CFSTR_PERFORMEDDROPEFFECT format set to DROPEFFECT_MOVE.
//   [!IF BOTH FORMATS ARE SENT BY THE TARGET!], the source will have to delete the data.
//
//If only the CFSTR_PASTESUCCEEDED format is received, the source can simply remove the data
//from its display. If the transfer fails, the source updates the display to its original
//appearance.
//(c) http://msdn.microsoft.com/en-us/library/bb776904%28VS.85%29.aspx

/*
 * Class:     com_sun_glass_ui_win_WinSystemClipboard
 * Method:    pushTargetActionToSystem
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinSystemClipboard_pushTargetActionToSystem
  (JNIEnv *env, jobject obj, jint actionDone)
{
    ENTER_MAIN_THREAD()
    {
        //please, read: http://msdn.microsoft.com/en-us/library/bb776904%28VS.85%29.aspx
        if (NULL != p) {
            //Make it in one step!
            LPCWSTR staffFormas[] = {
                PASTE_SUCCEEDED,
                PERFORMED_DROP_EFFECT_MIME
            };
            OLE_TRY
            for(int i = 0; i < 2; ++i) {
                FORMATETC fmt = {
                    getClipboardFormat(staffFormas[i]),
                    NULL,
                    DVASPECT_CONTENT,
                    -1L,
                    TYMED_HGLOBAL};
                STGMEDIUM data = {TYMED_HGLOBAL, 0};
                data.hGlobal = ::GlobalAlloc(GALLOCFLG, sizeof(DROPEFFECT));
                OLE_CHECK_NOTNULL(data.hGlobal)
                *((DROPEFFECT *)::GlobalLock(data.hGlobal)) = getDROPEFFECT(actionDone);
                ::GlobalUnlock(data.hGlobal);
                OLE_HRT(p->SetData(&fmt, &data, TRUE))
            }
            OLE_CATCH
        }
    }
    jint actionDone;
    LEAVE_MAIN_THREAD_WITH_p;

    ARG(actionDone) = actionDone;
    PERFORM();
}

/*
 * Class:     com_sun_glass_ui_win_WinSystemClipboard
 * Method:    popSupportedActionFromSystem
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_sun_glass_ui_win_WinSystemClipboard_popSupportedSourceActions
  (JNIEnv *env, jobject obj)
{
    ENTER_MAIN_THREAD_AND_RETURN(jint)
    {
        //please, read: http://msdn.microsoft.com/en-us/library/bb776904%28VS.85%29.aspx
        //So we are here if we are not the owners of the clipboard
        jint ret = com_sun_glass_ui_win_WinSystemClipboard_ACTION_NONE;
        if (NULL != p) {
            static FORMATETC fmt = {
                getClipboardFormat(PREFERRED_DROP_EFFECT_MIME),
                NULL,
                DVASPECT_CONTENT,
                -1L,
                TYMED_HGLOBAL};
            STGMEDIUM data = {0};
            if( FAILED(p->GetData(&fmt, &data)) ) {
                ret = com_sun_glass_ui_win_WinSystemClipboard_ACTION_ANY;
            } else {
                if (TYMED_HGLOBAL==data.tymed && NULL != data.hGlobal) {
                    DROPEFFECT *pDF = (DROPEFFECT *)::GlobalLock(data.hGlobal);
                    if (NULL != pDF) {
                        ret = getACTION(*pDF);
                    }
                    GlobalUnlock(data.hGlobal);
                } else {
                    ret = com_sun_glass_ui_win_WinSystemClipboard_ACTION_ANY;
                }
                ReleaseStgMedium(&data);
            }
        }
        return ret;
    }
    LEAVE_MAIN_THREAD_WITH_p;
    return PERFORM_AND_RETURN();
}

//////////////////////////////////////////////////////////////////////////
//WinDnDClipboard
//////////////////////////////////////////////////////////////////////////


/*
 * Class:     com_sun_glass_ui_win_WinDnDClipboard
 * Method:    dispose
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinDnDClipboard_dispose
  (JNIEnv *env, jobject obj)
{
    ENTER_MAIN_THREAD()
    {
        if (NULL != p) {
            p->Release();
            STRACE(_T("Dnd Clipboard Closed"));
        }
    }
    LEAVE_MAIN_THREAD_WITH_p;
    PERFORM();
}

/*
 * Class:     com_sun_glass_ui_win_WinDnDClipboard
 * Method:    push
 * Signature: ([Ljava/lang/Object;I)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinDnDClipboard_push
  (JNIEnv *env, jobject obj, jobjectArray keys, jint supportedActions)
{
    ENTER_MAIN_THREAD()
    {
        DWORD performedDropEffect = DROPEFFECT_MOVE;
        JNIEnv * env = GetEnv();
        OLE_TRY
        if (NULL != p) {
            //We need to create new object here due to [POSTPONED RELEASE] algorithm
            //in data provider.
            p->Release();
            STRACE(_T("Alarm Dnd Clipboard Release"));
        }
        ClipboardData *pcd = new ClipboardData(
            env,
            obj,
            JLString(env, (jstring)env->GetObjectField(obj, fidName)));
        setPtr(env, obj, pcd);
        //from now 'pcd' would be destroyed on dispose

        OLE_HRT( pcd->pushCommit(env, keys, supportedActions) )

        STRACE(_T("{DoDragDrop %08x"), getDROPEFFECT(supportedActions));
        OLE_HRT( ::DoDragDrop(
            pcd,
            IDropSourcePtr(new GlassDropSource(obj), false),
            getDROPEFFECT(supportedActions),
            &performedDropEffect) )
        OLE_CATCH
        env->CallVoidMethod(obj, midActionPerformed,
            getACTION(SUCCEEDED(OLE_HR) ? performedDropEffect : DROPEFFECT_NONE));
        OLE_HRT(checkJavaException(env));
        GlassDropSource::SetDragButton(0);
        STRACE(_T("}DoDragDrop effect:%08x result:%08x"), performedDropEffect, OLE_HR);
    }
    DECL_jobject(obj);
    DECL_JREF(jobjectArray, keys);
    jint supportedActions;
    LEAVE_MAIN_THREAD_WITH_p;

    ARG(obj) = obj;
    ARG(keys) = keys;
    ARG(supportedActions) = supportedActions;
    PERFORM();
}

}

