// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Storing of snapping preferences.
 *
 * Authors:
 *   Diederik van Lierop <mail@diedenrezi.nl>
 *
 * Copyright (C) 2008 - 2011 Authors
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include "inkscape.h"
#include "snap-preferences.h"

Inkscape::SnapPreferences::SnapPreferences() :
    _snap_enabled_globally(true),
    _snap_postponed_globally(false),
    _strict_snapping(true)
{
    // Check for powers of two; see the comments in snap-enums.h
    g_assert((SNAPTARGET_BBOX_CATEGORY != 0) && !(SNAPTARGET_BBOX_CATEGORY & (SNAPTARGET_BBOX_CATEGORY - 1)));
    g_assert((SNAPTARGET_DISTRIBUTION_CATEGORY != 0) && !(SNAPTARGET_DISTRIBUTION_CATEGORY & (SNAPTARGET_DISTRIBUTION_CATEGORY - 1)));
    g_assert((SNAPTARGET_ALIGNMENT_CATEGORY != 0) && !(SNAPTARGET_ALIGNMENT_CATEGORY & (SNAPTARGET_ALIGNMENT_CATEGORY - 1)));
    g_assert((SNAPTARGET_NODE_CATEGORY != 0) && !(SNAPTARGET_NODE_CATEGORY & (SNAPTARGET_NODE_CATEGORY - 1)));
    g_assert((SNAPTARGET_DATUMS_CATEGORY != 0) && !(SNAPTARGET_DATUMS_CATEGORY & (SNAPTARGET_DATUMS_CATEGORY - 1)));
    g_assert((SNAPTARGET_OTHERS_CATEGORY != 0) && !(SNAPTARGET_OTHERS_CATEGORY & (SNAPTARGET_OTHERS_CATEGORY - 1)));

    for (int & _active_snap_target : _active_snap_targets) {
        _active_snap_target = -1;
    }
    clearTargetMask();

    for (bool& b : _simple_snapping) {
        b = false;
    }
}

bool Inkscape::SnapPreferences::get_simple_snap(Inkscape::SimpleSnap option) const {
    auto index = static_cast<size_t>(option);
    assert(index < size_t(Inkscape::SimpleSnap::_MaxEnumValue));
    return _simple_snapping[index];
}

void Inkscape::SnapPreferences::set_simple_snap(Inkscape::SimpleSnap option, bool enable) {
    auto index = static_cast<size_t>(option);
    assert(index < size_t(Inkscape::SimpleSnap::_MaxEnumValue));
    _simple_snapping[index] = enable;
}

bool Inkscape::SnapPreferences::isAnyDatumSnappable() const
{
    return isTargetSnappable(SNAPTARGET_GUIDE, SNAPTARGET_GRID, SNAPTARGET_PAGE_EDGE_BORDER, SNAPTARGET_PAGE_MARGIN_BORDER, SNAPTARGET_PAGE_BLEED_BORDER);
}

bool Inkscape::SnapPreferences::isAnyCategorySnappable() const
{
    return isTargetSnappable(SNAPTARGET_NODE_CATEGORY, SNAPTARGET_BBOX_CATEGORY, SNAPTARGET_OTHERS_CATEGORY)
        || isTargetSnappable(SNAPTARGET_GUIDE, SNAPTARGET_GRID, SNAPTARGET_PAGE_EDGE_BORDER, SNAPTARGET_PAGE_MARGIN_BORDER, SNAPTARGET_PAGE_BLEED_BORDER);
}

void Inkscape::SnapPreferences::_mapTargetToArrayIndex(Inkscape::SnapTargetType &target, bool &always_on, bool &group_on) const
{
    if (target == SNAPTARGET_BBOX_CATEGORY ||
            target == SNAPTARGET_NODE_CATEGORY ||
            target == SNAPTARGET_OTHERS_CATEGORY ||
            target == SNAPTARGET_DATUMS_CATEGORY ||
            target == SNAPTARGET_ALIGNMENT_CATEGORY ||
            target == SNAPTARGET_DISTRIBUTION_CATEGORY) {
        // These main targets should be handled separately, because otherwise we might call isTargetSnappable()
        // for them (to check whether the corresponding group is on) which would lead to an infinite recursive loop
        always_on = (target == SNAPTARGET_DATUMS_CATEGORY);
        group_on = true;
        return;
    }

    if (target & SNAPTARGET_BBOX_CATEGORY) {
        group_on = isTargetSnappable(SNAPTARGET_BBOX_CATEGORY); // Only if the group with bbox sources/targets has been enabled, then we might snap to any of the bbox targets
        return;
    }

    if (target & SNAPTARGET_NODE_CATEGORY) {
        group_on = isTargetSnappable(SNAPTARGET_NODE_CATEGORY); // Only if the group with path/node sources/targets has been enabled, then we might snap to any of the nodes/paths
        switch (target) {
            case SNAPTARGET_RECT_CORNER:
                target = SNAPTARGET_NODE_CUSP;
                break;
            case SNAPTARGET_ELLIPSE_QUADRANT_POINT:
                target = SNAPTARGET_NODE_SMOOTH;
                break;
            case SNAPTARGET_PATH_GUIDE_INTERSECTION:
                target = SNAPTARGET_PATH_INTERSECTION;
                break;
            // case SNAPTARGET_PATH_PERPENDICULAR:
            // case SNAPTARGET_PATH_TANGENTIAL:
                // target = SNAPTARGET_PATH;
                break;
            default:
                break;
        }

        return;
    }

    if (target & SNAPTARGET_DATUMS_CATEGORY) {
        group_on = true; // These snap targets cannot be disabled as part of a disabled group;
        switch (target) {
            // Some snap targets don't have their own toggle. These targets are called "secondary targets". We will re-map
            // them to their cousin which does have a toggle, and which is called a "primary target"
            case SNAPTARGET_GRID_INTERSECTION:
            case SNAPTARGET_GRID_PERPENDICULAR:
                target = SNAPTARGET_GRID;
                break;
            case SNAPTARGET_GUIDE_INTERSECTION:
            case SNAPTARGET_GUIDE_ORIGIN:
            case SNAPTARGET_GUIDE_PERPENDICULAR:
                target = SNAPTARGET_GUIDE;
                break;
            case SNAPTARGET_PAGE_EDGE_CORNER:
            case SNAPTARGET_PAGE_EDGE_CENTER:
                target = SNAPTARGET_PAGE_EDGE_BORDER;
                break;

            case SNAPTARGET_PAGE_MARGIN_CORNER:
            case SNAPTARGET_PAGE_MARGIN_CENTER:
                target = SNAPTARGET_PAGE_MARGIN_BORDER;
                break;

            case SNAPTARGET_PAGE_BLEED_CORNER:
                target = SNAPTARGET_PAGE_BLEED_BORDER;
                break;


            // Some snap targets cannot be toggled at all, and are therefore always enabled
            case SNAPTARGET_GRID_GUIDE_INTERSECTION:
                always_on = true; // Doesn't have it's own button
                break;

            // These are only listed for completeness
            case SNAPTARGET_GRID:
            case SNAPTARGET_GRID_LINE:
            case SNAPTARGET_GUIDE:
            case SNAPTARGET_PAGE_EDGE_BORDER:
            case SNAPTARGET_PAGE_MARGIN_BORDER:
            case SNAPTARGET_PAGE_BLEED_BORDER:
            case SNAPTARGET_DATUMS_CATEGORY:
                break;
            default:
                g_warning("Snap-preferences warning: Undefined snap target (#%i)", target);
                break;
        }
        return;
    }

    if (target & SNAPTARGET_ALIGNMENT_CATEGORY) {
        group_on = isTargetSnappable(SNAPTARGET_ALIGNMENT_CATEGORY);
        return;
    }

    if (target & SNAPTARGET_DISTRIBUTION_CATEGORY) {
        group_on = isTargetSnappable(SNAPTARGET_DISTRIBUTION_CATEGORY);
        return;
    }

    if (target & SNAPTARGET_OTHERS_CATEGORY) {
        // Only if the group with "other" snap sources/targets has been enabled, then we might snap to any of those targets
        // ... but this doesn't hold for the page border, grids, and guides
        group_on = isTargetSnappable(SNAPTARGET_OTHERS_CATEGORY);
        switch (target) {
            // Some snap targets don't have their own toggle. These targets are called "secondary targets". We will re-map
            // them to their cousin which does have a toggle, and which is called a "primary target"
            case SNAPTARGET_TEXT_ANCHOR:
                target = SNAPTARGET_TEXT_BASELINE;
                break;

            case SNAPTARGET_IMG_CORNER: // Doesn't have its own button, on if the group is on
                target = SNAPTARGET_OTHERS_CATEGORY;
                break;
            // Some snap targets cannot be toggled at all, and are therefore always enabled
            case SNAPTARGET_CONSTRAINED_ANGLE:
            case SNAPTARGET_CONSTRAINT:
                always_on = true; // Doesn't have it's own button
                break;

            // These are only listed for completeness
            case SNAPTARGET_OBJECT_MIDPOINT:
            case SNAPTARGET_ROTATION_CENTER:
            case SNAPTARGET_TEXT_BASELINE:
            case SNAPTARGET_OTHERS_CATEGORY:
                break;
            default:
                g_warning("Snap-preferences warning: Undefined snap target (#%i)", target);
                break;
        }

        return;
    }

    if (target == SNAPTARGET_UNDEFINED ) {
        g_warning("Snap-preferences warning: Undefined snaptarget (#%i)", target);
    } else {
        g_warning("Snap-preferences warning: Snaptarget not handled (#%i)", target);
    }
}

void Inkscape::SnapPreferences::setTargetSnappable(Inkscape::SnapTargetType const target, bool enabled)
{
    bool always_on = false;
    bool group_on = false; // Only needed as a dummy
    Inkscape::SnapTargetType index = target;

    _mapTargetToArrayIndex(index, always_on, group_on);

    if (always_on) { // If true, then this snap target is always active and cannot be toggled
        // Catch coding errors
        g_warning("Snap-preferences warning: Trying to enable/disable a snap target (#%i) that's always on by definition", index);
    } else {
        if (index == target) { // I.e. if it has not been re-mapped, then we have a primary target at hand
            _active_snap_targets[index] = enabled;
        } else { // If it has been re-mapped though, then this target does not have its own toggle button and should therefore not be set
            g_warning("Snap-preferences warning: Trying to enable/disable a secondary snap target (#%i); only primary targets can be set", index);
        }
    }
}

/**
 * Set a target mask, which will turn off all other targets except the masked ones.
 *
 * target - The Snap Target to change the mask for.
 * enabled - The mask setting to set.
 *    -1 means use user settings (turn off mask)
 *     0 means mask with disabled,
 *     1 means mask with enabled,
 */
void Inkscape::SnapPreferences::setTargetMask(Inkscape::SnapTargetType const target, int enabled)
{
    bool always_on = false;
    bool group_on = false; // Only needed as a dummy
    Inkscape::SnapTargetType index = target;

    _mapTargetToArrayIndex(index, always_on, group_on);

    _active_mask_targets[index] = enabled;
}

/**
 * Clear the target mask, this should be done in a four step process.
 *
 * snap_manager->snaprepfs->clearTargetMask(0); // Default all options to disabled
 * snap_manager->snaprepfs->setTargetMask(SOME_TARGET, 1);
 * snap_manager->freeSnap(...);
 * snap_manager->snaprepfs->clearTargetMask(); // Turns off masking
 */
void Inkscape::SnapPreferences::clearTargetMask(int enabled)
{
    for (int & _active_mask_targets : _active_mask_targets) {
        _active_mask_targets = enabled;
    }
}

bool Inkscape::SnapPreferences::isTargetSnappable(Inkscape::SnapTargetType const target) const
{
    bool always_on = false;
    bool group_on = false;
    Inkscape::SnapTargetType index = target;

    _mapTargetToArrayIndex(index, always_on, group_on);

    // Check masking first, it over-rides even group_on
    if (_active_mask_targets[index] != -1) {
        return _active_mask_targets[index];
    }

    if (group_on) { // If true, then this snap target is in a snap group that has been enabled (e.g. bbox group, nodes/paths group, or "others" group
        if (always_on) { // If true, then this snap target is always active and cannot be toggled
            return true;
        } else {
            if (_active_snap_targets[index] == -1) {
                // Catch coding errors
                g_warning("Snap-preferences warning: Using an uninitialized snap target setting (#%i)", index);
                // This happens if setTargetSnappable() has not been called for this parameter, e.g. from within sp_namedview_set,
                // or if this target index doesn't exist at all
            }
            return _active_snap_targets[index];
        }
    } else {
        return false;
    }
}

bool Inkscape::SnapPreferences::isTargetSnappable(Inkscape::SnapTargetType const target1, Inkscape::SnapTargetType const target2) const {
    return isTargetSnappable(target1) || isTargetSnappable(target2);
}

bool Inkscape::SnapPreferences::isTargetSnappable(Inkscape::SnapTargetType const target1, Inkscape::SnapTargetType const target2, Inkscape::SnapTargetType const target3) const {
    return isTargetSnappable(target1) || isTargetSnappable(target2) || isTargetSnappable(target3);
}

bool Inkscape::SnapPreferences::isTargetSnappable(Inkscape::SnapTargetType const target1, Inkscape::SnapTargetType const target2, Inkscape::SnapTargetType const target3, Inkscape::SnapTargetType const target4) const {
    return isTargetSnappable(target1) || isTargetSnappable(target2) || isTargetSnappable(target3) || isTargetSnappable(target4);
}

bool Inkscape::SnapPreferences::isTargetSnappable(Inkscape::SnapTargetType const target1, Inkscape::SnapTargetType const target2, Inkscape::SnapTargetType const target3, Inkscape::SnapTargetType const target4, Inkscape::SnapTargetType const target5) const {
    return isTargetSnappable(target1) || isTargetSnappable(target2) || isTargetSnappable(target3) || isTargetSnappable(target4) || isTargetSnappable(target5);
}

bool Inkscape::SnapPreferences::isSnapButtonEnabled(Inkscape::SnapTargetType const target) const
{
    bool always_on = false; // Only needed as a dummy
    bool group_on = false; // Only needed as a dummy
    Inkscape::SnapTargetType index = target;

    _mapTargetToArrayIndex(index, always_on, group_on);

    if (_active_snap_targets[index] == -1) {
        // Catch coding errors
        g_warning("Snap-preferences warning: Using an uninitialized snap target setting (#%i)", index);
        // This happens if setTargetSnappable() has not been called for this parameter, e.g. from within sp_namedview_set,
        // or if this target index doesn't exist at all
    } else {
        if (index == target) { // I.e. if it has not been re-mapped, then we have a primary target at hand, which does have its own toggle button
            return _active_snap_targets[index];
        } else { // If it has been re-mapped though, then this target does not have its own toggle button and therefore the button status cannot be read
            g_warning("Snap-preferences warning: Trying to determine the button status of a secondary snap target (#%i); However, only primary targets have a button", index);
        }
    }

    return false;
}

Inkscape::SnapTargetType Inkscape::SnapPreferences::source2target(Inkscape::SnapSourceType source) const
{
    switch (source)
    {
        case SNAPSOURCE_UNDEFINED:
            return SNAPTARGET_UNDEFINED;
        case SNAPSOURCE_BBOX_CATEGORY:
            return SNAPTARGET_BBOX_CATEGORY;
        case SNAPSOURCE_BBOX_CORNER:
            return SNAPTARGET_BBOX_CORNER;
        case SNAPSOURCE_BBOX_MIDPOINT:
            return SNAPTARGET_BBOX_MIDPOINT;
        case SNAPSOURCE_BBOX_EDGE_MIDPOINT:
            return SNAPTARGET_BBOX_EDGE_MIDPOINT;
        case SNAPSOURCE_NODE_CATEGORY:
            return SNAPTARGET_NODE_CATEGORY;
        case SNAPSOURCE_NODE_SMOOTH:
            return SNAPTARGET_NODE_SMOOTH;
        case SNAPSOURCE_NODE_CUSP:
            return SNAPTARGET_NODE_CUSP;
        case SNAPSOURCE_LINE_MIDPOINT:
            return SNAPTARGET_LINE_MIDPOINT;
        case SNAPSOURCE_PATH_INTERSECTION:
            return SNAPTARGET_PATH_INTERSECTION;
        case SNAPSOURCE_RECT_CORNER:
            return SNAPTARGET_RECT_CORNER;
        case SNAPSOURCE_ELLIPSE_QUADRANT_POINT:
            return SNAPTARGET_ELLIPSE_QUADRANT_POINT;
        case SNAPSOURCE_DATUMS_CATEGORY:
            return SNAPTARGET_DATUMS_CATEGORY;
        case SNAPSOURCE_GUIDE:
            return SNAPTARGET_GUIDE;
        case SNAPSOURCE_GUIDE_ORIGIN:
            return SNAPTARGET_GUIDE_ORIGIN;
        case SNAPSOURCE_OTHERS_CATEGORY:
            return SNAPTARGET_OTHERS_CATEGORY;
        case SNAPSOURCE_ROTATION_CENTER:
            return SNAPTARGET_ROTATION_CENTER;
        case SNAPSOURCE_OBJECT_MIDPOINT:
            return SNAPTARGET_OBJECT_MIDPOINT;
        case SNAPSOURCE_IMG_CORNER:
            return SNAPTARGET_IMG_CORNER;
        case SNAPSOURCE_TEXT_ANCHOR:
            return SNAPTARGET_TEXT_ANCHOR;

        case SNAPSOURCE_NODE_HANDLE:
        case SNAPSOURCE_OTHER_HANDLE:
        case SNAPSOURCE_CONVEX_HULL_CORNER:
            // For these snapsources there doesn't exist an equivalent snap target
            return SNAPTARGET_NODE_CATEGORY;
        case SNAPSOURCE_GRID_PITCH:
            return SNAPTARGET_GRID;

        case SNAPSOURCE_PAGE_CORNER:
            return SNAPTARGET_PAGE_EDGE_CORNER;
        case SNAPSOURCE_PAGE_CENTER:
            return SNAPTARGET_PAGE_EDGE_CENTER;

        case SNAPSOURCE_ALIGNMENT_CATEGORY:
            return SNAPTARGET_ALIGNMENT_CATEGORY;
        case SNAPSOURCE_ALIGNMENT_BBOX_CORNER:
            return SNAPTARGET_ALIGNMENT_BBOX_CORNER;
        case SNAPSOURCE_ALIGNMENT_BBOX_MIDPOINT:
            return SNAPTARGET_ALIGNMENT_BBOX_EDGE_MIDPOINT;
        case SNAPSOURCE_ALIGNMENT_BBOX_EDGE_MIDPOINT:
            return SNAPTARGET_ALIGNMENT_BBOX_EDGE_MIDPOINT;
        case SNAPSOURCE_ALIGNMENT_PAGE_CENTER:
            return SNAPTARGET_ALIGNMENT_PAGE_EDGE_CENTER;
        case SNAPSOURCE_ALIGNMENT_PAGE_CORNER:
            return SNAPTARGET_ALIGNMENT_PAGE_EDGE_CORNER;
        case Inkscape::SNAPSOURCE_ALIGNMENT_HANDLE:
            return SNAPTARGET_ALIGNMENT_HANDLE;

        default:
            g_warning("Mapping of snap source to snap target undefined (#%i)", source);
            return SNAPTARGET_UNDEFINED;
    }
}

bool Inkscape::SnapPreferences::isSourceSnappable(Inkscape::SnapSourceType const source) const
{
    return isTargetSnappable(source2target(source));
}


/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
