/*
 * synergy -- mouse and keyboard sharing utility
 * Copyright (C) 2014-2016 Symless Ltd.
 * 
 * This package is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * found in the file LICENSE that should have accompanied this file.
 * 
 * This package 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "platform/MSWindowsDropTarget.h"

#include "base/Log.h"
#include "common/common.h"

#include <stdio.h>
#include <Shlobj.h>

void getDropData(IDataObject *pDataObject);

MSWindowsDropTarget* MSWindowsDropTarget::s_instance = NULL;

MSWindowsDropTarget::MSWindowsDropTarget() :
    m_refCount(1),
    m_allowDrop(false)
{
    s_instance = this;
}

MSWindowsDropTarget::~MSWindowsDropTarget()
{
}

MSWindowsDropTarget&
MSWindowsDropTarget::instance()
{
    assert(s_instance != NULL);
    return *s_instance;
}

HRESULT
MSWindowsDropTarget::DragEnter(IDataObject* dataObject, DWORD keyState, POINTL point, DWORD* effect)
{
    // check if data object contain drop
    m_allowDrop = queryDataObject(dataObject);
    if (m_allowDrop) {
        getDropData(dataObject);
    }
    
    *effect = DROPEFFECT_NONE;

    return S_OK;
}

HRESULT
MSWindowsDropTarget::DragOver(DWORD keyState, POINTL point, DWORD* effect)
{
    *effect = DROPEFFECT_NONE;

    return S_OK;
}

HRESULT
MSWindowsDropTarget::DragLeave(void)
{
    return S_OK;
}

HRESULT
MSWindowsDropTarget::Drop(IDataObject* dataObject, DWORD keyState, POINTL point, DWORD* effect)
{
    *effect = DROPEFFECT_NONE;

    return S_OK;
}

bool
MSWindowsDropTarget::queryDataObject(IDataObject* dataObject)
{
    // check if it supports CF_HDROP using a HGLOBAL
    FORMATETC fmtetc = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };

    return dataObject->QueryGetData(&fmtetc) == S_OK ? true : false;
}

void
MSWindowsDropTarget::setDraggingFilename(char* const filename)
{
    m_dragFilename = filename;
}

std::string
MSWindowsDropTarget::getDraggingFilename()
{
    return m_dragFilename;
}

void
MSWindowsDropTarget::clearDraggingFilename()
{
    m_dragFilename.clear();
}

void
getDropData(IDataObject* dataObject)
{
    // construct a FORMATETC object
    FORMATETC fmtEtc = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    STGMEDIUM stgMed;

    // See if the dataobject contains any DROP stored as a HGLOBAL
    if (dataObject->QueryGetData(&fmtEtc) == S_OK) {
        if (dataObject->GetData(&fmtEtc, &stgMed) == S_OK) {
            // get data here
            PVOID data = GlobalLock(stgMed.hGlobal);

            // data object global handler contains:
            // DROPFILESfilename1 filename2 two spaces as the end
            // TODO: get multiple filenames
            wchar_t* wcData = (wchar_t*)((LPBYTE)data + sizeof(DROPFILES));

            // convert wchar to char
            char* filename = new char[wcslen(wcData) + 1];
            filename[wcslen(wcData)] = '\0';
            wcstombs(filename, wcData, wcslen(wcData));

            MSWindowsDropTarget::instance().setDraggingFilename(filename);
            
            GlobalUnlock(stgMed.hGlobal);

            // release the data using the COM API
            ReleaseStgMedium(&stgMed);

            delete[] filename;
        }
    }
}

HRESULT __stdcall
MSWindowsDropTarget::QueryInterface (REFIID iid, void ** object)
{
    if (iid == IID_IDropTarget || iid == IID_IUnknown) {
        AddRef();
        *object = this;
        return S_OK;
    }
    else {
        *object = 0;
        return E_NOINTERFACE;
    }
}

ULONG __stdcall
MSWindowsDropTarget::AddRef(void)
{
    return InterlockedIncrement(&m_refCount);
}

ULONG __stdcall
MSWindowsDropTarget::Release(void)
{
    LONG count = InterlockedDecrement(&m_refCount);
        
    if (count == 0) {
        delete this;
        return 0;
    }
    else {
        return count;
    }
}
