(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)
            ) {
                        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)) {
                        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)
            ) {
                        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);
                    }
            // 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) {
