#include "ToolHandler.h"

#include <algorithm>  // for clamp
#include <cstdint>    // for uint32_t
#include <cstdio>     // for size_t
#include <optional>   // for nullopt, optional
#include <string>     // for operator==, string, basic_string
#include <utility>    // for move

#include <glib.h>  // for g_warning, g_error

#include "control/Tool.h"  // for Tool, Tool::toolSizes
#include "control/actions/ActionDatabase.h"
#include "control/settings/Settings.h"  // for SElement, Settings
#include "model/StrokeStyle.h"          // for StrokeStyle
#include "util/Color.h"
#include "util/Stacktrace.h"  // for Stac...
#include "util/safe_casts.h"  // for as_unsigned

class LineStyle;


ToolListener::~ToolListener() = default;

ToolHandler::ToolHandler(ToolListener* stateChangeListener, ActionDatabase* actionDB, Settings* settings):
        stateChangeListener(stateChangeListener), actionDB(actionDB), settings(settings) {
    initTools();
}

class ToolSelectPDFText: public Tool {
public:
    ToolSelectPDFText(std::string name, ToolType type, Color color): Tool(name, type, color, std::nullopt) {}

    ~ToolSelectPDFText() override{};

    void setColor(Color color) override {
        if (color.alpha == 0) {
            color.alpha = DEFAULT_SELECT_PDF_TEXT_MARKER_OPACITY;
        }
        Tool::setColor(color);
    }

private:
    static const int DEFAULT_SELECT_PDF_TEXT_MARKER_OPACITY = 60;
};

void ToolHandler::initTools() {
    std::array<double, Tool::toolSizes> thickness;
    // pen thicknesses = 0.15, 0.3, 0.5, 0.8, 2 mm
    thickness[TOOL_SIZE_VERY_FINE] = 0.42;
    thickness[TOOL_SIZE_FINE] = 0.85;
    thickness[TOOL_SIZE_MEDIUM] = 1.41;
    thickness[TOOL_SIZE_THICK] = 2.26;
    thickness[TOOL_SIZE_VERY_THICK] = 5.67;
    tools[TOOL_PEN - TOOL_PEN] = std::make_unique<Tool>("pen", TOOL_PEN, Colors::xopp_royalblue, thickness);

    thickness[TOOL_SIZE_VERY_FINE] = 1;
    thickness[TOOL_SIZE_FINE] = 2.83;
    thickness[TOOL_SIZE_MEDIUM] = 8.50;
    thickness[TOOL_SIZE_THICK] = 12;
    thickness[TOOL_SIZE_VERY_THICK] = 18;
    tools[TOOL_ERASER - TOOL_PEN] = std::make_unique<Tool>("eraser", TOOL_ERASER, Colors::black, thickness);

    // highlighter thicknesses = 1, 3, 7 mm
    thickness[TOOL_SIZE_VERY_FINE] = 1;
    thickness[TOOL_SIZE_FINE] = 2.83;
    thickness[TOOL_SIZE_MEDIUM] = 8.50;
    thickness[TOOL_SIZE_THICK] = 19.84;
    thickness[TOOL_SIZE_VERY_THICK] = 30;
    tools[TOOL_HIGHLIGHTER - TOOL_PEN] =
            std::make_unique<Tool>("highlighter", TOOL_HIGHLIGHTER, Colors::yellow, thickness);

    tools[TOOL_TEXT - TOOL_PEN] = std::make_unique<Tool>("text", TOOL_TEXT, Colors::black, std::nullopt);

    tools[TOOL_LATEX - TOOL_PEN] = std::make_unique<Tool>("latex", TOOL_LATEX, Colors::black, std::nullopt);

    tools[TOOL_IMAGE - TOOL_PEN] = std::make_unique<Tool>("image", TOOL_IMAGE, Colors::black, std::nullopt);

    tools[TOOL_SELECT_RECT - TOOL_PEN] =
            std::make_unique<Tool>("selectRect", TOOL_SELECT_RECT, Colors::black, std::nullopt);

    tools[TOOL_SELECT_REGION - TOOL_PEN] =
            std::make_unique<Tool>("selectRegion", TOOL_SELECT_REGION, Colors::black, std::nullopt);

    tools[TOOL_SELECT_MULTILAYER_RECT - TOOL_PEN] =
            std::make_unique<Tool>("selectMultiLayerRect", TOOL_SELECT_MULTILAYER_RECT, Colors::black, std::nullopt);

    tools[TOOL_SELECT_MULTILAYER_REGION - TOOL_PEN] = std::make_unique<Tool>(
            "selectMultiLayerRegion", TOOL_SELECT_MULTILAYER_REGION, Colors::black, std::nullopt);

    tools[TOOL_SELECT_OBJECT - TOOL_PEN] =
            std::make_unique<Tool>("selectObject", TOOL_SELECT_OBJECT, Colors::black, std::nullopt);

    tools[TOOL_VERTICAL_SPACE - TOOL_PEN] =
            std::make_unique<Tool>("verticalSpace", TOOL_VERTICAL_SPACE, Colors::black, std::nullopt);

    tools[TOOL_HAND - TOOL_PEN] = std::make_unique<Tool>("hand", TOOL_HAND, Colors::black, std::nullopt);

    tools[TOOL_PLAY_OBJECT - TOOL_PEN] =
            std::make_unique<Tool>("playObject", TOOL_PLAY_OBJECT, Colors::black, std::nullopt);

    tools[TOOL_DRAW_RECT - TOOL_PEN] = std::make_unique<Tool>("drawRect", TOOL_DRAW_RECT, Colors::black, std::nullopt);

    tools[TOOL_DRAW_ELLIPSE - TOOL_PEN] =
            std::make_unique<Tool>("drawEllipse", TOOL_DRAW_ELLIPSE, Colors::black, std::nullopt);

    tools[TOOL_DRAW_ARROW - TOOL_PEN] =
            std::make_unique<Tool>("drawArrow", TOOL_DRAW_ARROW, Colors::black, std::nullopt);

    tools[TOOL_DRAW_DOUBLE_ARROW - TOOL_PEN] =
            std::make_unique<Tool>("drawDoubleArrow", TOOL_DRAW_DOUBLE_ARROW, Colors::black, std::nullopt);

    tools[TOOL_DRAW_COORDINATE_SYSTEM - TOOL_PEN] =
            std::make_unique<Tool>("drawCoordinateSystem", TOOL_DRAW_COORDINATE_SYSTEM, Colors::black, std::nullopt);

    tools[TOOL_DRAW_SPLINE - TOOL_PEN] =
            std::make_unique<Tool>("drawSpline", TOOL_DRAW_SPLINE, Colors::black, std::nullopt);

    tools[TOOL_FLOATING_TOOLBOX - TOOL_PEN] =
            std::make_unique<Tool>("showFloatingToolbox", TOOL_FLOATING_TOOLBOX, Colors::black, std::nullopt);

    tools[TOOL_SELECT_PDF_TEXT_LINEAR - TOOL_PEN] =
            std::make_unique<ToolSelectPDFText>("selectPdfTextLinear", TOOL_SELECT_PDF_TEXT_LINEAR, Colors::black);

    tools[TOOL_SELECT_PDF_TEXT_RECT - TOOL_PEN] =
            std::make_unique<ToolSelectPDFText>("selectPdfTextRect", TOOL_SELECT_PDF_TEXT_RECT, Colors::black);

    thickness[TOOL_SIZE_VERY_FINE] = .7;
    thickness[TOOL_SIZE_FINE] = 1.41;
    thickness[TOOL_SIZE_MEDIUM] = 2.4;
    thickness[TOOL_SIZE_THICK] = 4;
    thickness[TOOL_SIZE_VERY_THICK] = 7;
    tools[TOOL_LASER_POINTER_PEN - TOOL_PEN] =
            std::make_unique<Tool>("laserPointerPen", TOOL_LASER_POINTER_PEN, Colors::red, thickness);
    thickness[TOOL_SIZE_VERY_FINE] = 1;
    thickness[TOOL_SIZE_FINE] = 2.83;
    thickness[TOOL_SIZE_MEDIUM] = 8.50;
    thickness[TOOL_SIZE_THICK] = 19.84;
    thickness[TOOL_SIZE_VERY_THICK] = 30;
    tools[TOOL_LASER_POINTER_HIGHLIGHTER - TOOL_PEN] =
            std::make_unique<Tool>("laserPointerHighlighter", TOOL_LASER_POINTER_HIGHLIGHTER, Colors::red, thickness);

    tools[TOOL_LINK - TOOL_PEN] = std::make_unique<Tool>("link", TOOL_LINK, Colors::black, std::nullopt);

    this->eraserButtonTool = std::make_unique<Tool>(*tools[TOOL_HIGHLIGHTER - TOOL_PEN]);
    this->stylusButton1Tool = std::make_unique<Tool>(*tools[TOOL_HIGHLIGHTER - TOOL_PEN]);
    this->stylusButton2Tool = std::make_unique<Tool>(*tools[TOOL_HIGHLIGHTER - TOOL_PEN]);
    this->mouseMiddleButtonTool = std::make_unique<Tool>(*tools[TOOL_HIGHLIGHTER - TOOL_PEN]);
    this->mouseRightButtonTool = std::make_unique<Tool>(*tools[TOOL_HIGHLIGHTER - TOOL_PEN]);
    this->mouseButton4Tool = std::make_unique<Tool>(*tools[TOOL_HIGHLIGHTER - TOOL_PEN]);
    this->mouseButton5Tool = std::make_unique<Tool>(*tools[TOOL_HIGHLIGHTER - TOOL_PEN]);
    this->touchDrawingButtonTool = std::make_unique<Tool>(*tools[TOOL_HIGHLIGHTER - TOOL_PEN]);

    this->toolbarSelectedTool = &getTool(TOOL_PEN);
    this->activeTool = &getTool(TOOL_PEN);
}

ToolHandler::~ToolHandler() {
    // Do not delete settings!
    this->settings = nullptr;
}

void ToolHandler::setEraserType(EraserType eraserType) {
    Tool& tool = this->getTool(TOOL_ERASER);
    tool.setEraserType(eraserType);
    eraserTypeChanged();
}

void ToolHandler::setButtonEraserType(EraserType eraserType, Button button) {
    Tool* tool = getButtonTool(button);
    tool->setEraserType(eraserType);
}

void ToolHandler::eraserTypeChanged() {
    if (this->actionDB == nullptr) {
        return;
    }

    this->actionDB->setActionState(Action::TOOL_ERASER_TYPE, this->getEraserType());
}

auto ToolHandler::getEraserType() const -> EraserType {
    // if active tool is eraser get its type
    if (this->activeTool->type == TOOL_ERASER)
        return this->activeTool->getEraserType();
    else  // otherwise get EraserType of Toolbar Eraser
        return this->getTool(TOOL_ERASER).getEraserType();
}

void ToolHandler::selectTool(ToolType type) {
    if (type < 1 || type > TOOL_COUNT) {
        g_warning("unknown tool selected: %i\n", type);
        return;
    }
    this->toolbarSelectedTool = &getTool(type);
    // set activeTool is necessary for fireToolChanged()
    // if called after this method
    // to result in the correct Button shown as active
    this->activeTool = this->toolbarSelectedTool;
}

void ToolHandler::fireToolChanged() const {
    for (auto&& listener: this->toolChangeListeners) { listener(this->activeTool->type); }

    stateChangeListener->toolChanged();
}

void ToolHandler::addToolChangedListener(ToolChangedCallback listener) {
    toolChangeListeners.emplace_back(std::move(listener));
}

auto ToolHandler::getTool(ToolType type) const -> Tool& { return *(this->tools[type - TOOL_PEN]); }

auto ToolHandler::getActiveTool() const -> Tool* {return this->activeTool; }

auto ToolHandler::getToolType() const -> ToolType {
    Tool* tool = this->activeTool;
    return tool->type;
}

auto ToolHandler::hasCapability(ToolCapabilities cap, SelectedTool selectedTool) const -> bool {
    Tool* tool = getSelectedTool(selectedTool);
    return (tool->capabilities & cap) != 0;
}

auto ToolHandler::isDrawingTool() const -> bool {
    Tool* tool = this->activeTool;
    return tool->isDrawingTool();
}

auto ToolHandler::getSize(SelectedTool selectedTool) const -> ToolSize {
    Tool* tool = getSelectedTool(selectedTool);
    return tool->getSize();
}

auto ToolHandler::getPenSize() const -> ToolSize { return tools[TOOL_PEN - TOOL_PEN]->getSize(); }

auto ToolHandler::getEraserSize() const -> ToolSize { return tools[TOOL_ERASER - TOOL_PEN]->getSize(); }

auto ToolHandler::getHighlighterSize() const -> ToolSize { return tools[TOOL_HIGHLIGHTER - TOOL_PEN]->getSize(); }

void ToolHandler::setPenSize(ToolSize size) {
    this->tools[TOOL_PEN - TOOL_PEN]->setSize(size);

    if (this->activeTool->type == TOOL_PEN) {
        this->stateChangeListener->toolSizeChanged();
    }
}

void ToolHandler::setEraserSize(ToolSize size) {
    this->tools[TOOL_ERASER - TOOL_PEN]->setSize(size);

    if (this->activeTool->type == TOOL_ERASER) {
        this->stateChangeListener->toolSizeChanged();
    }
}

void ToolHandler::setHighlighterSize(ToolSize size) {
    this->tools[TOOL_HIGHLIGHTER - TOOL_PEN]->setSize(size);

    if (this->activeTool->type == TOOL_HIGHLIGHTER) {
        this->stateChangeListener->toolSizeChanged();
    }
}

void ToolHandler::setPenFillEnabled(bool fill) {
    this->tools[TOOL_PEN - TOOL_PEN]->setFill(fill);

    if (this->activeTool->type == TOOL_PEN) {
        this->stateChangeListener->toolFillChanged();
    }
}

auto ToolHandler::getPenFillEnabled() const -> bool { return this->tools[TOOL_PEN - TOOL_PEN]->getFill(); }

void ToolHandler::setPenFill(int alpha) { this->tools[TOOL_PEN - TOOL_PEN]->setFillAlpha(alpha); }

auto ToolHandler::getPenFill() const -> int { return this->tools[TOOL_PEN - TOOL_PEN]->getFillAlpha(); }

void ToolHandler::setHighlighterFillEnabled(bool fill) {
    this->tools[TOOL_HIGHLIGHTER - TOOL_PEN]->setFill(fill);

    if (this->activeTool->type == TOOL_HIGHLIGHTER) {
        this->stateChangeListener->toolFillChanged();
    }
}

auto ToolHandler::getHighlighterFillEnabled() const -> bool {
    return this->tools[TOOL_HIGHLIGHTER - TOOL_PEN]->getFill();
}

void ToolHandler::setHighlighterFill(int alpha) { this->tools[TOOL_HIGHLIGHTER - TOOL_PEN]->setFillAlpha(alpha); }

auto ToolHandler::getHighlighterFill() const -> int { return this->tools[TOOL_HIGHLIGHTER - TOOL_PEN]->getFillAlpha(); }

static void inline setColorAlpha(Tool& tool, int alpha) {
    if (tool.hasCapability(TOOL_CAP_COLOR)) {
        Color color = tool.getColor();
        color.alpha = static_cast<uint8_t>(alpha);
        tool.setColor(color);
    } else {
        g_warning("setColorAlpha : tool='%s' has no color capability!", tool.getName().c_str());
        Stacktrace::printStacktrace();
    }
}

void ToolHandler::setSelectPDFTextMarkerOpacity(int alpha) {
    // Use same marker opacity for 'select linear pdf text' or 'select pdf text in rectangle'
    setColorAlpha(this->getTool(TOOL_SELECT_PDF_TEXT_LINEAR), alpha);
    setColorAlpha(this->getTool(TOOL_SELECT_PDF_TEXT_RECT), alpha);
}

auto ToolHandler::getSelectPDFTextMarkerOpacity() const -> int {
    // Use same marker opacity for 'select linear pdf text' or 'select pdf text in rectangle'
    return this->getTool(TOOL_SELECT_PDF_TEXT_LINEAR).getColor().alpha;
}

auto ToolHandler::getThickness() const -> double {
    Tool* tool = this->activeTool;
    if (tool->thickness) {
        return tool->thickness.value()[tool->getSize()];
    }

    g_warning("Request size of \"%s\"", tool->getName().c_str());
    return 0;
}

void ToolHandler::setSize(ToolSize size) {
    ToolSize clippedSize = std::clamp(size, TOOL_SIZE_VERY_FINE, TOOL_SIZE_VERY_THICK);
    if (clippedSize != size)
        g_warning("ToolHandler::setSize: Invalid size! %i", size);

    Tool* tool = this->toolbarSelectedTool;
    tool->setSize(clippedSize);
    this->stateChangeListener->toolSizeChanged();
}

void ToolHandler::setButtonSize(ToolSize size, Button button) {
    ToolSize clippedSize = std::clamp(size, TOOL_SIZE_VERY_FINE, TOOL_SIZE_VERY_THICK);
    if (clippedSize != size)
        g_warning("ToolHandler::setSize: Invalid size! %i", size);

    Tool* tool = getButtonTool(button);
    tool->setSize(clippedSize);
}

void ToolHandler::setLineStyle(const LineStyle& style) {
    Tool* tool = this->toolbarSelectedTool;
    tool->setLineStyle(style);
    this->stateChangeListener->toolLineStyleChanged();
}

void ToolHandler::setColor(Color color, bool userSelection) {
    if (this->activeTool != this->toolbarSelectedTool && !this->hasCapability(TOOL_CAP_COLOR, SelectedTool::active)) {
        this->toolbarSelectedTool->setColor(color);
    }
    Tool* tool = this->activeTool;
    int currentAlpha = tool->getColor().alpha;
    tool->setColor(color);
    this->stateChangeListener->toolColorChanged();
    if (userSelection) {
        // ensure that tool color alpha is re-applied on new selected color;
        setColorAlpha(*tool, currentAlpha);
        this->stateChangeListener->changeColorOfSelection();
    }
}

void ToolHandler::setButtonColor(Color color, Button button) {
    Tool* tool = this->getButtonTool(button);
    tool->setColor(color);
}

auto ToolHandler::getColor() const -> Color { return this->activeTool->getColor(); }

auto ToolHandler::getColorMaskAlpha() const -> Color {
    Color c = this->activeTool->getColor();
    c.alpha = 0xff;
    return c;
}

void ToolHandler::setFillEnabled(bool fill) {
    Tool* tool = this->toolbarSelectedTool;
    tool->setFill(fill);
    this->stateChangeListener->toolFillChanged();
}

auto ToolHandler::getFill() const -> int {
    Tool* tool = this->activeTool;
    if (!tool->getFill()) {
        return -1;
    }
    return tool->getFillAlpha();
}

auto ToolHandler::getLineStyle() const -> const LineStyle& {
    Tool* tool = this->activeTool;
    return tool->getLineStyle();
}

auto ToolHandler::getDrawingType(SelectedTool selectedTool) const -> DrawingType {
    Tool* tool = getSelectedTool(selectedTool);
    return tool->getDrawingType();
}

void ToolHandler::setDrawingType(DrawingType drawingType) {
    Tool* tool = this->toolbarSelectedTool;
    tool->setDrawingType(drawingType);
}

void ToolHandler::setButtonDrawingType(DrawingType drawingType, Button button) {
    Tool* tool = getButtonTool(button);
    tool->setDrawingType(drawingType);
}

void ToolHandler::setButtonStrokeType(StrokeType strokeType, Button button) {
    this->setButtonStrokeType(strokeTypeToLineStyle(strokeType), button);
}

void ToolHandler::setButtonStrokeType(const LineStyle& lineStyle, Button button) {
    Tool* tool = getButtonTool(button);
    tool->setLineStyle(lineStyle);
}

auto ToolHandler::getTools() const -> std::array<std::unique_ptr<Tool>, TOOL_COUNT> const& { return tools; }

void ToolHandler::saveSettings() const {
    SElement& s = settings->getCustomElement("tools");
    s.clear();

    s.setString("current", this->activeTool->getName());

    for (auto&& tool: tools) {
        SElement& st = s.child(tool->getName());
        if (tool->hasCapability(TOOL_CAP_COLOR)) {
            st.setIntHex("color", int(uint32_t(tool->getColor())));
        }

        static constexpr unsigned int SHAPE_CAPS = TOOL_CAP_RULER | TOOL_CAP_RECTANGLE | TOOL_CAP_ELLIPSE |
                                                   TOOL_CAP_ARROW | TOOL_CAP_DOUBLE_ARROW | TOOL_CAP_RECOGNIZER |
                                                   TOOL_CAP_SPLINE;
        if (tool->capabilities & SHAPE_CAPS) {
            st.setString("drawingType", drawingTypeToString(tool->getDrawingType()).data());
        }

        if (tool->hasCapability(TOOL_CAP_SIZE)) {
            std::string value;
            switch (tool->getSize()) {
                case TOOL_SIZE_VERY_FINE:
                    value = "VERY_FINE";
                    break;
                case TOOL_SIZE_FINE:
                    value = "THIN";
                    break;
                case TOOL_SIZE_MEDIUM:
                    value = "MEDIUM";
                    break;
                case TOOL_SIZE_THICK:
                    value = "BIG";
                    break;
                case TOOL_SIZE_VERY_THICK:
                    value = "VERY_BIG";
                    break;
                default:
                    value = "";
            }

            st.setString("size", value);
        }

        if (tool->hasCapability(TOOL_CAP_FILL)) {
            st.setInt("fill", tool->getFill());
            st.setInt("fillAlpha", tool->getFillAlpha());
        }

        if (tool->type == TOOL_PEN) {
            st.setString("style", StrokeStyle::formatStyle(tool->getLineStyle()));
        }

        if (tool->type == TOOL_ERASER) {
            if (this->getEraserType() == ERASER_TYPE_DELETE_STROKE) {
                st.setString("type", "deleteStroke");
            } else if (this->getEraserType() == ERASER_TYPE_WHITEOUT) {
                st.setString("type", "whiteout");
            } else  // ERASER_TYPE_DEFAULT
            {
                st.setString("type", "default");
            }
        }
    }

    settings->customSettingsChanged();
}

void ToolHandler::loadSettings() {
    SElement& s = settings->getCustomElement("tools");

    std::string selectedTool;
    if (s.getString("current", selectedTool)) {
        for (auto&& tool: tools) {
            SElement& st = s.child(tool->getName());

            if (selectedTool == tool->getName()) {
                this->activeTool = tool.get();
            }

            int iColor{};
            if (tool->hasCapability(TOOL_CAP_COLOR) && st.getInt("color", iColor)) {
                tool->setColor(Color(as_unsigned(iColor)));
            }

            std::string drawingType;
            if (st.getString("drawingType", drawingType)) {
                tool->setDrawingType(drawingTypeFromString(drawingType));
            }

            int fill = -1;
            if (st.getInt("fill", fill)) {
                tool->setFill(fill);
            }
            int fillAlpha = -1;
            if (st.getInt("fillAlpha", fillAlpha)) {
                tool->setFillAlpha(fillAlpha);
            }

            std::string style;
            if (st.getString("style", style)) {
                tool->setLineStyle(StrokeStyle::parseStyle(style));
            }

            std::string value;
            if (tool->hasCapability(TOOL_CAP_SIZE) && st.getString("size", value)) {
                if (value == "VERY_FINE") {
                    tool->setSize(TOOL_SIZE_VERY_FINE);
                } else if (value == "THIN") {
                    tool->setSize(TOOL_SIZE_FINE);
                } else if (value == "MEDIUM") {
                    tool->setSize(TOOL_SIZE_MEDIUM);
                } else if (value == "BIG") {
                    tool->setSize(TOOL_SIZE_THICK);
                } else if (value == "VERY_BIG") {
                    tool->setSize(TOOL_SIZE_VERY_THICK);
                } else {
                    g_warning("Settings::Unknown tool size: %s\n", value.c_str());
                }
            }

            if (tool->type == TOOL_ERASER) {
                std::string type;

                if (st.getString("type", type)) {
                    if (type == "deleteStroke") {
                        setEraserType(ERASER_TYPE_DELETE_STROKE);
                    } else if (type == "whiteout") {
                        setEraserType(ERASER_TYPE_WHITEOUT);
                    } else {
                        setEraserType(ERASER_TYPE_DEFAULT);
                    }
                    eraserTypeChanged();
                }
            }
        }
    }
}

bool ToolHandler::pointActiveToolToButtonTool(Button button) {
    Tool* tool = getButtonTool(button);
    if (!tool || this->activeTool == tool) {
        return false;
    }
    this->activeTool = tool;
    return true;
}

bool ToolHandler::pointActiveToolToToolbarTool() {
    if (this->activeTool == this->toolbarSelectedTool)
        return false;
    this->activeTool = this->toolbarSelectedTool;
    return true;
}

auto ToolHandler::getToolThickness(ToolType type) const -> const double* {
    return this->tools[type - TOOL_PEN]->thickness.value().data();
}

/**
 * Change the selection tools capabilities, depending on the selected elements
 */
void ToolHandler::setSelectionEditTools(bool setColor, bool setSize, bool setFill, bool setLineStyle) {
    // For all selection tools, apply the features
    for (size_t i = TOOL_SELECT_RECT - TOOL_PEN; i <= TOOL_SELECT_OBJECT - TOOL_PEN; i++) {
        Tool* t = tools[i].get();
        t->setCapability(TOOL_CAP_COLOR, setColor);
        t->setCapability(TOOL_CAP_SIZE, setSize);
        t->setCapability(TOOL_CAP_FILL, setFill);
        t->setCapability(TOOL_CAP_LINE_STYLE, setLineStyle);
        t->setSize(TOOL_SIZE_NONE);
        t->setColor(Colors::white);
        t->setFill(false);
    }

    if (this->activeTool->type == TOOL_SELECT_RECT || this->activeTool->type == TOOL_SELECT_REGION ||
        this->activeTool->type == TOOL_SELECT_MULTILAYER_RECT || this->activeTool->type == TOOL_SELECT_MULTILAYER_REGION ||
        this->activeTool->type == TOOL_SELECT_OBJECT || this->activeTool->type == TOOL_PLAY_OBJECT) {
        this->stateChangeListener->toolColorChanged();
        this->stateChangeListener->toolSizeChanged();
        this->stateChangeListener->toolFillChanged();
        this->stateChangeListener->toolLineStyleChanged();
        this->fireToolChanged();
    }
}

auto ToolHandler::isSinglePageTool() const -> bool {
    ToolType toolType = this->getToolType();
    DrawingType drawingType = this->getDrawingType();

    return ((toolType == TOOL_PEN || toolType == TOOL_HIGHLIGHTER) &&
            (drawingType == DRAWING_TYPE_ARROW || drawingType == DRAWING_TYPE_DOUBLE_ARROW ||
             drawingType == DRAWING_TYPE_ELLIPSE || drawingType == DRAWING_TYPE_COORDINATE_SYSTEM ||
             drawingType == DRAWING_TYPE_LINE || drawingType == DRAWING_TYPE_RECTANGLE ||
             drawingType == DRAWING_TYPE_SPLINE)) ||
           toolType == TOOL_SELECT_RECT || toolType == TOOL_SELECT_REGION || toolType == TOOL_SELECT_MULTILAYER_RECT ||
           toolType == TOOL_SELECT_MULTILAYER_REGION || toolType == TOOL_SELECT_OBJECT || toolType == TOOL_DRAW_RECT ||
           toolType == TOOL_DRAW_ELLIPSE || toolType == TOOL_DRAW_COORDINATE_SYSTEM || toolType == TOOL_DRAW_ARROW ||
           toolType == TOOL_DRAW_DOUBLE_ARROW || toolType == TOOL_FLOATING_TOOLBOX || toolType == TOOL_DRAW_SPLINE ||
           toolType == TOOL_SELECT_PDF_TEXT_LINEAR || toolType == TOOL_SELECT_PDF_TEXT_RECT || toolType == TOOL_LINK;
}

auto ToolHandler::acceptsOutOfPageEvents() const -> bool {
    ToolType toolType = this->getToolType();
    DrawingType drawingType = this->getDrawingType();

    return ((toolType == TOOL_PEN || toolType == TOOL_HIGHLIGHTER) && (drawingType == DRAWING_TYPE_SPLINE)) ||
           (toolType == TOOL_DRAW_SPLINE);
}

auto ToolHandler::supportsTapFilter() const -> bool {
    ToolType toolType = this->getToolType();

    return toolType == TOOL_PEN || toolType == TOOL_HIGHLIGHTER || toolType == TOOL_LASER_POINTER_PEN ||
           toolType == TOOL_LASER_POINTER_HIGHLIGHTER || toolType == TOOL_HAND || toolType == TOOL_DRAW_RECT ||
           toolType == TOOL_DRAW_ELLIPSE || toolType == TOOL_DRAW_COORDINATE_SYSTEM || toolType == TOOL_DRAW_ARROW ||
           toolType == TOOL_DRAW_DOUBLE_ARROW || toolType == TOOL_DRAW_SPLINE;
}

auto ToolHandler::getSelectedTool(SelectedTool selectedTool) const -> Tool* {
    switch (selectedTool) {
        case SelectedTool::active:
            return this->activeTool;
        case SelectedTool::toolbar:
            return this->toolbarSelectedTool;
        default:
            g_error("This button does not exist.");
    }
}

auto ToolHandler::getButtonTool(Button button) const -> Tool* {
    switch (button) {
        case Button::BUTTON_ERASER:
            return this->eraserButtonTool.get();
        case Button::BUTTON_STYLUS_ONE:
            return this->stylusButton1Tool.get();
        case Button::BUTTON_STYLUS_TWO:
            return this->stylusButton2Tool.get();
        case Button::BUTTON_MOUSE_LEFT:
            return this->mouseLeftButtonTool.get();
        case Button::BUTTON_MOUSE_MIDDLE:
            return this->mouseMiddleButtonTool.get();
        case Button::BUTTON_MOUSE_RIGHT:
            return this->mouseRightButtonTool.get();
        case Button::BUTTON_MOUSE_4:
            return this->mouseButton4Tool.get();
        case Button::BUTTON_MOUSE_5:
            return this->mouseButton5Tool.get();
        case Button::BUTTON_TOUCH:
            return this->touchDrawingButtonTool.get();
        default:
            g_error("This button is not defined for ToolHandler.");
    }
}

void ToolHandler::resetButtonTool(ToolType type, Button button) {
    auto& tool = *(tools[type - TOOL_PEN]);
    switch (button) {
        case Button::BUTTON_ERASER:
            this->eraserButtonTool.reset(new Tool(tool));
            break;
        case Button::BUTTON_STYLUS_ONE:
            this->stylusButton1Tool.reset(new Tool(tool));
            break;
        case Button::BUTTON_STYLUS_TWO:
            this->stylusButton2Tool.reset(new Tool(tool));
            break;
        case Button::BUTTON_MOUSE_LEFT:
            this->mouseLeftButtonTool.reset(new Tool(tool));
            break;
        case Button::BUTTON_MOUSE_MIDDLE:
            this->mouseMiddleButtonTool.reset(new Tool(tool));
            break;
        case Button::BUTTON_MOUSE_RIGHT:
            this->mouseRightButtonTool.reset(new Tool(tool));
            break;
        case Button::BUTTON_MOUSE_4:
            this->mouseButton4Tool.reset(new Tool(tool));
            break;
        case Button::BUTTON_MOUSE_5:
            this->mouseButton5Tool.reset(new Tool(tool));
            break;
        case Button::BUTTON_TOUCH:
            this->touchDrawingButtonTool.reset(new Tool(tool));
            break;
        default:
            g_error("This button is not defined for ToolHandler.");
    }
}
