/*
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "config.h"
#include "ClipboardGtk.h"

#include "CachedImage.h"
#include "DragData.h"
#include "Editor.h"
#include "Element.h"
#include "FileList.h"
#include "Frame.h"
#include "HTMLNames.h"
#include "Image.h"
#include "NotImplemented.h"
#include "Pasteboard.h"
#include "PasteboardHelper.h"
#include "RenderImage.h"
#include "ScriptExecutionContext.h"
#include "markup.h"
#include <wtf/text/CString.h>
#include <wtf/text/StringHash.h>
#include <gtk/gtk.h>

namespace WebCore {

enum ClipboardDataType {
    ClipboardDataTypeText,
    ClipboardDataTypeMarkup,
    ClipboardDataTypeURIList,
    ClipboardDataTypeURL,
    ClipboardDataTypeImage,
    ClipboardDataTypeUnknown
};

PassRefPtr<Clipboard> Editor::newGeneralClipboard(ClipboardAccessPolicy policy, Frame* frame)
{
    return ClipboardGtk::create(policy, gtk_clipboard_get_for_display(gdk_display_get_default(), GDK_SELECTION_CLIPBOARD), frame);
}

PassRefPtr<Clipboard> Clipboard::create(ClipboardAccessPolicy policy, DragData* dragData, Frame* frame)
{
    return ClipboardGtk::create(policy, dragData->platformData(), DragAndDrop, frame);
}

ClipboardGtk::ClipboardGtk(ClipboardAccessPolicy policy, GtkClipboard* clipboard, Frame* frame)
    : Clipboard(policy, CopyAndPaste)
    , m_dataObject(DataObjectGtk::forClipboard(clipboard))
    , m_clipboard(clipboard)
    , m_helper(Pasteboard::generalPasteboard()->helper())
    , m_frame(frame)
{
}

ClipboardGtk::ClipboardGtk(ClipboardAccessPolicy policy, PassRefPtr<DataObjectGtk> dataObject, ClipboardType clipboardType, Frame* frame)
    : Clipboard(policy, clipboardType)
    , m_dataObject(dataObject)
    , m_clipboard(0)
    , m_helper(Pasteboard::generalPasteboard()->helper())
    , m_frame(frame)
{
}

ClipboardGtk::~ClipboardGtk()
{
}

static ClipboardDataType dataObjectTypeFromHTMLClipboardType(const String& rawType)
{
    String type(rawType.stripWhiteSpace());

    // Two special cases for IE compatibility
    if (type == "Text" || type == "text")
        return ClipboardDataTypeText;
    if (type == "URL")
        return ClipboardDataTypeURL;

    // From the Mac port: Ignore any trailing charset - JS strings are
    // Unicode, which encapsulates the charset issue.
    if (type == "text/plain" || type.startsWith("text/plain;"))
        return ClipboardDataTypeText;
    if (type == "text/html" || type.startsWith("text/html;"))
        return ClipboardDataTypeMarkup;
    if (type == "Files" || type == "text/uri-list" || type.startsWith("text/uri-list;"))
        return ClipboardDataTypeURIList;

    // Not a known type, so just default to using the text portion.
    return ClipboardDataTypeUnknown;
}

void ClipboardGtk::clearData(const String& typeString)
{
    if (policy() != ClipboardWritable)
        return;

    ClipboardDataType type = dataObjectTypeFromHTMLClipboardType(typeString);
    switch (type) {
    case ClipboardDataTypeURIList:
    case ClipboardDataTypeURL:
        m_dataObject->clearURIList();
        break;
    case ClipboardDataTypeMarkup:
        m_dataObject->clearMarkup();
        break;
    case ClipboardDataTypeText:
        m_dataObject->clearText();
        break;
    case ClipboardDataTypeUnknown:
    default:
        m_dataObject->clear();
    }

    if (m_clipboard)
        m_helper->writeClipboardContents(m_clipboard);
}


void ClipboardGtk::clearAllData()
{
    if (policy() != ClipboardWritable)
        return;

    m_dataObject->clear();

    if (m_clipboard)
        m_helper->writeClipboardContents(m_clipboard);
}

String ClipboardGtk::getData(const String& typeString, bool& success) const
{
    success = true; // According to http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html
    // "The getData(format) method must return the data that is associated with the type format converted
    // to ASCII lowercase, if any, and must return the empty string otherwise." Since success == false 
    // results in an 'undefined' return value, we always want to return success == true. This parameter
    // should eventually be removed.
    if (policy() != ClipboardReadable || !m_dataObject)
        return String();

    if (m_clipboard)
        m_helper->getClipboardContents(m_clipboard);

    ClipboardDataType type = dataObjectTypeFromHTMLClipboardType(typeString);
    if (type == ClipboardDataTypeURIList)
        return m_dataObject->uriList();
    if (type == ClipboardDataTypeURL)
        return m_dataObject->url();
    if (type == ClipboardDataTypeMarkup)
        return m_dataObject->markup();
    if (type == ClipboardDataTypeText)
        return m_dataObject->text();

    return String();
}

bool ClipboardGtk::setData(const String& typeString, const String& data)
{
    if (policy() != ClipboardWritable)
        return false;

    bool success = false;
    ClipboardDataType type = dataObjectTypeFromHTMLClipboardType(typeString);
    if (type == ClipboardDataTypeURIList || type == ClipboardDataTypeURL) {
        m_dataObject->setURIList(data);
        success = true;
    } else if (type == ClipboardDataTypeMarkup) {
        m_dataObject->setMarkup(data);
        success = true;
    } else if (type == ClipboardDataTypeText) {
        m_dataObject->setText(data);
        success = true;
    }

    if (success && m_clipboard)
        m_helper->writeClipboardContents(m_clipboard);

    return success;
}

HashSet<String> ClipboardGtk::types() const
{
    if (policy() != ClipboardReadable && policy() != ClipboardTypesReadable)
        return HashSet<String>();

    if (m_clipboard)
        m_helper->getClipboardContents(m_clipboard);

    HashSet<String> types;
    if (m_dataObject->hasText()) {
        types.add("text/plain");
        types.add("Text");
        types.add("text");
    }

    if (m_dataObject->hasMarkup())
        types.add("text/html");

    if (m_dataObject->hasURIList()) {
        types.add("text/uri-list");
        types.add("URL");
    }

    if (m_dataObject->hasFilenames())
        types.add("Files");

    return types;
}

PassRefPtr<FileList> ClipboardGtk::files() const
{
    if (policy() != ClipboardReadable)
        return FileList::create();

    if (m_clipboard)
        m_helper->getClipboardContents(m_clipboard);

    RefPtr<FileList> fileList = FileList::create();
    const Vector<String>& filenames = m_dataObject->filenames();
    for (size_t i = 0; i < filenames.size(); i++)
        fileList->append(File::create(filenames[i]));
    return fileList.release();
}

void ClipboardGtk::setDragImage(CachedImage* image, const IntPoint& location)
{
    setDragImage(image, 0, location);
}

void ClipboardGtk::setDragImageElement(Node* element, const IntPoint& location)
{
    setDragImage(0, element, location);
}

void ClipboardGtk::setDragImage(CachedImage* image, Node* element, const IntPoint& location)
{
    if (policy() != ClipboardImageWritable && policy() != ClipboardWritable)
        return;

    if (m_dragImage)
        m_dragImage->removeClient(this);
    m_dragImage = image;
    if (m_dragImage)
        m_dragImage->addClient(this);

    m_dragLoc = location;
    m_dragImageElement = element;
}

DragImageRef ClipboardGtk::createDragImage(IntPoint& location) const
{
    location = m_dragLoc;
    if (!m_dragImage)
        return 0;

    return createDragImageFromImage(m_dragImage->image());
}

static CachedImage* getCachedImage(Element* element)
{
    // Attempt to pull CachedImage from element
    ASSERT(element);
    RenderObject* renderer = element->renderer();
    if (!renderer || !renderer->isImage())
        return 0;

    RenderImage* image = static_cast<RenderImage*>(renderer);
    if (image->cachedImage() && !image->cachedImage()->errorOccurred())
        return image->cachedImage();

    return 0;
}

void ClipboardGtk::declareAndWriteDragImage(Element* element, const KURL& url, const String& label, Frame* frame)
{
    m_dataObject->setURL(url, label);
    m_dataObject->setMarkup(createMarkup(element, IncludeNode, 0, AbsoluteURLs));

    CachedImage* image = getCachedImage(element);
    if (!image || !image->isLoaded())
        return;

    GRefPtr<GdkPixbuf> pixbuf = adoptGRef(image->image()->getGdkPixbuf());
    if (!pixbuf)
        return;

    m_dataObject->setImage(pixbuf.get());
}

void ClipboardGtk::writeURL(const KURL& url, const String& label, Frame*)
{
    m_dataObject->setURL(url, label);
    if (m_clipboard)
        m_helper->writeClipboardContents(m_clipboard);
}

void ClipboardGtk::writeRange(Range* range, Frame* frame)
{
    ASSERT(range);

    m_dataObject->setText(frame->editor()->selectedText());
    m_dataObject->setMarkup(createMarkup(range, 0, AnnotateForInterchange, false, AbsoluteURLs));

    if (m_clipboard)
        m_helper->writeClipboardContents(m_clipboard);
}

void ClipboardGtk::writePlainText(const String& text)
{
    m_dataObject->setText(text);

    if (m_clipboard)
        m_helper->writeClipboardContents(m_clipboard);
}

bool ClipboardGtk::hasData()
{
    if (m_clipboard)
        m_helper->getClipboardContents(m_clipboard);

    return m_dataObject->hasText() || m_dataObject->hasMarkup()
        || m_dataObject->hasURIList() || m_dataObject->hasImage();
}

}
