/*
 * Copyright (C) 2021 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "ImageControlsMac.h"

#include "Chrome.h"
#include "ChromeClient.h"
#include "CommonAtomStrings.h"
#include "ContextMenuController.h"
#include "ElementInlines.h"
#include "ElementRareData.h"
#include "Event.h"
#include "EventHandler.h"
#include "EventLoop.h"
#include "EventNames.h"
#include "HTMLAttachmentElement.h"
#include "HTMLButtonElement.h"
#include "HTMLDivElement.h"
#include "HTMLImageElement.h"
#include "HTMLNames.h"
#include "HTMLStyleElement.h"
#include "MouseEvent.h"
#include "RenderAttachment.h"
#include "RenderImage.h"
#include "ShadowRoot.h"
#include "UserAgentStyleSheets.h"
#include <wtf/text/AtomString.h>

namespace WebCore {
namespace ImageControlsMac {

#if ENABLE(SERVICE_CONTROLS)

static const AtomString& imageControlsElementIdentifier()
{
    static MainThreadNeverDestroyed<const AtomString> identifier("image-controls"_s);
    return identifier;
}

static const AtomString& imageControlsButtonIdentifier()
{
    static MainThreadNeverDestroyed<const AtomString> identifier("image-controls-button"_s);
    return identifier;
}

bool hasControls(const HTMLElement& element)
{
    auto shadowRoot = element.shadowRoot();
    if (!shadowRoot || shadowRoot->mode() != ShadowRootMode::UserAgent)
        return false;

    return shadowRoot->hasElementWithId(*imageControlsElementIdentifier().impl());
}

bool isImageControlsButtonElement(const Node& node)
{
    return is<Element>(node) && downcast<Element>(node).getIdAttribute() == imageControlsButtonIdentifier();
}

bool isInsideImageControls(const Node& node)
{
    RefPtr host = node.shadowHost();
    if (!is<HTMLElement>(host.get()) || !hasControls(downcast<HTMLElement>(*host)))
        return false;
    return is<Element>(node) && downcast<Element>(node).getIdAttribute() == imageControlsElementIdentifier();
}

void createImageControls(HTMLElement& element)
{
    Ref document = element.document();
    Ref shadowRoot = element.ensureUserAgentShadowRoot();
    auto controlLayer = HTMLDivElement::create(document.get());
    controlLayer->setIdAttribute(imageControlsElementIdentifier());
    controlLayer->setAttributeWithoutSynchronization(HTMLNames::contenteditableAttr, falseAtom());
    shadowRoot->appendChild(controlLayer);
    
    static MainThreadNeverDestroyed<const String> shadowStyle(StringImpl::createWithoutCopying(imageControlsMacUserAgentStyleSheet, sizeof(imageControlsMacUserAgentStyleSheet)));
    auto style = HTMLStyleElement::create(HTMLNames::styleTag, document.get(), false);
    style->setTextContent(String { shadowStyle });
    shadowRoot->appendChild(WTFMove(style));
    
    auto button = HTMLButtonElement::create(HTMLNames::buttonTag, element.document(), nullptr);
    button->setIdAttribute(imageControlsButtonIdentifier());
    controlLayer->appendChild(button);
    
    if (auto* renderObject = element.renderer(); is<RenderImage>(renderObject))
        downcast<RenderImage>(*renderObject).setHasShadowControls(true);
}

static Image* imageFromImageElementNode(Node& node)
{
    auto* renderer = node.renderer();
    if (!is<RenderImage>(renderer))
        return nullptr;
    auto* image = downcast<RenderImage>(*renderer).cachedImage();
    if (!image || image->errorOccurred())
        return nullptr;
    return image->imageForRenderer(renderer);
}

bool handleEvent(HTMLElement& element, Event& event)
{
    if (event.type() != eventNames().clickEvent)
        return false;
    
    RefPtr frame = element.document().frame();
    if (!frame)
        return false;

    Page* page = element.document().page();
    if (!page)
        return false;
    
    if (!is<MouseEvent>(event))
        return false;
    
    auto& mouseEvent = downcast<MouseEvent>(event);
    if (!is<Node>(mouseEvent.target()))
        return false;
    auto& node = downcast<Node>(*mouseEvent.target());

    if (ImageControlsMac::isImageControlsButtonElement(node)) {
        Ref element = downcast<Element>(node);
        auto* renderer = element->renderer();
        if (!renderer)
            return false;

        RefPtr view = frame->view();
        if (!view)
            return false;

        auto point = view->contentsToWindow(renderer->absoluteBoundingBoxRect()).minXMaxYCorner();

        if (RefPtr shadowHost = dynamicDowncast<HTMLImageElement>(node.shadowHost())) {
            auto* image = imageFromImageElementNode(*shadowHost);
            if (!image)
                return false;
            page->chrome().client().handleImageServiceClick(point, *image, *shadowHost);
        } else if (RefPtr shadowHost = dynamicDowncast<HTMLAttachmentElement>(node.shadowHost()))
            page->chrome().client().handlePDFServiceClick(point, *shadowHost);

        event.setDefaultHandled();
        return true;
    }
    return false;
}

static bool isImageMenuEnabled(HTMLElement& element)
{
    if (RefPtr imageElement = dynamicDowncast<HTMLImageElement>(element))
        return imageElement->isImageMenuEnabled();

    if (RefPtr attachmentElement = dynamicDowncast<HTMLAttachmentElement>(element))
        return attachmentElement->isImageMenuEnabled();

    return false;
}

void updateImageControls(HTMLElement& element)
{
    // If this is an image element and it is inside a shadow tree then it is part of an image control.
    if (element.isInShadowTree())
        return;

    if (!element.document().settings().imageControlsEnabled())
        return;

    element.document().eventLoop().queueTask(TaskSource::InternalAsyncTask, [weakElement = WeakPtr { element }] {
        RefPtr protectedElement = weakElement.get();
        if (!protectedElement)
            return;
        ASSERT(is<HTMLAttachmentElement>(*protectedElement) || is<HTMLImageElement>(*protectedElement));
        bool imageMenuEnabled = isImageMenuEnabled(*protectedElement);
        bool hasControls = hasImageControls(*protectedElement);
        if (!imageMenuEnabled && hasControls)
            destroyImageControls(*protectedElement);
        else if (imageMenuEnabled && !hasControls)
            tryCreateImageControls(*protectedElement);
    });
}

void tryCreateImageControls(HTMLElement& element)
{
    ASSERT(isImageMenuEnabled(element));
    ASSERT(!hasImageControls(element));
    createImageControls(element);
}

void destroyImageControls(HTMLElement& element)
{
    auto shadowRoot = element.userAgentShadowRoot();
    if (!shadowRoot)
        return;

    if (RefPtr node = shadowRoot->firstChild()) {
        if (!is<HTMLElement>(*node))
            return;
        RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(ImageControlsMac::hasControls(downcast<HTMLElement>(*node)));
        shadowRoot->removeChild(*node);
    }

    auto* renderObject = element.renderer();
    if (!renderObject)
        return;

    if (auto* renderImage = dynamicDowncast<RenderImage>(*renderObject))
        renderImage->setHasShadowControls(false);
    else if (auto* renderAttachment = dynamicDowncast<RenderAttachment>(*renderObject))
        renderAttachment->setHasShadowControls(false);
}

bool hasImageControls(const HTMLElement& element)
{
    return hasControls(element);
}

#endif // ENABLE(SERVICE_CONTROLS)

} // namespace ImageControlsMac
} // namespace WebCore
