/*
 * Copyright (C) 2017 Igalia S.L.
 *
 * 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 "CommandResult.h"

namespace WebDriver {

// These error codes are specified in JSON-RPC 2.0, Section 5.1.
enum ProtocolErrorCode {
    ParseError = -32700,
    InvalidRequest = -32600,
    MethodNotFound = -32601,
    InvalidParams = -32602,
    InternalError = -32603,
    ServerError = -32000
};

CommandResult::CommandResult(RefPtr<JSON::Value>&& result, std::optional<ErrorCode> errorCode)
    : m_errorCode(errorCode)
{
    if (!m_errorCode) {
        m_result = WTFMove(result);
        return;
    }

    if (!result)
        return;

    auto errorObject = result->asObject();
    if (!errorObject)
        return;

    auto error = errorObject->getInteger("code");
    if (!error)
        return;

    auto errorMessage = errorObject->getString("message");
    if (!errorMessage)
        return;

    switch (*error) {
    case ProtocolErrorCode::ParseError:
    case ProtocolErrorCode::InvalidRequest:
    case ProtocolErrorCode::MethodNotFound:
    case ProtocolErrorCode::InvalidParams:
    case ProtocolErrorCode::InternalError:
        m_errorCode = ErrorCode::UnknownError;
        m_errorMessage = errorMessage;
        break;
    case ProtocolErrorCode::ServerError: {
        String errorName;
        auto position = errorMessage.find(';');
        if (position != notFound) {
            errorName = errorMessage.substring(0, position);
            m_errorMessage = errorMessage.substring(position + 1);
        } else
            errorName = errorMessage;

        if (errorName == "WindowNotFound")
            m_errorCode = ErrorCode::NoSuchWindow;
        else if (errorName == "FrameNotFound")
            m_errorCode = ErrorCode::NoSuchFrame;
        else if (errorName == "NotImplemented")
            m_errorCode = ErrorCode::UnsupportedOperation;
        else if (errorName == "ElementNotInteractable")
            m_errorCode = ErrorCode::ElementNotInteractable;
        else if (errorName == "JavaScriptError")
            m_errorCode = ErrorCode::JavascriptError;
        else if (errorName == "JavaScriptTimeout")
            m_errorCode = ErrorCode::ScriptTimeout;
        else if (errorName == "NodeNotFound")
            m_errorCode = ErrorCode::StaleElementReference;
        else if (errorName == "InvalidNodeIdentifier")
            m_errorCode = ErrorCode::NoSuchElement;
        else if (errorName == "MissingParameter" || errorName == "InvalidParameter")
            m_errorCode = ErrorCode::InvalidArgument;
        else if (errorName == "InvalidElementState")
            m_errorCode = ErrorCode::InvalidElementState;
        else if (errorName == "InvalidSelector")
            m_errorCode = ErrorCode::InvalidSelector;
        else if (errorName == "Timeout")
            m_errorCode = ErrorCode::Timeout;
        else if (errorName == "NoJavaScriptDialog")
            m_errorCode = ErrorCode::NoSuchAlert;
        else if (errorName == "ElementNotSelectable")
            m_errorCode = ErrorCode::ElementNotSelectable;
        else if (errorName == "ScreenshotError")
            m_errorCode = ErrorCode::UnableToCaptureScreen;
        else if (errorName == "UnexpectedAlertOpen")
            m_errorCode = ErrorCode::UnexpectedAlertOpen;
        else if (errorName == "TargetOutOfBounds")
            m_errorCode = ErrorCode::MoveTargetOutOfBounds;

        break;
    }
    }
}

CommandResult::CommandResult(ErrorCode errorCode, std::optional<String> errorMessage)
    : m_errorCode(errorCode)
    , m_errorMessage(errorMessage)
{
}

unsigned CommandResult::httpStatusCode() const
{
    if (!m_errorCode)
        return 200;

    // §6.6 Handling Errors.
    // https://www.w3.org/TR/webdriver/#handling-errors
    switch (m_errorCode.value()) {
    case ErrorCode::ElementClickIntercepted:
    case ErrorCode::ElementNotSelectable:
    case ErrorCode::ElementNotInteractable:
    case ErrorCode::InvalidArgument:
    case ErrorCode::InvalidElementState:
    case ErrorCode::InvalidSelector:
        return 400;
    case ErrorCode::NoSuchAlert:
    case ErrorCode::NoSuchCookie:
    case ErrorCode::NoSuchElement:
    case ErrorCode::NoSuchFrame:
    case ErrorCode::NoSuchWindow:
    case ErrorCode::NoSuchShadowRoot:
    case ErrorCode::StaleElementReference:
    case ErrorCode::DetachedShadowRoot:
    case ErrorCode::InvalidSessionID:
    case ErrorCode::UnknownCommand:
        return 404;
    case ErrorCode::JavascriptError:
    case ErrorCode::MoveTargetOutOfBounds:
    case ErrorCode::ScriptTimeout:
    case ErrorCode::SessionNotCreated:
    case ErrorCode::Timeout:
    case ErrorCode::UnableToCaptureScreen:
    case ErrorCode::UnexpectedAlertOpen:
    case ErrorCode::UnknownError:
    case ErrorCode::UnsupportedOperation:
        return 500;
    }

    ASSERT_NOT_REACHED();
    return 200;
}

String CommandResult::errorString() const
{
    ASSERT(isError());

    switch (m_errorCode.value()) {
    case ErrorCode::ElementClickIntercepted:
        return "element click intercepted"_s;
    case ErrorCode::ElementNotSelectable:
        return "element not selectable"_s;
    case ErrorCode::ElementNotInteractable:
        return "element not interactable"_s;
    case ErrorCode::DetachedShadowRoot:
        return "detached shadow root"_s;
    case ErrorCode::InvalidArgument:
        return "invalid argument"_s;
    case ErrorCode::InvalidElementState:
        return "invalid element state"_s;
    case ErrorCode::InvalidSelector:
        return "invalid selector"_s;
    case ErrorCode::InvalidSessionID:
        return "invalid session id"_s;
    case ErrorCode::JavascriptError:
        return "javascript error"_s;
    case ErrorCode::NoSuchAlert:
        return "no such alert"_s;
    case ErrorCode::NoSuchCookie:
        return "no such cookie"_s;
    case ErrorCode::NoSuchElement:
        return "no such element"_s;
    case ErrorCode::NoSuchFrame:
        return "no such frame"_s;
    case ErrorCode::NoSuchShadowRoot:
        return "no such shadow root"_s;
    case ErrorCode::NoSuchWindow:
        return "no such window"_s;
    case ErrorCode::ScriptTimeout:
        return "script timeout"_s;
    case ErrorCode::SessionNotCreated:
        return "session not created"_s;
    case ErrorCode::StaleElementReference:
        return "stale element reference"_s;
    case ErrorCode::Timeout:
        return "timeout"_s;
    case ErrorCode::UnableToCaptureScreen:
        return "unable to capture screen"_s;
    case ErrorCode::MoveTargetOutOfBounds:
        return "move target out of bounds"_s;
    case ErrorCode::UnexpectedAlertOpen:
        return "unexpected alert open"_s;
    case ErrorCode::UnknownCommand:
        return "unknown command"_s;
    case ErrorCode::UnknownError:
        return "unknown error"_s;
    case ErrorCode::UnsupportedOperation:
        return "unsupported operation"_s;
    }

    ASSERT_NOT_REACHED();
    return emptyString();
}

} // namespace WebDriver
