(function(requirejs, require, define) {
    define(['js/capa/drag_and_drop/update_input', 'js/capa/drag_and_drop/targets'], function(updateInput, Targets) {
        return {
            moveDraggableTo: function(moveType, target, funcCallback) {
                var self, offset;

                if (this.hasLoaded === false) {
                    self = this;

                    setTimeout(function() {
                        self.moveDraggableTo(moveType, target, funcCallback);
                    }, 50);

                    return;
                }

                if ((this.isReusable === true) && (this.isOriginal === true)) {
                    this.makeDraggableCopy(function(draggableCopy) {
                        draggableCopy.moveDraggableTo(moveType, target, funcCallback);
                    });

                    return;
                }

                offset = 0;
                if (this.state.config.targetOutline === true) {
                    offset = 1;
                }

                this.inContainer = false;

                if (this.isOriginal === true) {
                    this.containerEl.hide();
                    this.iconEl.detach();
                }

                if (this.iconImgEl !== null) {
                    this.iconImgEl.css({
                        width: this.iconWidth,
                        height: this.iconHeight
                    });
                }

                this.iconEl.css({
                    'background-color': this.iconElBGColor,
                    'padding-left': this.iconElPadding,
                    'padding-right': this.iconElPadding,
                    border: this.iconElBorder,
                    width: this.iconWidth,
                    height: this.iconHeight
                });
                if (moveType === 'target') {
                    this.iconEl.css({
                        left: target.offset.left + 0.5 * target.w - this.iconWidth * 0.5 + offset - this.iconElLeftOffset,
                        top: target.offset.top + 0.5 * target.h - this.iconHeight * 0.5 + offset
                    });
                } else {
                    this.iconEl.css({
                        left: target.x - this.iconWidth * 0.5 + offset - this.iconElLeftOffset,
                        top: target.y - this.iconHeight * 0.5 + offset
                    });
                }
                this.iconEl.appendTo(this.state.baseImageEl.parent());

                if (this.labelEl !== null) {
                    if (this.isOriginal === true) {
                        this.labelEl.detach();
                    }
                    this.labelEl.css({
                        'background-color': this.state.config.labelBgColor,
                        'padding-left': 8,
                        'padding-right': 8,
                        border: '1px solid black'
                    });
                    if (moveType === 'target') {
                        this.labelEl.css({
                            left: target.offset.left + 0.5 * target.w - this.labelWidth * 0.5 + offset - 9, // Account for padding, border.
                            top: target.offset.top + 0.5 * target.h + this.iconHeight * 0.5 + 5 + offset
                        });
                    } else {
                        this.labelEl.css({
                            left: target.x - this.labelWidth * 0.5 + offset - 9, // Account for padding, border.
                            top: target.y - this.iconHeight * 0.5 + this.iconHeight + 5 + offset
                        });
                    }
                    this.labelEl.appendTo(this.state.baseImageEl.parent());
                }

                if (moveType === 'target') {
                    target.addDraggable(this);
                } else {
                    this.x = target.x;
                    this.y = target.y;
                }

                this.zIndex = 1000;
                this.correctZIndexes();

                Targets.initializeTargetField(this);

                if (this.isOriginal === true) {
                    this.state.numDraggablesInSlider -= 1;
                    this.state.updateArrowOpacity();
                }

                if ($.isFunction(funcCallback) === true) {
                    funcCallback();
                }
            },

            // At this point the mouse was realeased, and we need to check
            // where the draggable eneded up. Based on several things, we
            // will either move the draggable back to the slider, or update
            // the input with the user's answer (X-Y position of the draggable,
            // or the ID of the target where it landed.
            checkLandingElement: function() {
                var positionIE;

                this.mousePressed = false;
                positionIE = this.iconEl.position();

                if (this.state.config.individualTargets === true) {
                    if (this.checkIfOnTarget(positionIE) === true) {
                        this.correctZIndexes();

                        Targets.initializeTargetField(this);
                    } else {
                        if (this.onTarget !== null) {
                            this.onTarget.removeDraggable(this);
                        }

                        this.moveBackToSlider();

                        if (this.isOriginal === true) {
                            this.state.numDraggablesInSlider += 1;
                        }
                    }
                } else {
                    if (
                        (positionIE.left < 0)
                || (positionIE.left + this.iconWidth > this.state.baseImageEl.width())
                || (positionIE.top < 0)
                || (positionIE.top + this.iconHeight > this.state.baseImageEl.height())
                    ) {
                        this.moveBackToSlider();

                        this.x = -1;
                        this.y = -1;

                        if (this.isOriginal === true) {
                            this.state.numDraggablesInSlider += 1;
                        }
                    } else {
                        this.correctZIndexes();

                        this.x = positionIE.left + this.iconWidth * 0.5;
                        this.y = positionIE.top + this.iconHeight * 0.5;

                        Targets.initializeTargetField(this);
                    }
                }

                if (this.isOriginal === true) {
                    this.state.updateArrowOpacity();
                }
                updateInput.update(this.state);
            },

            // Determine if a draggable, after it was relased, ends up on a
            // target. We do this by iterating over all of the targets, and
            // for each one we check whether the draggable's center is
            // within the target's dimensions.
            //
            // positionIE is the object as returned by
            //
            //     this.iconEl.position()
            checkIfOnTarget: function(positionIE) {
                var c1, target;

                for (c1 = 0; c1 < this.state.targets.length; c1 += 1) {
                    target = this.state.targets[c1];

                    // If only one draggable per target is allowed, and
                    // the current target already has a draggable on it
                    // (with an ID different from the one we are checking
                    // against), then go to next target.
                    if (
                        (this.state.config.onePerTarget === true)
                && (target.draggableList.length === 1)
                && (target.draggableList[0].uniqueId !== this.uniqueId)
                    ) {
                        // eslint-disable-next-line no-continue
                        continue;
                    }

                    // If the target is on a draggable (from target field), we must make sure that
                    // this draggable is not the same as "this" one.
                    if ((target.type === 'on_drag') && (target.draggableObj.uniqueId === this.uniqueId)) {
                        // eslint-disable-next-line no-continue
                        continue;
                    }

                    // Check if the draggable's center coordinate is within
                    // the target's dimensions. If not, go to next target.
                    if (
                        (positionIE.top + this.iconHeight * 0.5 < target.offset.top)
                || (positionIE.top + this.iconHeight * 0.5 > target.offset.top + target.h)
                || (positionIE.left + this.iconWidth * 0.5 < target.offset.left)
                || (positionIE.left + this.iconWidth * 0.5 > target.offset.left + target.w)
                    ) {
                        // eslint-disable-next-line no-continue
                        continue;
                    }

                    // If the draggable was moved from one target to
                    // another, then we need to remove it from the
                    // previous target's draggables list, and add it to the
                    // new target's draggables list.
                    if ((this.onTarget !== null) && (this.onTarget.uniqueId !== target.uniqueId)) {
                        this.onTarget.removeDraggable(this);
                        target.addDraggable(this);
                    // eslint-disable-next-line brace-style
                    }
                    // If the draggable was moved from the slider to a
                    // target, remember the target, and add ID to the
                    // target's draggables list.
                    else if (this.onTarget === null) {
                        target.addDraggable(this);
                    }

                    // Reposition the draggable so that it's center
                    // coincides with the center of the target.
                    this.snapToTarget(target);

                    // Target was found.
                    return true;
                }

                // Target was not found.
                return false;
            },

            toggleTargets: function(isEnabled) {
                var effect = isEnabled ? 'move' : null;

                this.state.baseImageEl.attr('aria-dropeffect', effect);
                $.each(this.state.targets, function(index, target) {
                    target.targetEl.attr('aria-dropeffect', effect);
                });
            },

            snapToTarget: function(target) {
                var offset;

                offset = 0;
                if (this.state.config.targetOutline === true) {
                    offset = 1;
                }

                this.iconEl.css({
                    left: target.offset.left + 0.5 * target.w - this.iconWidth * 0.5 + offset - this.iconElLeftOffset,
                    top: target.offset.top + 0.5 * target.h - this.iconHeight * 0.5 + offset
                });

                if (this.labelEl !== null) {
                    this.labelEl.css({
                        left: target.offset.left + 0.5 * target.w - this.labelWidth * 0.5 + offset - 9, // Acoount for padding, border.
                        top: target.offset.top + 0.5 * target.h + this.iconHeight * 0.5 + 5 + offset
                    });
                }
            },

            // Go through all of the draggables subtract 1 from the z-index
            // of all whose z-index is higher than the old z-index of the
            // current element. After, set the z-index of the current
            // element to 1 + N (where N is the number of draggables - i.e.
            // the highest z-index possible).
            //
            // This will make sure that after releasing a draggable, it
            // will be on top of all of the other draggables. Also, the
            // ordering of the visibility (z-index) of the other draggables
            // will not change.
            correctZIndexes: function() {
                var c1, highestZIndex;

                highestZIndex = -10000;

                if (this.state.config.individualTargets === true) {
                    if (this.onTarget.draggableList.length > 0) {
                        for (c1 = 0; c1 < this.onTarget.draggableList.length; c1 += 1) {
                            if (
                                (this.onTarget.draggableList[c1].zIndex > highestZIndex)
                        && (this.onTarget.draggableList[c1].zIndex !== 1000)
                            ) {
                                highestZIndex = this.onTarget.draggableList[c1].zIndex;
                            }
                        }
                    } else {
                        highestZIndex = 0;
                    }
                } else {
                    for (c1 = 0; c1 < this.state.draggables.length; c1++) {
                        if (this.inContainer === false) {
                            if (
                                (this.state.draggables[c1].zIndex > highestZIndex)
                        && (this.state.draggables[c1].zIndex !== 1000)
                            ) {
                                highestZIndex = this.state.draggables[c1].zIndex;
                            }
                        }
                    }
                }

                if (highestZIndex === -10000) {
                    highestZIndex = 0;
                }

                this.zIndex = highestZIndex + 1;

                this.iconEl.css('z-index', this.zIndex);
                if (this.labelEl !== null) {
                    this.labelEl.css('z-index', this.zIndex);
                }
            },

            // If a draggable was released in a wrong positione, we will
            // move it back to the slider, placing it in the same position
            // that it was dragged out of.
            moveBackToSlider: function() {
                var c1;

                Targets.destroyTargetField(this);

                if (this.isOriginal === false) {
                    this.iconEl.remove();
                    if (this.labelEl !== null) {
                        this.labelEl.remove();
                    }

                    this.state.draggables.splice(this.stateDraggablesIndex, 1);

                    for (c1 = 0; c1 < this.state.draggables.length; c1 += 1) {
                        if (this.state.draggables[c1].stateDraggablesIndex > this.stateDraggablesIndex) {
                            this.state.draggables[c1].stateDraggablesIndex -= 1;
                        }
                    }

                    return;
                }

                this.containerEl.show();
                this.zIndex = 1;

                this.iconEl.detach();
                if (this.iconImgEl !== null) {
                    this.iconImgEl.css({
                        width: this.iconWidthSmall,
                        height: this.iconHeightSmall
                    });
                }
                this.iconEl.css({
                    border: 'none',
                    'background-color': 'transparent',
                    'padding-left': 0,
                    'padding-right': 0,
                    'z-index': this.zIndex,
                    width: this.iconWidthSmall,
                    height: this.iconHeightSmall,
                    left: 50 - this.iconWidthSmall * 0.5,

                    // Before:
                    // 'top': ((this.labelEl !== null) ? (100 - this.iconHeightSmall - 25) * 0.5 : 50 - this.iconHeightSmall * 0.5)
                    // After:
                    top: ((this.labelEl !== null) ? 37.5 : 50.0) - 0.5 * this.iconHeightSmall
                });
                this.iconEl.appendTo(this.containerEl);

                if (this.labelEl !== null) {
                    this.labelEl.detach();
                    this.labelEl.css({
                        border: 'none',
                        'background-color': 'transparent',
                        'padding-left': 0,
                        'padding-right': 0,
                        'z-index': this.zIndex,
                        left: 50 - this.labelWidth * 0.5,

                        // Before:
                        // 'top': (100 - this.iconHeightSmall - 25) * 0.5 + this.iconHeightSmall + 5
                        // After:
                        top: 42.5 + 0.5 * this.iconHeightSmall
                    });
                    this.labelEl.appendTo(this.containerEl);
                }

                this.inContainer = true;
            }
        }; // End-of: return {
    }); // End-of: define(['update_input', 'targets'], function (updateInput, Targets) {
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) {
