// 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 FauxtonAPI from '../../../core/api';
import Documents from '../resources';
import ActionTypes from './actiontypes';
import SidebarActions from '../sidebar/actions';

const selectReduceChanged = (reduceOption) => (dispatch) => {
  dispatch({
    type: ActionTypes.SELECT_REDUCE_CHANGE,
    reduceSelectedOption: reduceOption
  });
};

const changeViewName = (name) => (dispatch) => {
  dispatch({
    type: ActionTypes.VIEW_NAME_CHANGE,
    name: name
  });
};

const editIndex = (options) => (dispatch) => {
  dispatch({
    type: ActionTypes.EDIT_INDEX,
    options: options
  });
};

const dispatchClearIndex = () => {
  FauxtonAPI.reduxDispatch({ type: ActionTypes.CLEAR_INDEX });
};

const dispatchFetchDesignDocsBeforeEdit = (options) => {
  options.designDocs.fetch({reset: true}).then(() => {
    editIndex(options)(FauxtonAPI.reduxDispatch);
  }, xhr => {
    let errorMsg = 'Error';
    if (xhr.responseJSON && xhr.responseJSON.error === 'not_found') {
      const databaseName = options.designDocs.database.safeID();
      errorMsg = `The ${databaseName} database does not exist`;
      FauxtonAPI.navigate('/', {trigger: true});
    }
    FauxtonAPI.addNotification({
      msg: errorMsg,
      type: "error",
      clear:  true
    });
  });
};

const shouldRemoveDdocView = (viewInfo) => {
  return !viewInfo.isNewView &&
          viewInfo.originalDesignDocName === viewInfo.designDocId &&
          viewInfo.originalViewName !== viewInfo.viewName;
};

const saveView = (viewInfo, navigateToURL) => (dispatch) => {
  const designDoc = viewInfo.designDoc;
  designDoc.setDdocView(viewInfo.viewName, viewInfo.map, viewInfo.reduce);

  FauxtonAPI.addNotification({
    msg: 'Saving View...',
    type: 'info',
    clear: true
  });

  // if the view name just changed and it's in the SAME design doc, remove the old one before saving the doc
  if (shouldRemoveDdocView(viewInfo)) {
    designDoc.removeDdocView(viewInfo.originalViewName);
  }

  designDoc.save().then(function () {
    FauxtonAPI.addNotification({
      msg: 'View Saved.',
      type: 'success',
      clear: true
    });

    // if the user just saved an existing view to a different design doc, remove the view
    // from the old design doc and delete if it's empty
    if (!viewInfo.isNewView && viewInfo.originalDesignDocName !== viewInfo.designDocId) {
      const oldDesignDoc = findDesignDoc(viewInfo.designDocs, viewInfo.originalDesignDocName);
      safeDeleteIndex(oldDesignDoc, viewInfo.designDocs, 'views', viewInfo.originalViewName, {
        onSuccess: function () {
          SidebarActions.dispatchUpdateDesignDocs(viewInfo.designDocs);
        }
      });
    }

    if (viewInfo.designDocId === 'new-doc') {
      addDesignDoc(designDoc);
    }
    SidebarActions.dispatchUpdateDesignDocs(viewInfo.designDocs);
    dispatch({ type: ActionTypes.VIEW_SAVED });
    FauxtonAPI.navigate(navigateToURL, { trigger: true });
  }, (xhr) => {
    FauxtonAPI.addNotification({
      msg: 'Save failed. ' + (xhr.responseJSON ? `Reason: ${xhr.responseJSON.reason}` : ''),
      type: 'error',
      clear: true
    });
  });
};

const addDesignDoc = (designDoc) => (dispatch) => {
  dispatch({
    type: ActionTypes.VIEW_ADD_DESIGN_DOC,
    designDoc: designDoc.toJSON()
  });
};

const deleteView = (options) => {

  function onSuccess () {

    // if the user was on the index that was just deleted, redirect them back to all docs
    if (options.isOnIndex) {
      const url = FauxtonAPI.urls('allDocs', 'app', options.database.safeID());
      FauxtonAPI.navigate(url);
    }

    SidebarActions.dispatchUpdateDesignDocs(options.designDocs);

    FauxtonAPI.addNotification({
      msg: 'The "' + options.indexName + '" view has been deleted.',
      type: 'info',
      escape: false,
      clear: true
    });
    SidebarActions.dispatchHideDeleteIndexModal();
  }

  return safeDeleteIndex(options.designDoc, options.designDocs, 'views', options.indexName, { onSuccess: onSuccess });
};

const cloneView = (params) => {
  const targetDesignDoc = getDesignDoc(params.designDocs, params.targetDesignDocName, params.newDesignDocName,
    params.newDesignDocPartitioned, params.database, params.isDbPartitioned);
  let indexes = targetDesignDoc.get('views');
  if (indexes && _.has(indexes, params.newIndexName)) {
    FauxtonAPI.addNotification({
      msg: 'That index name is already used in this design doc. Please enter a new name.',
      type: 'error',
      clear: true
    });
    return;
  }
  if (!indexes) {
    indexes = {};
  }
  const sourceDesignDoc = findDesignDoc(params.designDocs, '_design/' + params.sourceDesignDocName);
  const sourceDesignDocJSON = sourceDesignDoc.toJSON();

  // this sets whatever content is in the source index into the target design doc under the new index name
  indexes[params.newIndexName] = sourceDesignDocJSON.views[params.sourceIndexName];
  targetDesignDoc.set({ views: indexes });

  targetDesignDoc.save().then(() => {
    params.onComplete();
    FauxtonAPI.addNotification({
      msg: 'The index has been cloned.',
      type: 'success',
      clear: true
    });
    SidebarActions.dispatchUpdateDesignDocs(params.designDocs);
  }, (xhr) => {
    params.onComplete();
    const responseText = JSON.parse(xhr.responseText).reason;
    FauxtonAPI.addNotification({
      msg: 'Clone failed: ' + responseText,
      type: 'error',
      clear: true
    });
  });
};

const gotoEditViewPage = (databaseName, partitionKey, designDocName, indexName) => {
  FauxtonAPI.navigate('#' + FauxtonAPI.urls('view', 'edit', encodeURIComponent(databaseName),
    (partitionKey ? encodeURIComponent(partitionKey) : ''),
    encodeURIComponent(designDocName), encodeURIComponent(indexName)));
};

const updateMapCode = (code) => (dispatch) => {
  dispatch({
    type: ActionTypes.VIEW_UPDATE_MAP_CODE,
    code: code
  });
};

const updateReduceCode = (code) => (dispatch) => {
  dispatch({
    type: ActionTypes.VIEW_UPDATE_REDUCE_CODE,
    code: code
  });
};

const selectDesignDoc = (designDoc) => (dispatch) => {
  dispatch({
    type: ActionTypes.DESIGN_DOC_CHANGE,
    options: {
      value: designDoc
    }
  });
};

const updateNewDesignDocName = (designDocName) => (dispatch) => {
  dispatch({
    type: ActionTypes.DESIGN_DOC_NEW_NAME_UPDATED,
    options: {
      value: designDocName
    }
  });
};

const updateNewDesignDocPartitioned = (isPartitioned) => (dispatch) => {
  dispatch({
    type: ActionTypes.DESIGN_DOC_NEW_PARTITIONED_UPDATED,
    options: {
      value: isPartitioned
    }
  });
};

// safely deletes an index of any type. It only deletes the actual design doc if there are no
// other indexes of any type left in the doc
const safeDeleteIndex = (designDoc, designDocs, indexPropName, indexName, options) => {
  const opts = _.extend({
    onSuccess: function () { },
    onError: function (xhr) {
      const responseText = JSON.parse(xhr.responseText).reason;
      FauxtonAPI.addNotification({
        msg: 'Delete failed: ' + responseText,
        type: 'error',
        clear: true
      });
    }
  }, options);

  const indexes = designDoc.get(indexPropName) || {};
  delete indexes[indexName];
  const newIndexes = {};
  newIndexes[indexPropName] = indexes;
  designDoc.set(newIndexes);

  // we either save the design doc with the now-removed index, or we remove it altogether if there are no indexes
  // of any type left in the design doc
  const indexTypePropNames = FauxtonAPI.getIndexTypePropNames();
  const hasRemainingIndex = _.some(indexTypePropNames, function (propName) {
    return designDoc.get(propName) && _.keys(designDoc.get(propName)).length > 0;
  });

  let promise;
  let deleteDesignDoc = false;
  if (hasRemainingIndex) {
    promise = designDoc.save();
  } else {
    promise = designDoc.destroy();
    deleteDesignDoc = true;
  }
  return promise.then(function () {
    if (deleteDesignDoc) {
      designDocs.remove(designDoc.id);
    }
    opts.onSuccess();
  }, opts.onError);
};



// ---- helpers ----

const findDesignDoc = (designDocs, designDocName) => {
  return designDocs.find(function (doc) {
    return doc.id === designDocName;
  }).dDocModel();
};

const getDesignDoc = (designDocs, targetDesignDocName, newDesignDocName, newDesignDocPartitioned, database, isDbPartitioned) => {
  if (targetDesignDocName === 'new-doc') {
    const doc = {
      "_id": "_design/" + newDesignDocName,
      "views": {},
      "language": "javascript"
    };
    const dDoc = new Documents.Doc(doc, { database: database });
    if (isDbPartitioned) {
      dDoc.setDDocPartitionedOption(newDesignDocPartitioned);
    }
    return dDoc;
  }

  const foundDoc = designDocs.find(function (ddoc) {
    return ddoc.id === targetDesignDocName;
  });
  return (!foundDoc) ? null : foundDoc.dDocModel();
};


export default {
  helpers: {
    findDesignDoc,
    getDesignDoc
  },
  safeDeleteIndex,
  selectReduceChanged,
  changeViewName,
  editIndex,
  dispatchClearIndex,
  dispatchFetchDesignDocsBeforeEdit,
  shouldRemoveDdocView,
  saveView,
  addDesignDoc,
  deleteView,
  cloneView,
  gotoEditViewPage,
  updateMapCode,
  updateReduceCode,
  selectDesignDoc,
  updateNewDesignDocName,
  updateNewDesignDocPartitioned
};
