<commit_before>/*
 * Copyright 2016 <NAME>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import _ from "lodash";
import {events} from "../../constants";
import {AlphabetSymbol} from "../../entities/AlphabetSymbol";

/**
 * The controller that handles the page for managing all actions of a symbol. The symbol whose actions should be
 * manages has to be defined in the url by its id.
 */
class SymbolsActionsView {

    /**
     * Constructor.
     *
     * @param $scope
     * @param $stateParams
     * @param {SymbolResource} SymbolResource
     * @param {SessionService} SessionService
     * @param {ToastService} ToastService
     * @param {ErrorService} ErrorService
     * @param {ActionService} ActionService
     * @param {ClipboardService} ClipboardService
     * @param $state
     * @param {PromptService} PromptService
     * @param {EventBus} EventBus
     * @param dragulaService
     */
    // @ngInject
    constructor($scope, $stateParams, SymbolResource, SessionService, ToastService, ErrorService,
                ActionService, ClipboardService, $state, PromptService, EventBus, dragulaService) {
        this.SymbolResource = SymbolResource;
        this.ToastService = ToastService;
        this.ActionService = ActionService;
        this.ClipboardService = ClipboardService;

        /**
         * The project that is stored in the session.
         * @type {Project}
         */
        this.project = SessionService.getProject();

        /**
         * The symbol whose actions are managed.
         * @type {AlphabetSymbol|null}
         */
        this.symbol = null;

        /**
         * The list of selected actions.
         * @type {Object[]}
         */
        this.selectedActions = [];

        /**
         * Whether there are unsaved changes to the symbol.
         * @type {boolean}
         */
        this.hasChanged = false;

        // load all actions from the symbol
        // redirect to an error page when the symbol from the url id cannot be found
        this.SymbolResource.get(this.project.id, $stateParams.symbolId)
            .then(symbol => {

                // create unique ids for actions so that they can be found
                symbol.actions.forEach(action => {
                    action._id = _.uniqueId();
                });

                // add symbol to scope and create a copy in order to revert changes
                this.symbol = symbol;
            })
            .catch(() => {
                ErrorService.setErrorMessage('The symbol with the ID "' + $stateParams.symbolId + "' could not be found");
            });

        // show a confirm dialog if the user leaves the page without having saved changes and
        // redirect to the state that the user was about to go to if he doesn't want to save changes
        const offHandler = $scope.$on('$stateChangeStart', (event, toState) => {
            if (this.hasChanged) {
                event.preventDefault();
                PromptService.confirm('There are unsaved changes. Do you still want to continue and discard them?')
                    .then(() => {
                        offHandler();
                        $state.go(toState);
                    });
            }
        });

        // listen on action created event
        EventBus.on(events.ACTION_CREATED, (evt, data) => {
            this.addAction(data.action);
        }, $scope);

        // listen on action updated event
        EventBus.on(events.ACTION_UPDATED, (evt, data) => {
            this.updateAction(data.action);
        }, $scope);

        // dragula
        dragulaService.options($scope, 'actionList', {
            removeOnSpill: false,
            mirrorContainer: document.createElement('div')
        });

        $scope.$on('actionList.drop-model', () => {
            this.hasChanged = true;
        });

        $scope.$on('$destroy', () => dragulaService.destroy($scope, 'actionList'));
    }

    /**
     * Deletes a list of actions.
     *
     * @param {Object[]} actions - The actions to be deleted.
     */
    deleteActions(actions) {
        if (actions.length > 0) {
            actions.forEach(action => {
                _.remove(this.symbol.actions, {_id: action._id});
            });
            this.ToastService.success('Action' + (actions.length > 1 ? 's' : '') + ' deleted');
            this.hasChanged = true;
        }
    }

    /**
     * Adds a new action to the list of actions of the symbol and gives it a temporary unique id.
     *
     * @param {Object} action - The action to add.
     */
    addAction(action) {
        action._id = _.uniqueId();
        this.symbol.actions.push(action);
        this.ToastService.success('Action created');
        this.hasChanged = true;
    }

    /**
     * Updates an existing action.
     *
     * @param {Object} updatedAction - The updated action.
     */
    updateAction(updatedAction) {
        const action = this.symbol.actions.find(a => a._id === updatedAction._id);
        for (let prop in action) {
            action[prop] = updatedAction[prop];
        }
        this.ToastService.success('Action updated');
        this.hasChanged = true;
    }

    /**
     * Saves the changes that were made to the symbol by updating it on the server.
     */
    saveChanges() {

        // make a copy of the symbol
        const symbolToUpdate = new AlphabetSymbol(this.symbol);

        // update the symbol
        this.SymbolResource.update(symbolToUpdate)
            .then(updatedSymbol => {
                this.symbol.revision = updatedSymbol.revision;
                this.ToastService.success('Symbol <strong>' + updatedSymbol.name + '</strong> updated');
                this.hasChanged = false;
            })
            .catch(response => {
                this.ToastService.danger('<p><strong>Error updating symbol</strong></p>' + response.data.message);
            });
    }

    /**
     * Copies actions to the clipboard.
     *<commit_msg>added strg+s command to the actions view<commit_after>/*
 * Copyright 2016 <NAME>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import _ from "lodash";
import {events} from "../../constants";
import {AlphabetSymbol} from "../../entities/AlphabetSymbol";

/**
 * The controller that handles the page for managing all actions of a symbol. The symbol whose actions should be
 * manages has to be defined in the url by its id.
 */
class SymbolsActionsView {

    /**
     * Constructor.
     *
     * @param $scope
     * @param $stateParams
     * @param {SymbolResource} SymbolResource
     * @param {SessionService} SessionService
     * @param {ToastService} ToastService
     * @param {ErrorService} ErrorService
     * @param {ActionService} ActionService
     * @param {ClipboardService} ClipboardService
     * @param $state
     * @param {PromptService} PromptService
     * @param {EventBus} EventBus
     * @param dragulaService
     */
    // @ngInject
    constructor($scope, $stateParams, SymbolResource, SessionService, ToastService, ErrorService,
                ActionService, ClipboardService, $state, PromptService, EventBus, dragulaService) {
        this.SymbolResource = SymbolResource;
        this.ToastService = ToastService;
        this.ActionService = ActionService;
        this.ClipboardService = ClipboardService;

        /**
         * The project that is stored in the session.
         * @type {Project}
         */
        this.project = SessionService.getProject();

        /**
         * The symbol whose actions are managed.
         * @type {AlphabetSymbol|null}
         */
        this.symbol = null;

        /**
         * The list of selected actions.
         * @type {Object[]}
         */
        this.selectedActions = [];

        /**
         * Whether there are unsaved changes to the symbol.
         * @type {boolean}
         */
        this.hasChanged = false;

        // load all actions from the symbol
        // redirect to an error page when the symbol from the url id cannot be found
        this.SymbolResource.get(this.project.id, $stateParams.symbolId)
            .then(symbol => {

                // create unique ids for actions so that they can be found
                symbol.actions.forEach(action => {
                    action._id = _.uniqueId();
                });

                // add symbol to scope and create a copy in order to revert changes
                this.symbol = symbol;
            })
            .catch(() => {
                ErrorService.setErrorMessage('The symbol with the ID "' + $stateParams.symbolId + "' could not be found");
            });

        // show a confirm dialog if the user leaves the page without having saved changes and
        // redirect to the state that the user was about to go to if he doesn't want to save changes
        const offHandler = $scope.$on('$stateChangeStart', (event, toState) => {
            if (this.hasChanged) {
                event.preventDefault();
                PromptService.confirm('There are unsaved changes. Do you still want to continue and discard them?')
                    .then(() => {
                        offHandler();
                        $state.go(toState);
                    });
            }
        });

        const keyDownHandler = (e) => {
            if (e.ctrlKey && e.which === 83) {
                e.preventDefault();
                this.saveChanges();
                return false;
            }
        };

        document.addEventListener('keydown', keyDownHandler);

        // listen on action created event
        EventBus.on(events.ACTION_CREATED, (evt, data) => {
            this.addAction(data.action);
        }, $scope);

        // listen on action updated event
        EventBus.on(events.ACTION_UPDATED, (evt, data) => {
            this.updateAction(data.action);
        }, $scope);

        // dragula
        dragulaService.options($scope, 'actionList', {
            removeOnSpill: false,
            mirrorContainer: document.createElement('div')
        });

        $scope.$on('actionList.drop-model', () => {
            this.hasChanged = true;
        });

        $scope.$on('$destroy', () => {
            dragulaService.destroy($scope, 'actionList');
            document.removeEventListener('keydown', keyDownHandler);
        });
    }

    /**
     * Deletes a list of actions.
     *
     * @param {Object[]} actions - The actions to be deleted.
     */
    deleteActions(actions) {
        if (actions.length > 0) {
            actions.forEach(action => {
                _.remove(this.symbol.actions, {_id: action._id});
            });
            this.ToastService.success('Action' + (actions.length > 1 ? 's' : '') + ' deleted');
            this.hasChanged = true;
        }
    }

    /**
     * Adds a new action to the list of actions of the symbol and gives it a temporary unique id.
     *
     * @param {Object} action - The action to add.
     */
    addAction(action) {
        action._id = _.uniqueId();
        this.symbol.actions.push(action);
        this.ToastService.success('Action created');
        this.hasChanged = true;
    }

    /**
     * Updates an existing action.
     *
     * @param {Object} updatedAction - The updated action.
     */
    updateAction(updatedAction) {
        const action = this.symbol.actions.find(a => a._id === updatedAction._id);
        for (let prop in action) {
            action[prop] = updatedAction[prop];
        }
        this.ToastService.success('Action updated');
        this.hasChanged = true;
    }

    /**
     * Saves the changes that were made to the symbol by updating it on the server.
     */
    saveChanges() {
        if (!this.hasChanged) {
            this.ToastService.info('There are no changes to save.');
            return;
        }

        // make a copy of the symbol
        const symbolToUpdate = new AlphabetSymbol(this.symbol);

        // update the symbol
        this.SymbolResource.update(symbolToUpdate)
            .then(updatedSymbol => {
                this.symbol.revision = updatedSymbol.revision;
                this.ToastService.success('Symbol <strong>' + updatedSymbol.name + '</strong> updated');
                this.hasChanged = false;
            })
            .catch(response => {
                this.ToastService.danger('<p><strong>Error updating symbol</strong></p>' + response.data.message);
            });
    }

    /**
     * Copies actions to the clipboard.
     *
{'fraction_non_alphanumeric': 0.10949282862634925, 'fraction_numerical': 0.0016264971166642024, 'mean_word_length': 1.937459283387622, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 48, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1115831', 'n_tokens_mistral': 3538, 'n_tokens_neox': 3436, 'n_words': 1062}