/*****************************************************************************
 * Open MCT, Copyright (c) 2014-2018, United States Government
 * as represented by the Administrator of the National Aeronautics and Space
 * Administration. All rights reserved.
 *
 * Open MCT is 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.
 *
 * Open MCT includes source code licensed under additional open source
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 * this source code distribution or the Licensing information page available
 * at runtime from the About dialog for additional information.
 *****************************************************************************/


define([
    '../creation/CreateWizard',
    './SaveInProgressDialog'
],
function (
    CreateWizard,
    SaveInProgressDialog
) {

    /**
         * The "Save" action; the action triggered by clicking Save from
         * Edit Mode. Exits the editing user interface and invokes object
         * capabilities to persist the changes that have been made.
         * @constructor
         * @implements {Action}
         * @memberof platform/commonUI/edit
         */
    function SaveAsAction(
        $injector,
        dialogService,
        copyService,
        notificationService,
        openmct,
        context
    ) {
        this.domainObject = (context || {}).domainObject;
        this.injectObjectService = function () {
            this.objectService = $injector.get("objectService");
        };
        this.dialogService = dialogService;
        this.copyService = copyService;
        this.notificationService = notificationService;
        this.openmct = openmct;
    }

    /**
         * @private
         */
    SaveAsAction.prototype.createWizard = function (parent) {
        return new CreateWizard(
            this.domainObject,
            parent,
            this.openmct
        );
    };

    /**
         * @private
         */
    SaveAsAction.prototype.getObjectService = function () {
        // Lazily acquire object service (avoids cyclical dependency)
        if (!this.objectService) {
            this.injectObjectService();
        }
        return this.objectService;
    };

    function resolveWith(object) {
        return function () {
            return object;
        };
    }

    /**
         * Save changes and conclude editing.
         *
         * @returns {Promise} a promise that will be fulfilled when
         *          cancellation has completed
         * @memberof platform/commonUI/edit.SaveAction#
         */
    SaveAsAction.prototype.perform = function () {
        return this.save();
    };

    /**
         * @private
         */
    SaveAsAction.prototype.save = function () {
        var self = this,
            domainObject = this.domainObject,
            copyService = this.copyService,
            dialog = new SaveInProgressDialog(this.dialogService),
            toUndirty = [];

        function doWizardSave(parent) {
            var wizard = self.createWizard(parent);

            return self.dialogService
                .getUserInput(wizard.getFormStructure(true),
                    wizard.getInitialFormValue())
                .then(wizard.populateObjectFromInput.bind(wizard), function (failureReason) {
                    return Promise.reject("user canceled");
                });
        }

        function showBlockingDialog(object) {
            dialog.show();
            return object;
        }

        function hideBlockingDialog(object) {
            dialog.hide();
            return object;
        }

        function fetchObject(objectId) {
            return self.getObjectService().getObjects([objectId]).then(function (objects) {
                return objects[objectId];
            });
        }

        function getParent(object) {
            return fetchObject(object.getModel().location);
        }

        function allowClone(objectToClone) {
            var allowed =
                    (objectToClone.getId() === domainObject.getId()) ||
                        objectToClone.getCapability('location').isOriginal();
            if (allowed) {
                toUndirty.push(objectToClone);
            }
            return allowed;
        }

        function cloneIntoParent(parent) {
            return copyService.perform(domainObject, parent, allowClone);
        }

        function undirty(object) {
            return object.getCapability('persistence').refresh();
        }

        function undirtyOriginals(object) {
            return Promise.all(toUndirty.map(undirty))
                .then(resolveWith(object));
        }

        function saveAfterClone(clonedObject) {
            return this.openmct.editor.save().then(() => {
                // Force mutation for search indexing
                return clonedObject;
            })
        }

        function finishEditing(clonedObject) {
            return fetchObject(clonedObject.getId())
        }

        function indexForSearch(savedObject) {
            savedObject.useCapability('mutation', (model) => {
                return model;
            });

            return savedObject;
        }

        function onSuccess(object) {
            self.notificationService.info("Save Succeeded");
            return object;
        }

        function onFailure(reason) {
            hideBlockingDialog();
            if (reason !== "user canceled") {
                self.notificationService.error("Save Failed");
            }
            throw reason;
        }

        return getParent(domainObject)
            .then(doWizardSave)
            .then(showBlockingDialog)
            .then(getParent)
            .then(cloneIntoParent)
            .then(undirtyOriginals)
            .then(saveAfterClone)
            .then(finishEditing)
            .then(indexForSearch)
            .then(hideBlockingDialog)
            .then(onSuccess)
            .catch(onFailure);
    };


    /**
         * Check if this action is applicable in a given context.
         * This will ensure that a domain object is present in the context,
         * and that this domain object is in Edit mode.
         * @returns true if applicable
         */
    SaveAsAction.appliesTo = function (context) {
        var domainObject = (context || {}).domainObject;
        return domainObject !== undefined &&
                domainObject.hasCapability('editor') &&
                domainObject.getCapability('editor').isEditContextRoot() &&
                domainObject.getModel().persisted === undefined;
    };

    return SaveAsAction;
}
);
