(function (requirejs, require, define) {
define([], function () {
    return {
        'check': check,
        'update': update
    };

    function update(state) {
        var draggables, tempObj;

        draggables = [];

        if (state.config.individualTargets === false) {
            (function (c1) {
                while (c1 < state.draggables.length) {
                    if (state.draggables[c1].x !== -1) {
                        tempObj = {};
                        tempObj[state.draggables[c1].id] = [
                            state.draggables[c1].x,
                            state.draggables[c1].y
                        ];
                        draggables.push(tempObj);
                        tempObj = null;
                    }

                    c1 += 1;
                }
            }(0));
        } else {
            (function (c1) {
                while (c1 < state.targets.length) {
                    (function (c2) {
                        while (c2 < state.targets[c1].draggableList.length) {
                            tempObj = {};

                            if (state.targets[c1].type === 'base') {
                                tempObj[state.targets[c1].draggableList[c2].id] = state.targets[c1].id;
                            } else {
                                addTargetRecursively(tempObj, state.targets[c1].draggableList[c2], state.targets[c1]);
                            }
                            draggables.push(tempObj);
                            tempObj = null;

                            c2 += 1;
                        }
                    }(0));

                    c1 += 1;
                }
            }(0));
        }

        $('#input_' + state.problemId).val(JSON.stringify(draggables));
    }

    function addTargetRecursively(tempObj, draggable, target) {
        if (target.type === 'base') {
            tempObj[draggable.id] = target.id;
        } else {
            tempObj[draggable.id] = {};
            tempObj[draggable.id][target.id] = {};

            addTargetRecursively(tempObj[draggable.id][target.id], target.draggableObj, target.draggableObj.onTarget);
        }
    }

    // Check if input has an answer from server. If yes, then position
    // all draggables according to answer.
    function check(state) {
        var inputElVal;

        inputElVal = $('#input_' + state.problemId).val();

        if (inputElVal.length === 0) {
            return false;
        }

        repositionDraggables(state, JSON.parse(inputElVal));

        return true;
    }

    function processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, depth, i) {
        var baseDraggableId, baseDraggable, baseTargetId, baseTarget,
            layeredDraggableId, layeredDraggable, layeredTargetId, layeredTarget,
            chain;

        if (depth === 0) {
            // We are at the lowest depth? The end.

            return;
        }

        if (answerSortedByDepth.hasOwnProperty(depth) === false) {
            // We have a depth that ts not valid, we decrease the depth by one.
            processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, depth - 1, 0);

            return;
        }

        if (answerSortedByDepth[depth].length <= i) {
            // We ran out of answers at this depth, go to the next depth down.
            processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, depth - 1, 0);

            return;
        }

        chain = answerSortedByDepth[depth][i];

        baseDraggableId = Object.keys(chain)[0];

        // This is a hack. For now we will work with depths 1 and 3.
        if (depth === 1) {
            baseTargetId = chain[baseDraggableId];

            layeredTargetId = null;
            layeredDraggableId = null;

            // createBaseDraggableOnTarget(state, baseDraggableId, baseTargetId);
        } else if (depth === 3) {
            layeredDraggableId = baseDraggableId;

            layeredTargetId = Object.keys(chain[layeredDraggableId])[0];

            baseDraggableId = Object.keys(chain[layeredDraggableId][layeredTargetId])[0];

            baseTargetId = chain[layeredDraggableId][layeredTargetId][baseDraggableId];
        }

        checkBaseDraggable();

        return;

        function checkBaseDraggable() {
            if ((baseDraggable = getById(state, 'draggables', baseDraggableId, null, false, baseTargetId)) === null) {
                createBaseDraggableOnTarget(state, baseDraggableId, baseTargetId, true, function () {
                    if ((baseDraggable = getById(state, 'draggables', baseDraggableId, null, false, baseTargetId)) === null) {
                        console.log('ERROR: Could not successfully create a base draggable on a base target.');
                    } else {
                        baseTarget = baseDraggable.onTarget;

                        if ((layeredTargetId === null) || (layeredDraggableId === null)) {
                            processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, depth, i + 1);
                        } else {
                            checklayeredDraggable();
                        }
                    }
                });
            } else {
                baseTarget = baseDraggable.onTarget;

                if ((layeredTargetId === null) || (layeredDraggableId === null)) {
                    processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, depth, i + 1);
                } else {
                    checklayeredDraggable();
                }
            }
        }

        function checklayeredDraggable() {
            if ((layeredDraggable = getById(state, 'draggables', layeredDraggableId, null, false, layeredTargetId, baseDraggableId, baseTargetId)) === null) {
                layeredDraggable = getById(state, 'draggables', layeredDraggableId);
                layeredTarget = null;
                baseDraggable.targetField.every(function (target) {
                    if (target.id === layeredTargetId) {
                        layeredTarget = target;
                    }

                    return true;
                });

                if ((layeredDraggable !== null) && (layeredTarget !== null)) {
                    layeredDraggable.moveDraggableTo('target', layeredTarget, function () {
                        processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, depth, i + 1);
                    });
                } else {
                    processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, depth, i + 1);
                }
            } else {
                processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, depth, i + 1);
            }
        }
    }

    function createBaseDraggableOnTarget(state, draggableId, targetId, reportError, funcCallback) {
        var draggable, target;

        if ((draggable = getById(state, 'draggables', draggableId)) === null) {
            if (reportError !== false) {
                console.log(
                    'ERROR: In answer there exists a ' +
                    'draggable ID "' + draggableId + '". No ' +
                    'draggable with this ID could be found.'
                );
            }

            return false;
        }

        if ((target = getById(state, 'targets', targetId)) === null) {
            if (reportError !== false) {
                console.log(
                    'ERROR: In answer there exists a target ' +
                    'ID "' + targetId + '". No target with this ' +
                    'ID could be found.'
                );
            }

            return false;
        }

        draggable.moveDraggableTo('target', target, funcCallback);

        return true;
    }

    function processAnswerPositions(state, answer) {
        var draggableId, draggable;

        (function (c1) {
            while (c1 < answer.length) {
                for (draggableId in answer[c1]) {
                    if (answer[c1].hasOwnProperty(draggableId) === false) {
                        continue;
                    }

                    if ((draggable = getById(state, 'draggables', draggableId)) === null) {
                        console.log(
                            'ERROR: In answer there exists a ' +
                            'draggable ID "' + draggableId + '". No ' +
                            'draggable with this ID could be found.'
                        );

                        continue;
                    }

                    draggable.moveDraggableTo('XY', {
                        'x': answer[c1][draggableId][0],
                        'y': answer[c1][draggableId][1]
                    });
                }

                c1 += 1;
            }
        }(0));
    }

    function repositionDraggables(state, answer) {
        var answerSortedByDepth, minDepth, maxDepth;

        answerSortedByDepth = {};
        minDepth = 1000;
        maxDepth = 0;

        answer.every(function (chain) {
            var depth;

            depth = findDepth(chain, 0);

            if (depth < minDepth) {
                minDepth = depth;
            }
            if (depth > maxDepth) {
                maxDepth = depth;
            }

            if (answerSortedByDepth.hasOwnProperty(depth) === false) {
                answerSortedByDepth[depth] = [];
            }

            answerSortedByDepth[depth].push(chain);

            return true;
        });

        if (answer.length === 0) {
            return;
        }

        // For now we support only one case.
        if ((minDepth < 1) || (maxDepth > 3)) {
            return;
        }

        if (state.config.individualTargets === true) {
            processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, maxDepth, 0);
        } else if (state.config.individualTargets === false) {
            processAnswerPositions(state, answer);
        }
    }

    function findDepth(tempObj, depth) {
        var i;

        if ($.isPlainObject(tempObj) === false) {
            return depth;
        }

        depth += 1;

        for (i in tempObj) {
            if (tempObj.hasOwnProperty(i) === true) {
                depth = findDepth(tempObj[i], depth);
            }
        }

        return depth;
    }

    function getById(state, type, id, fromTargetField, inContainer, targetId, baseDraggableId, baseTargetId) {
        return (function (c1) {
            while (c1 < state[type].length) {
                if (type === 'draggables') {
                    if ((targetId !== undefined) && (inContainer === false) && (baseDraggableId !== undefined) && (baseTargetId !== undefined)) {
                        if (
                            (state[type][c1].id === id) &&
                            (state[type][c1].inContainer === false) &&
                            (state[type][c1].onTarget.id === targetId) &&
                            (state[type][c1].onTarget.type === 'on_drag') &&
                            (state[type][c1].onTarget.draggableObj.id === baseDraggableId) &&
                            (state[type][c1].onTarget.draggableObj.onTarget.id === baseTargetId)
                        ) {
                            return state[type][c1];
                        }
                    } else if ((targetId !== undefined) && (inContainer === false)) {
                        if (
                            (state[type][c1].id === id) &&
                            (state[type][c1].inContainer === false) &&
                            (state[type][c1].onTarget.id === targetId)
                        ) {
                            return state[type][c1];
                        }
                    } else {
                        if (inContainer === false) {
                            if ((state[type][c1].id === id) && (state[type][c1].inContainer === false)) {
                                return state[type][c1];
                            }
                        } else {
                            if ((state[type][c1].id === id) && (state[type][c1].inContainer === true)) {
                                return state[type][c1];
                            }
                        }
                    }
                } else { // 'targets'
                    if (fromTargetField === true) {
                        if ((state[type][c1].id === id) && (state[type][c1].type === 'on_drag')) {
                            return state[type][c1];
                        }
                    } else {
                        if ((state[type][c1].id === id) && (state[type][c1].type === 'base')) {
                            return state[type][c1];
                        }
                    }
                }

                c1 += 1;
            }

            return null;
        }(0));
    }
}); // End-of: define([], function () {
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) {