// Licensed to Cloudera, Inc. under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  Cloudera, Inc. licenses this file
// to you 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 'jquery';
import * as ko from 'knockout';

import apiHelper from '../api/apiHelper';
import huePubSub from '../utils/huePubSub';
import HueDocument from '../doc/hueDocument';
import { DOCUMENT_TYPE_I18n, DOCUMENT_TYPES } from '../doc/docSupport';
import { SHOW_DELETE_DOC_MODAL_EVENT } from '../ko/components/ko.deleteDocModal';

const SORTS = {
  defaultAsc: (a, b) => {
    if (a.isDirectory() && !b.isDirectory()) {
      return -1;
    }
    if (!a.isDirectory() && b.isDirectory()) {
      return 1;
    }
    if (a.isDirectory() && b.isDirectory()) {
      return SORTS.nameAsc(a, b);
    }
    return SORTS.lastModifiedDesc(a, b);
  },
  nameAsc: (a, b) => a.definition().name.localeCompare(b.definition().name),
  nameDesc: (a, b) => SORTS.nameAsc(b, a),
  descriptionAsc: (a, b) => a.definition().description.localeCompare(b.definition().description),
  descriptionDesc: (a, b) => SORTS.descriptionAsc(b, a),
  typeAsc: (a, b) => a.definition().type.localeCompare(b.definition().type),
  typeDesc: (a, b) => SORTS.typeAsc(b, a),
  ownerAsc: (a, b) => a.definition().owner.localeCompare(b.definition().owner),
  ownerDesc: (a, b) => SORTS.ownerAsc(b, a),
  lastModifiedAsc: (a, b) => a.definition().last_modified_ts - b.definition().last_modified_ts,
  lastModifiedDesc: (a, b) => SORTS.lastModifiedAsc(b, a)
};

class HueFileEntry {
  /**
   *
   * @param {Object} options
   * @param {Object} options.definition
   * @param {Function} options.activeEntry - The observable keeping track of the current open directory
   * @param {Function} options.trashEntry - The observable keeping track of the trash directory
   * @param {HueFolder} options.parent
   * @param {string} options.app - Currently only 'documents' is supported
   * @param {string} options.user
   *
   * @constructor
   */
  constructor(options) {
    this.activeEntry = options.activeEntry;
    this.trashEntry = options.trashEntry;
    this.parent = options.parent;
    this.definition = ko.observable(options.definition);
    this.app = options.app;
    this.user = options.user;
    this.superuser = options.superuser;
    this.serverTypeFilter = options.serverTypeFilter || ko.observable({ type: 'all' });
    this.statsVisible = ko.observable(false);
    this.highlight = ko.observable(false);

    this.document = ko.observable();
    this.selectedDocsWithDependents = ko.observable([]);
    this.importedDocSummary = ko.observable();
    this.showTable = ko.observable();
    this.entries = ko.observableArray([]);

    // Filter is only used in the assist panel at the moment
    this.isFilterVisible = ko.observable(false);
    this.filter = ko.observable('').extend({ rateLimit: 400 });

    this.typeFilter = options.typeFilter || ko.observable(DOCUMENT_TYPES[0]); // First one is always 'all'

    this.isFilterVisible.subscribe(newValue => {
      if (!newValue && this.filter()) {
        this.filter('');
      }
    });

    this.filteredEntries = ko.pureComputed(() => {
      const filter = this.filter().toLowerCase();
      const typeFilter = this.typeFilter().type;
      if (filter || typeFilter !== 'all') {
        return this.entries().filter(entry => {
          const entryType = entry.definition().type;
          return (
            (typeFilter === 'all' || entryType === typeFilter || entryType === 'directory') &&
            (!filter ||
              entry.definition().name.toLowerCase().indexOf(filter) !== -1 ||
              (DOCUMENT_TYPE_I18n[entryType] &&
                DOCUMENT_TYPE_I18n[entryType].toLowerCase().indexOf(filter) !== -1) ||
              entry.definition().description.toLowerCase().indexOf(filter) !== -1)
          );
        });
      }
      return this.entries();
    });

    this.getSelectedDocsWithDependents = () => {
      this.selectedDocsWithDependents([]);
      const uuids = this.selectedEntries()
        .map(entry => entry.definition().uuid)
        .join(',');

      const data = {
        uuids: uuids,
        data: 'false',
        dependencies: 'true'
      };

      $.get('/desktop/api2/doc/', data, response => {
        const docsWithDependents = [];
        if (response && response.data_list) {
          for (let index = 0; index < response.data_list.length; index++) {
            docsWithDependents.push({
              name: response.data_list[index].document.name,
              dependents: response.data_list[index].dependents
            });
          }
        }
        this.selectedDocsWithDependents(docsWithDependents);
      }).fail(response => {
        $(document).trigger('error', 'Error getting document data: ' + response.responseText);
      });
    };

    this.isTrash = ko.pureComputed(() => this.definition().name === '.Trash');

    this.isTrashed = ko.pureComputed(() => {
      if (typeof this.parent !== 'undefined' && this.parent !== null) {
        return this.parent.isTrash() || this.parent.isTrashed();
      }
      return false;
    });

    this.isRoot = ko.pureComputed(() => this.definition().name === '');

    this.isDirectory = ko.pureComputed(() => this.definition().type === 'directory');

    this.isShared = ko.pureComputed(() => {
      const perms = this.definition().perms;
      return (
        perms &&
        (perms.read.users.length > 0 ||
          perms.read.groups.length > 0 ||
          perms.write.users.length > 0 ||
          perms.write.groups.length > 0 ||
          perms.link_sharing_on)
      );
    });

    this.isSharedWithMe = ko.pureComputed(() => this.user !== this.definition().owner);

    this.canModify = ko.pureComputed(() => {
      const perms = this.definition().perms;
      return (
        !this.isSharedWithMe() ||
        this.superuser ||
        (perms &&
          (perms.write.users.some(user => user.username === this.user) ||
            perms.write.groups.some(
              writeGroup => LOGGED_USERGROUPS.indexOf(writeGroup.name) !== -1
            ) ||
            (perms.link_sharing_on && perms.link_write)))
      );
    });

    this.canReshare = ko.pureComputed(() => {
      if (!this.isSharedWithMe()) {
        return true;
      }
      let target = this;
      while (target.parent && !target.isShared()) {
        target = target.parent;
      }
      return target.canModify();
    });

    this.entriesToDelete = ko.observableArray();
    this.deletingEntries = ko.observable(false);

    this.selected = ko.observable(false);

    this.loaded = ko.observable(false);
    this.loading = ko.observable(false);
    this.hasErrors = ko.observable(false);

    this.uploading = ko.observable(false);
    this.uploadComplete = ko.observable(false);
    this.uploadFailed = ko.observable(false);
    this.selectedImportFile = ko.observable('');

    this.importEnabled = ko.pureComputed(() => this.selectedImportFile() !== '');

    this.activeSort = options.activeSort;

    this.selectedEntries = ko.pureComputed(() => this.entries().filter(entry => entry.selected()));

    this.sharedWithMeSelected = ko.pureComputed(() =>
      this.selectedEntries().some(entry => entry.isSharedWithMe())
    );

    this.directorySelected = ko.pureComputed(() =>
      this.selectedEntries().some(entry => entry.isDirectory())
    );

    this.selectedEntry = ko.pureComputed(() => {
      if (this.selectedEntries().length === 1) {
        return this.selectedEntries()[0];
      }
      return null;
    });

    this.breadcrumbs = ko.pureComputed(() => {
      const result = [];
      let lastParent = this.parent;
      while (lastParent) {
        result.unshift(lastParent);
        lastParent = lastParent.parent;
      }
      return result;
    });
  }

  getDirectory() {
    if (!this.definition() || this.isRoot()) {
      return null;
    } else {
      return this.definition().uuid;
    }
  }

  highlightInside(uuid) {
    const self = this;
    self.typeFilter(DOCUMENT_TYPES[0]);
    let foundEntry;
    self.entries().forEach(entry => {
      entry.highlight(false);
      if (entry.definition() && entry.definition().uuid === uuid) {
        foundEntry = entry;
      }
    });
    if (foundEntry) {
      if (foundEntry.definition().type === 'directory') {
        self.activeEntry(foundEntry);
        if (!foundEntry.entries().length) {
          foundEntry.load();
        }
      } else {
        window.setTimeout(() => {
          huePubSub.subscribeOnce('assist.db.scrollToComplete', () => {
            foundEntry.highlight(true);
            // Timeout is for animation effect
            window.setTimeout(() => {
              foundEntry.highlight(false);
            }, 1800);
          });
          huePubSub.publish('assist.db.scrollTo', foundEntry);
        }, 0);
      }
    }
  }

  showContextPopover(entry, event, positionAdjustment) {
    const self = this;
    const $source = $(event.target);
    const offset = $source.offset();
    if (positionAdjustment) {
      offset.left += positionAdjustment.left;
      offset.top += positionAdjustment.top;
    }

    self.statsVisible(true);
    huePubSub.publish('context.popover.show', {
      data: {
        type: 'hue',
        definition: self.definition()
      },
      showInAssistEnabled: false,
      orientation: 'right',
      pinEnabled: false,
      source: {
        element: event.target,
        left: offset.left,
        top: offset.top - 3,
        right: offset.left + $source.width() + 1,
        bottom: offset.top + $source.height() - 3
      }
    });
    huePubSub.subscribeOnce('context.popover.hidden', () => {
      self.statsVisible(false);
    });
  }

  addDirectoryParamToUrl(url) {
    const self = this;
    const directoryId = self.getDirectory();

    if (!directoryId) {
      return url;
    } else if (url.indexOf('?') !== -1) {
      return url + '&directory_uuid=' + self.definition().uuid;
    } else {
      return url + '?directory_uuid=' + self.definition().uuid;
    }
  }

  beforeContextOpen() {
    if (!this.selected()) {
      this.parent.selectedEntries().forEach(entry => {
        entry.selected(false);
      });
      this.selected(true);
    }
  }

  showRenameDirectoryModal() {
    let selectedEntry = this;
    if (!selectedEntry.selected()) {
      selectedEntry = selectedEntry.selectedEntry();
    }
    if (!selectedEntry.isTrash() && !selectedEntry.isTrashed() && selectedEntry.canModify()) {
      $('#renameDirectoryName').val(selectedEntry.definition().name);
      $('#renameDirectoryModal').modal('show');
    }
  }

  setSort(name) {
    if (this.activeSort().indexOf(name) === -1) {
      if (name === 'lastModified') {
        this.activeSort('lastModifiedDesc');
      } else {
        this.activeSort(name + 'Asc');
      }
    } else if (name !== 'lastModified' && this.activeSort().indexOf('Asc') !== -1) {
      this.activeSort(name + 'Desc');
    } else if (name === 'lastModified' && this.activeSort().indexOf('Desc') !== -1) {
      this.activeSort('lastModifiedAsc');
    } else {
      this.activeSort('defaultAsc');
    }
    this.entries.sort(SORTS[this.activeSort()]);
  }

  showSharingModal(entry) {
    if (entry) {
      this.selectedEntries().forEach(otherEntry => {
        if (otherEntry !== entry) {
          otherEntry.selected(false);
        }
      });
      entry.selected(true);
    }
    if (this.selectedEntry()) {
      this.selectedEntry().loadDocument();
      huePubSub.publish('doc.show.share.modal', this.selectedEntry());
    }
  }

  copy() {
    const self = this;
    if (self.selectedEntries().indexOf(self) !== -1) {
      self.activeEntry(self.parent);
    }
    const copyNext = () => {
      if (self.selectedEntries().length > 0) {
        const nextUuid = self.selectedEntries().shift().definition().uuid;
        apiHelper.copyDocument({
          uuid: nextUuid,
          successCallback: () => {
            copyNext();
          },
          errorCallback: () => {
            self.activeEntry().load();
          }
        });
      } else {
        huePubSub.publish('assist.document.refresh');
        self.activeEntry().load();
      }
    };
    copyNext();
  }

  async loadDocument(successCallback, errorCallback) {
    return new Promise((resolve, reject) => {
      this.document(new HueDocument({ fileEntry: this }));
      this.document().load(
        () => {
          if (successCallback) {
            successCallback(this.document());
          }
          resolve(this.document());
        },
        err => {
          if (errorCallback) {
            errorCallback(err);
          }
          reject(err);
        }
      );
    });
  }

  /**
   * @param {HueFileEntry[]} entries
   */
  moveHere(entries) {
    const self = this;
    if (self.app === 'documents') {
      const moveNext = () => {
        if (entries.length > 0) {
          const nextId = entries.shift().definition().uuid;
          apiHelper.moveDocument({
            successCallback: () => {
              moveNext();
            },
            errorCallback: () => {
              self.activeEntry().load();
            },
            sourceId: nextId,
            destinationId: self.definition().uuid
          });
        } else {
          huePubSub.publish('assist.document.refresh');
          if (self !== self.activeEntry()) {
            self.load();
          }
          self.activeEntry().load();
        }
      };
      moveNext();
    }
  }

  createNewEntry(options) {
    return new HueFileEntry(
      $.extend(
        {
          activeEntry: this.activeEntry,
          activeSort: this.activeSort,
          trashEntry: this.trashEntry,
          serverTypeFilter: this.serverTypeFilter,
          typeFilter: this.typeFilter,
          app: this.app,
          user: this.user,
          superuser: this.superuser
        },
        options
      )
    );
  }

  search(query) {
    const self = this;

    const owner = self.definition().isSearchResult ? self.parent : self;

    if (!query) {
      if (self.definition().isSearchResult) {
        self.activeEntry(self.parent);
      }
      return;
    }

    const resultEntry = self.createNewEntry({
      definition: {
        isSearchResult: true,
        name: '"' + query + '"'
      },
      parent: owner
    });

    self.activeEntry(resultEntry);

    resultEntry.loading(true);

    apiHelper.searchDocuments({
      uuid: owner.uuid,
      query: query,
      type: self.serverTypeFilter().type,
      successCallback: function (data) {
        resultEntry.hasErrors(false);
        const newEntries = [];

        data.documents.forEach(definition => {
          const entry = self.createNewEntry({
            definition: definition,
            parent: self
          });
          if (!entry.isTrash()) {
            newEntries.push(entry);
          }
        });

        resultEntry.entries(newEntries);
        resultEntry.loading(false);
        resultEntry.loaded(true);
      },
      errorCallback: () => {
        resultEntry.hasErrors(true);
        resultEntry.loading(false);
        resultEntry.loaded(true);
      }
    });
  }

  toggleSelected() {
    this.selected(!this.selected());
  }

  openSelected() {
    if (this.selectedEntries().length === 1) {
      this.selectedEntry().open();
    }
  }

  open() {
    if (this.definition().type === 'directory') {
      this.makeActive();
      huePubSub.publish('file.browser.directory.opened');
      if (!this.loaded()) {
        this.load();
      }
    } else {
      huePubSub.publish('open.link', this.definition().absoluteUrl);
    }
  }

  load(successCallback, errorCallback, silenceErrors) {
    const self = this;
    if (self.loading()) {
      return;
    }
    self.loading(true);

    if (self.app === 'documents') {
      apiHelper.fetchDocuments({
        uuid: self.definition().uuid,
        type: self.serverTypeFilter().type,
        silenceErrors: !!silenceErrors,
        successCallback: data => {
          self.definition(data.document);
          self.hasErrors(false);
          const newEntries = [];

          data.children.forEach(definition => {
            const entry = self.createNewEntry({
              definition: definition,
              parent: self
            });
            if (entry.isTrash()) {
              self.trashEntry(entry);
            } else {
              newEntries.push(entry);
            }
          });

          newEntries.sort(SORTS[self.activeSort()]);
          self.entries(newEntries);
          if (!self.parent && data.parent) {
            self.parent = self.createNewEntry({
              definition:
                data.parent.path === '/' && self.isSharedWithMe() ? { name: '/' } : data.parent,
              parent: null
            });
          }
          self.loading(false);
          self.loaded(true);

          if (
            self.isRoot() &&
            self.entries().length === 1 &&
            self.entries()[0].definition().type === 'directory' &&
            self.entries()[0].isSharedWithMe()
          ) {
            self.activeEntry(self.entries()[0]);
            self.activeEntry().load(successCallback);
          } else if (successCallback && typeof successCallback === 'function') {
            successCallback();
          }
        },
        errorCallback: () => {
          self.hasErrors(true);
          self.loading(false);
          self.loaded(true);
          if (errorCallback) {
            errorCallback();
          }
        }
      });
    }
  }

  showTrash() {
    if (this.trashEntry()) {
      this.trashEntry().open();
    }
  }

  emptyTrash() {
    if (this.trashEntry()) {
      const openConfirmation = () => {
        this.trashEntry()
          .entries()
          .forEach(entry => entry.selected(true));
        huePubSub.publish(SHOW_DELETE_DOC_MODAL_EVENT, this.trashEntry());
      };

      if (!this.trashEntry().loaded()) {
        this.trashEntry().load(openConfirmation);
      } else {
        openConfirmation();
      }
    }
  }

  moveToTrash() {
    if (this.selectedEntries().length > 0 && (this.superuser || !this.sharedWithMeSelected())) {
      this.entriesToDelete(this.selectedEntries());
      this.removeDocuments(false);
    }
  }

  showRestoreConfirmation() {
    if (this.selectedEntries().length > 0 && (this.superuser || !this.sharedWithMeSelected())) {
      $('#restoreFromTrashModal').modal('show');
    }
  }

  removeDocuments(deleteForever) {
    const self = this;
    if (self.entriesToDelete().indexOf(self) !== -1) {
      self.activeEntry(self.parent);
    }

    const deleteNext = () => {
      if (self.entriesToDelete().length > 0) {
        const nextUuid = self.entriesToDelete().shift().definition().uuid;
        apiHelper.deleteDocument({
          uuid: nextUuid,
          skipTrash: deleteForever,
          successCallback: () => {
            deleteNext();
          },
          errorCallback: () => {
            self.activeEntry().load();
            $('#deleteEntriesModal').modal('hide');
            $('.modal-backdrop').remove();
            self.deletingEntries(false);
          }
        });
      } else {
        self.deletingEntries(false);
        huePubSub.publish('assist.document.refresh');
        $('#deleteEntriesModal').modal('hide');
        $('.modal-backdrop').remove();
        self.activeEntry().load();
      }
    };
    self.deletingEntries(true);
    deleteNext();
  }

  closeUploadModal() {
    if (this.app === 'documents') {
      huePubSub.publish('hide.import.documents.modal');
    }
    // Allow the modal to hide
    window.setTimeout(() => {
      this.uploading(false);
      this.uploadComplete(false);
      this.uploadFailed(false);
    }, 400);
  }

  upload(importSuccessCallback) {
    const self = this;
    if (
      document.getElementById('importDocumentInput').files.length > 0 &&
      self.app === 'documents'
    ) {
      self.uploading(true);
      self.uploadComplete(false);
      self.uploadFailed(false);
      self.importedDocSummary(null);
      self.showTable(false);
      apiHelper.uploadDocument({
        formData: new FormData($('#importDocumentsForm')[0]),
        successCallback: data => {
          self.uploading(false);
          self.uploadComplete(true);
          huePubSub.publish('assist.document.refresh');
          self.load();
          self.importedDocSummary(data);

          if (importSuccessCallback) {
            importSuccessCallback();
          }
        },
        progressHandler: event => {
          $('#importDocumentsProgress').val(Math.round((event.loaded / event.total) * 100));
        },
        errorCallback: () => {
          self.uploading(false);
          self.uploadComplete(true);
          self.uploadFailed(true);
        }
      });
    }
  }

  importedDocumentCount() {
    if (this.importedDocSummary()) {
      return this.importedDocSummary()['documents'].length;
    }
    return 0;
  }

  toggleShowTable() {
    this.showTable(!this.showTable());
  }

  makeActive() {
    if (!this.loaded()) {
      this.load();
    }
    this.activeEntry(this);
  }

  showUploadModal() {
    if (this.app === 'documents' && !this.isTrash() && !this.isTrashed()) {
      huePubSub.publish('show.import.documents.modal', { fileEntry: this });
    }
  }

  contextMenuDownload() {
    if (this.selected()) {
      this.parent.download();
    } else {
      this.downloadThis();
    }
  }

  downloadThis() {
    window.location.href =
      window.HUE_BASE_URL +
      '/desktop/api2/doc/export?documents=' +
      ko.mapping.toJSON([this.definition().id]);
  }

  download() {
    if (this.app === 'documents') {
      if (this.selectedEntries().length > 0) {
        const ids = this.selectedEntries().map(entry => entry.definition().id);
        window.location.href =
          window.HUE_BASE_URL + '/desktop/api2/doc/export?documents=' + ko.mapping.toJSON(ids);
      } else {
        this.downloadThis();
      }
    }
  }

  restoreFromTrash() {
    const self = this;
    if (self.app === 'documents') {
      if (self.selectedEntries().indexOf(self) !== -1) {
        self.activeEntry(self.parent);
      }

      if (self.selectedEntries().length > 0) {
        const uuids = self
          .selectedEntries()
          .map(entry => entry.definition().uuid)
          .join(',');
        apiHelper.restoreDocument({
          uuids: uuids,
          successCallback: () => {
            huePubSub.publish('assist.document.refresh');
            self.activeEntry().load();
          },
          errorCallback: () => {
            self.activeEntry().load();
          }
        });
      } else {
        self.activeEntry().load();
      }
      $('#restoreFromTrashModal').modal('hide');
    }
  }

  createDirectory(name) {
    const self = this;
    if (name && self.app === 'documents') {
      apiHelper.createDocumentsFolder({
        successCallback: function () {
          huePubSub.publish('assist.document.refresh');
          self.load();
        },
        parentUuid: self.definition().uuid,
        name: name
      });
      $('#newDirectoryName').val(null);
    }
  }

  renameDirectory(name) {
    const self = this;
    if (name && self.app === 'documents') {
      apiHelper.updateDocument({
        successCallback: function () {
          huePubSub.publish('assist.document.refresh');
          self.load();
        },
        uuid: self.definition().uuid,
        name: name
      });
      $('#renameDirectoryName').val(null);
    }
  }
}

export default HueFileEntry;
