/*
 * synergy -- mouse and keyboard sharing utility
 * Copyright (C) 2002 Chris Schoeneman
 * Copyright (C) 2006 Knut St. Osmundsen
 *
 * 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 COPYING 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.
 */

#include "CPMClipboard.h"
#include "CPMClipboardTextConverter.h"
#include "CPMClipboardBitmapConverter.h"
#include "CPMClipboardHTMLConverter.h"
#include "CPMUtil.h"
#include "CLog.h"

//
// CPMClipboard
//

ATOM                    CPMClipboard::s_ownershipFormat = 0;

CPMClipboard::CPMClipboard(HWND window) :
    m_window(window),
    m_time(0)
{
    // add converters, most desired first
    m_converters.push_back(new CPMClipboardTextConverter);
    m_converters.push_back(new CPMClipboardBitmapConverter);
    m_converters.push_back(new CPMClipboardHTMLConverter);
}

CPMClipboard::~CPMClipboard()
{
    clearConverters();
}

bool
CPMClipboard::empty()
{
    LOG((CLOG_DEBUG "empty clipboard"));
    if (!WinEmptyClipbrd(CPMUtil::getHAB())) {
        LOG((CLOG_ERR "failed to empty the clipboard"));
        return false;
    }

    // mark clipboard as being owned by synergy
    PVOID pv = NULL;
    APIRET rc = DosAllocSharedMem(&pv, NULL, 1, PAG_READ | PAG_WRITE | PAG_COMMIT | OBJ_GETTABLE | OBJ_GIVEABLE);
    if (rc) {
        assert(rc);
        return false;
    }
    if (!WinSetClipbrdData(CPMUtil::getHAB(), (ULONG)pv, getOwnershipFormat(), CFI_POINTER)) {
        LOG((CLOG_ERR "failed to set dummy clipboard data. %#x", WinGetLastError(CPMUtil::getHAB())));
        DosFreeMem(pv);
        return false;
    }
    return true;
}

void
CPMClipboard::add(EFormat format, const CString& data)
{
    LOG((CLOG_DEBUG "add %d bytes to clipboard format: %d", data.size(), format));

    // convert data to win32 form
    for (ConverterList::const_iterator index = m_converters.begin();
         index != m_converters.end();
         ++index) {
        IPMClipboardConverter* converter = *index;

        // skip converters for other formats
        if (converter->getFormat() == format) {
            ULONG ulPMData = converter->fromIClipboard(data);
            if (ulPMData != NULL) {
                ULONG ulPMFormat = converter->getPMFormat();
                ULONG ulPMFormatInfo = converter->getPMFormatInfo();
                if (!WinSetClipbrdData(CPMUtil::getHAB(), ulPMData, ulPMFormat, ulPMFormatInfo)) {
                    // free converted data if we couldn't put it on the clipboard
                    LOG((CLOG_ERR "failed to set clipboard data ulPMData=%#lx ulPMFormat=%#lx ulPMFormatInfo=%#lx. %#x",
                         ulPMData, ulPMFormat, ulPMFormatInfo, WinGetLastError(CPMUtil::getHAB())));
                    converter->freePMData(ulPMData);
                }
            }
        }
    }
}

bool
CPMClipboard::open(Time time) const
{
    LOG((CLOG_DEBUG "open clipboard"));

    if (!WinOpenClipbrd(m_window)) {
        LOG((CLOG_WARN "failed to open clipboard"));
        return false;
    }
    m_time = time;
    return true;
}

void
CPMClipboard::close() const
{
    LOG((CLOG_DEBUG "close clipboard"));
    if (!WinCloseClipbrd(CPMUtil::getHAB())) {
        LOG((CLOG_WARN "failed to close the clipboard. %#x", WinGetLastError(CPMUtil::getHAB())));
    }
}

IClipboard::Time
CPMClipboard::getTime() const
{
    return m_time;
}

bool
CPMClipboard::has(EFormat format) const
{
    LOG((CLOG_DEBUG "has clipboard format=%d?", format));
    for (ConverterList::const_iterator index = m_converters.begin();
         index != m_converters.end();
         ++index) {
        IPMClipboardConverter* converter = *index;
        LOG((CLOG_DEBUG2 "CPMClipboard::has: format=%d convert->getFormat()->%d (pmformat=%d)", format, converter->getFormat(), converter->getPMFormat()));
        if (converter->getFormat() == format) {
            ULONG fFmtInfo;
            if (WinQueryClipbrdFmtInfo(CPMUtil::getHAB(), converter->getPMFormat(), &fFmtInfo)) {
                LOG((CLOG_DEBUG "yes, we have format %d!", format));
                return true;
            }
        }
    }
    LOG((CLOG_DEBUG "sorry, no we haven't."));
    return false;
}

CString
CPMClipboard::get(EFormat format) const
{
    LOG((CLOG_DEBUG "get clipboard format=%d?", format));

    // find the converter for the first clipboard format we can handle
    IPMClipboardConverter* converter = NULL;
    ULONG pmFormat = WinEnumClipbrdFmts(CPMUtil::getHAB(), 0);
    while (converter == NULL && pmFormat != 0) {
        for (ConverterList::const_iterator index = m_converters.begin();
             index != m_converters.end();
             ++index) {
            converter = *index;
            LOG((CLOG_DEBUG2 "CPMClipboard::get: pmFormat=%d format=%d ?=? %d:%d", pmFormat, format, converter->getPMFormat(), converter->getFormat()));
            if (converter->getPMFormat() == pmFormat &&
                converter->getFormat()   == format) {
                break;
            }
            converter = NULL;
        }
        pmFormat = WinEnumClipbrdFmts(CPMUtil::getHAB(), pmFormat);
    }

    // if no converter then we don't recognize any formats
    if (converter == NULL) {
        return CString();
    }

    // get a handle to the clipboard data
    ULONG ulData = WinQueryClipbrdData(CPMUtil::getHAB(), converter->getPMFormat());
    if (ulData == 0) {
        LOG((CLOG_DEBUG "CPMClipboard::get: ulData=%#x\n", ulData));
        return CString();
    }

    // convert
    CString str = converter->toIClipboard(ulData);
    LOG((CLOG_DEBUG2 "CPMClipboard::get: converted ulData=%lx to '%s'\n", ulData, str.c_str()));
    return str;
}

void
CPMClipboard::clearConverters()
{
    for (ConverterList::iterator index = m_converters.begin();
         index != m_converters.end();
         ++index) {
        delete *index;
    }
    m_converters.clear();
}

bool
CPMClipboard::isOwnedBySynergy()
{
    ULONG fFmtInfo;
    if (WinQueryClipbrdFmtInfo(CPMUtil::getHAB(), getOwnershipFormat(), &fFmtInfo)) {
        LOG((CLOG_DEBUG "isOwnedBySynergy: yes"));
        return true;
    }
    LOG((CLOG_DEBUG "isOwnedBySynergy: no"));
    return false;
}

ATOM
CPMClipboard::getOwnershipFormat()
{
    // create ownership format if we haven't yet
    if (s_ownershipFormat == 0) {
        ATOM hAtom = WinAddAtom(WinQuerySystemAtomTable(), (PCSZ)"SynergyOwnership");
        if (hAtom == 0)
            hAtom = WinFindAtom(WinQuerySystemAtomTable(), (PCSZ)"SynergyOwnership");
        assert(hAtom != 0);
        s_ownershipFormat = hAtom;
    }

    // return the format
    return s_ownershipFormat;
}
