﻿// TortoiseSVN - a Windows shell extension for easy version control

// Copyright (C) 2003-2012, 2021 - TortoiseSVN

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program 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, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#pragma once

#include "DragDropImpl.h"
#include "UnicodeUtils.h"

/**
 * \ingroup Utils
 * helper class to turn a control into a file drop target
 */
class CFileDropTarget : public CIDropTarget
{
public:
    CFileDropTarget(HWND hTargetWnd)
        : CIDropTarget(hTargetWnd)
    {
    }

    bool OnDrop(FORMATETC* pFmtEtc, STGMEDIUM& medium, DWORD* /*pdwEffect*/, POINTL /*pt*/) override
    {
        if (pFmtEtc->cfFormat == CF_TEXT && medium.tymed == TYMED_ISTREAM)
        {
            if (medium.pstm != nullptr)
            {
                const int BUF_SIZE = 10000;
                auto      buff     = std::make_unique<char[]>(BUF_SIZE + 1);
                ULONG     cbRead   = 0;
                HRESULT   hr       = medium.pstm->Read(buff.get(), BUF_SIZE, &cbRead);
                if (SUCCEEDED(hr) && (cbRead > 0) && (cbRead < BUF_SIZE))
                {
                    buff[cbRead] = 0;
                    LRESULT nLen = ::SendMessage(m_hTargetWnd, WM_GETTEXTLENGTH, 0, 0);
                    ::SendMessage(m_hTargetWnd, EM_SETSEL, nLen, -1);
                    std::wstring str = CUnicodeUtils::StdGetUnicode(std::string(buff.get()));
                    ::SendMessage(m_hTargetWnd, EM_REPLACESEL, TRUE, reinterpret_cast<LPARAM>(str.c_str()));
                }
                else
                {
                    while ((hr == S_OK) && (cbRead > 0))
                    {
                        buff[cbRead] = 0;
                        LRESULT nLen = ::SendMessage(m_hTargetWnd, WM_GETTEXTLENGTH, 0, 0);
                        ::SendMessage(m_hTargetWnd, EM_SETSEL, nLen, -1);
                        std::wstring str = CUnicodeUtils::StdGetUnicode(std::string(buff.get()));
                        ::SendMessage(m_hTargetWnd, EM_REPLACESEL, TRUE, reinterpret_cast<LPARAM>(str.c_str()));
                        cbRead = 0;
                        hr     = medium.pstm->Read(buff.get(), BUF_SIZE, &cbRead);
                    }
                }
            }
        }
        if (pFmtEtc->cfFormat == CF_UNICODETEXT && medium.tymed == TYMED_ISTREAM)
        {
            if (medium.pstm != nullptr)
            {
                const int BUF_SIZE = 10000;
                auto      buff     = std::make_unique<WCHAR[]>(BUF_SIZE + 1);
                ULONG     cbRead   = 0;
                HRESULT   hr       = medium.pstm->Read(buff.get(), BUF_SIZE, &cbRead);
                if (SUCCEEDED(hr) && (cbRead > 0) && (cbRead < BUF_SIZE))
                {
                    buff[cbRead] = 0;
                    LRESULT nLen = ::SendMessage(m_hTargetWnd, WM_GETTEXTLENGTH, 0, 0);
                    ::SendMessage(m_hTargetWnd, EM_SETSEL, nLen, -1);
                    ::SendMessage(m_hTargetWnd, EM_REPLACESEL, TRUE, reinterpret_cast<LPARAM>(buff.get()));
                }
                else
                {
                    while ((hr == S_OK) && (cbRead > 0))
                    {
                        buff[cbRead] = 0;
                        LRESULT nLen = ::SendMessage(m_hTargetWnd, WM_GETTEXTLENGTH, 0, 0);
                        ::SendMessage(m_hTargetWnd, EM_SETSEL, nLen, -1);
                        ::SendMessage(m_hTargetWnd, EM_REPLACESEL, TRUE, reinterpret_cast<LPARAM>(buff.get()));
                        cbRead = 0;
                        hr     = medium.pstm->Read(buff.get(), BUF_SIZE, &cbRead);
                    }
                }
            }
        }
        if (pFmtEtc->cfFormat == CF_TEXT && medium.tymed == TYMED_HGLOBAL)
        {
            char* pStr = static_cast<char*>(GlobalLock(medium.hGlobal));
            if (pStr != nullptr)
            {
                LRESULT nLen = ::SendMessage(m_hTargetWnd, WM_GETTEXTLENGTH, 0, 0);
                ::SendMessage(m_hTargetWnd, EM_SETSEL, nLen, -1);
                std::wstring str = CUnicodeUtils::StdGetUnicode(std::string(pStr));
                ::SendMessage(m_hTargetWnd, EM_REPLACESEL, TRUE, reinterpret_cast<LPARAM>(str.c_str()));
            }
            GlobalUnlock(medium.hGlobal);
        }
        if (pFmtEtc->cfFormat == CF_UNICODETEXT && medium.tymed == TYMED_HGLOBAL)
        {
            WCHAR* pStr = static_cast<WCHAR*>(GlobalLock(medium.hGlobal));
            if (pStr != nullptr)
            {
                LRESULT nLen = ::SendMessage(m_hTargetWnd, WM_GETTEXTLENGTH, 0, 0);
                ::SendMessage(m_hTargetWnd, EM_SETSEL, nLen, -1);
                ::SendMessage(m_hTargetWnd, EM_REPLACESEL, TRUE, reinterpret_cast<LPARAM>(pStr));
            }
            GlobalUnlock(medium.hGlobal);
        }
        if (pFmtEtc->cfFormat == CF_HDROP && medium.tymed == TYMED_HGLOBAL)
        {
            HDROP hDrop = static_cast<HDROP>(GlobalLock(medium.hGlobal));
            if (hDrop != nullptr)
            {
                wchar_t szFileName[MAX_PATH];

                UINT cFiles = DragQueryFile(hDrop, 0xFFFFFFFF, nullptr, 0);
                for (UINT i = 0; i < cFiles; ++i)
                {
                    if (DragQueryFile(hDrop, i, szFileName, _countof(szFileName)))
                        ::SendMessage(m_hTargetWnd, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(szFileName));
                }
                //DragFinish(hDrop); // base class calls ReleaseStgMedium
            }
            GlobalUnlock(medium.hGlobal);
        }
        return true; //let base free the medium
    }
};

/**
 * \ingroup Utils
 * Enhancement for a CEdit control which allows the edit control to have files
 * dropped onto it and fill in the path of that dropped file.
 */
class CFileDropEdit : public CEdit
{
    DECLARE_DYNAMIC(CFileDropEdit)

public:
    CFileDropEdit();
    ~CFileDropEdit() override;

protected:
    DECLARE_MESSAGE_MAP()

    std::unique_ptr<CFileDropTarget> m_pDropTarget;
    void                             PreSubclassWindow() override;
};
