/home/ivoiecob/email.hirewise-va.com/modules/MailWebclient/js/views/CComposeView.js
'use strict'

const _ = require('underscore'),
  $ = require('jquery'),
  ko = require('knockout')

const AddressUtils = require('%PathToCoreWebclientModule%/js/utils/Address.js'),
  FilesUtils = require('%PathToCoreWebclientModule%/js/utils/Files.js'),
  TextUtils = require('%PathToCoreWebclientModule%/js/utils/Text.js'),
  Types = require('%PathToCoreWebclientModule%/js/utils/Types.js'),
  Utils = require('%PathToCoreWebclientModule%/js/utils/Common.js'),
  App = require('%PathToCoreWebclientModule%/js/App.js'),
  Browser = require('%PathToCoreWebclientModule%/js/Browser.js'),
  CJua = require('%PathToCoreWebclientModule%/js/CJua.js'),
  CoreAjax = require('%PathToCoreWebclientModule%/js/Ajax.js'),
  ModulesManager = require('%PathToCoreWebclientModule%/js/ModulesManager.js'),
  Routing = require('%PathToCoreWebclientModule%/js/Routing.js'),
  Screens = require('%PathToCoreWebclientModule%/js/Screens.js'),
  UserSettings = require('%PathToCoreWebclientModule%/js/Settings.js'),
  WindowOpener = require('%PathToCoreWebclientModule%/js/WindowOpener.js'),
  CAbstractScreenView = require('%PathToCoreWebclientModule%/js/views/CAbstractScreenView.js'),
  Popups = require('%PathToCoreWebclientModule%/js/Popups.js'),
  AlertPopup = require('%PathToCoreWebclientModule%/js/popups/AlertPopup.js')

const SelectFilesPopup = ModulesManager.run('FilesWebclient', 'getSelectFilesPopup')

const LinksUtils = require('modules/%ModuleName%/js/utils/Links.js'),
  SendingUtils = require('modules/%ModuleName%/js/utils/Sending.js'),
  EditorUtils = require('modules/%ModuleName%/js/utils/Editor.js'),
  AccountList = require('modules/%ModuleName%/js/AccountList.js'),
  Ajax = require('modules/%ModuleName%/js/Ajax.js'),
  MailCache = require('modules/%ModuleName%/js/Cache.js'),
  MainTabExtMethods = require('modules/%ModuleName%/js/MainTabExtMethods.js'),
  SenderSelector = require('modules/%ModuleName%/js/SenderSelector.js'),
  Settings = require('modules/%ModuleName%/js/Settings.js'),
  CMessageModel = require('modules/%ModuleName%/js/models/CMessageModel.js'),
  CAttachmentModel = require('modules/%ModuleName%/js/models/CAttachmentModel.js'),
  CComposeViewAutoEncrypt = require('modules/%ModuleName%/js/views/CComposeViewAutoEncrypt.js')

const CHtmlEditorView = EditorUtils.getCHtmlEditorView(),
  MainTab = App.isNewTab() && window.opener && window.opener.MainTabMailMethods,
  $html = $('html')
/**
 * @constructor
 */
function CComposeView() {
  CAbstractScreenView.call(this, '%ModuleName%')
  CComposeViewAutoEncrypt.call(this)

  this.browserTitle = ko.computed(function () {
    return AccountList.getEmail() + ' - ' + TextUtils.i18n('%MODULENAME%/HEADING_COMPOSE_BROWSER_TAB')
  })

  var self = this

  this.toAddrDom = ko.observable()
  this.toAddrDom.subscribe(function () {
    this.initInputosaurus(this.toAddrDom, this.toAddr, this.lockToAddr, 'to')
  }, this)
  this.ccAddrDom = ko.observable()
  this.ccAddrDom.subscribe(function () {
    this.initInputosaurus(this.ccAddrDom, this.ccAddr, this.lockCcAddr, 'cc')
  }, this)
  this.bccAddrDom = ko.observable()
  this.bccAddrDom.subscribe(function () {
    this.initInputosaurus(this.bccAddrDom, this.bccAddr, this.lockBccAddr, 'bcc')
  }, this)

  this.folderList = MailCache.folderList
  this.folderList.subscribe(function () {
    this.getMessageOnRoute()
  }, this)

  this.bNewTab = App.isNewTab()
  this.bDemo = UserSettings.IsDemo

  this.sending = ko.observable(false)
  this.saving = ko.observable(false)

  this.oHtmlEditor = new CHtmlEditorView(false, Settings.AllowComposePlainText, this)

  this.visibleBcc = ko.observable(false)
  this.visibleBcc.subscribe(function () {
    $html.toggleClass('screen-compose-bcc', this.visibleCc())
    _.defer(
      _.bind(function () {
        $(this.bccAddrDom()).inputosaurus('resizeInput')
      }, this)
    )
  }, this)
  this.visibleCc = ko.observable(false)
  this.visibleCc.subscribe(function () {
    $html.toggleClass('screen-compose-cc', this.visibleCc())
    _.defer(
      _.bind(function () {
        $(this.ccAddrDom()).inputosaurus('resizeInput')
      }, this)
    )
  }, this)

  this.sendReadingConfirmation = ko.observable(false).extend({ reversible: true })

  this.composeUploaderButton = ko.observable(null)
  this.composeUploaderButton.subscribe(function () {
    this.initUploader()
  }, this)
  this.composeUploaderDropPlace = ko.observable(null)
  this.composeUploaderBodyDragOver = ko.observable(false)
  this.composeUploaderDragOver = ko.observable(false)
  this.allowDragNDrop = ko.observable(false)
  this.uploaderBodyDragOver = ko.computed(function () {
    return this.allowDragNDrop() && this.composeUploaderBodyDragOver()
  }, this)
  this.uploaderDragOver = ko.computed(function () {
    return this.allowDragNDrop() && this.composeUploaderDragOver()
  }, this)

  this.selectedImportance = ko.observable(Enums.Importance.Normal).extend({ reversible: true })

  this.senderAccountId = SenderSelector.senderAccountId
  this.senderList = SenderSelector.senderList
  this.visibleFrom = ko.computed(function () {
    return App.isNewTab() || this.senderList().length > 1 || this.senderAccountId() !== MailCache.currentAccountId()
  }, this)
  this.selectedSender = SenderSelector.selectedSender
  this.selectedFetcherOrIdentity = SenderSelector.selectedFetcherOrIdentity
  this.selectedFetcherOrIdentity.subscribe(function () {
    this.oHtmlEditor.clearUndoRedo()
    this.oHtmlEditor.commit()
  }, this)

  this.signature = ko.observable('')
  this.prevSignature = ko.observable(null)
  ko.computed(function () {
    var sSignature = SendingUtils.getClearSignature(this.senderAccountId(), this.selectedFetcherOrIdentity())

    if (this.prevSignature() === null) {
      this.prevSignature(sSignature)
      this.signature(sSignature)
    } else {
      this.prevSignature(this.signature())
      this.signature(sSignature)
      this.oHtmlEditor.changeSignatureContent(this.signature(), this.prevSignature())
    }
  }, this)

  this.lockToAddr = ko.observable(false)
  this.toAddr = ko.observable('').extend({ reversible: true })
  this.toAddr.subscribe(function () {
    if (!this.lockToAddr()) {
      $(this.toAddrDom()).val(this.toAddr())
      $(this.toAddrDom()).inputosaurus('refresh')
    }
  }, this)
  this.lockCcAddr = ko.observable(false)
  this.ccAddr = ko.observable('').extend({ reversible: true })
  this.ccAddr.subscribe(function () {
    if (!this.lockCcAddr()) {
      $(this.ccAddrDom()).val(this.ccAddr())
      $(this.ccAddrDom()).inputosaurus('refresh')
    }
  }, this)
  this.lockBccAddr = ko.observable(false)
  this.bccAddr = ko.observable('').extend({ reversible: true })
  this.bccAddr.subscribe(function () {
    if (!this.lockBccAddr()) {
      $(this.bccAddrDom()).val(this.bccAddr())
      $(this.bccAddrDom()).inputosaurus('refresh')
    }
  }, this)
  this.recipientEmails = ko.computed(function () {
    var aRecip = [this.toAddr(), this.ccAddr(), this.bccAddr()].join(',').split(','),
      aEmails = []
    _.each(aRecip, function (sRecip) {
      var sTrimmedRecip = $.trim(sRecip),
        oRecip = null
      if (sTrimmedRecip !== '') {
        oRecip = AddressUtils.getEmailParts(sTrimmedRecip)
        if (oRecip.email) {
          aEmails.push(oRecip.email)
        }
      }
    })
    return aEmails
  }, this)
  this.subject = ko.observable('').extend({ reversible: true })
  this.textBody = ko.observable('')
  this.textBody.subscribe(function (value) {
    this.oHtmlEditor.setText(this.textBody(), this.oHtmlEditor.plainTextMode())
    this.oHtmlEditor.commit()
  }, this)

  this.focusedField = ko.observable()
  this.oHtmlEditor.textFocused.subscribe(function (val) {
    if (this.oHtmlEditor.textFocused()) {
      this.focusedField('text')
    }
  }, this)
  this.subjectFocused = ko.observable(false)
  this.subjectFocused.subscribe(function () {
    if (this.subjectFocused()) {
      this.focusedField('subject')
    }
  }, this)

  this.templateUid = ko.observable('')
  this.templateFolderName = ko.observable(MailCache.getTemplateFolder())

  this.draftUid = ko.observable('')
  this.draftUid.subscribe(function () {
    MailCache.editedDraftUid(this.draftUid())
  }, this)
  this.draftInfo = ko.observableArray([])
  this.routeType = ko.observable('')
  this.routeParams = ko.observableArray([])
  this.inReplyTo = ko.observable('')
  this.references = ko.observable('')

  this.bUploadStatus = false
  this.iUploadAttachmentsTimer = 0
  this.messageUploadAttachmentsStarted = ko.observable(false)

  this.messageUploadAttachmentsStarted.subscribe(function (bValue) {
    window.clearTimeout(self.iUploadAttachmentsTimer)
    if (bValue) {
      self.iUploadAttachmentsTimer = window.setTimeout(function () {
        self.bUploadStatus = true
        Screens.showLoading(TextUtils.i18n('%MODULENAME%/INFO_ATTACHMENTS_LOADING'))
      }, 4000)
    } else {
      if (self.bUploadStatus) {
        self.iUploadAttachmentsTimer = window.setTimeout(function () {
          self.bUploadStatus = false
          Screens.hideLoading()
        }, 1000)
      } else {
        Screens.hideLoading()
      }
    }
  }, this)

  this.attachments = ko.observableArray([])
  this.attachmentsChanged = ko.observable(false)
  this.attachments.subscribe(function () {
    this.attachmentsChanged(true)
  }, this)
  this.notUploadedAttachments = ko.computed(function () {
    return _.filter(this.attachments(), function (oAttach) {
      return !oAttach.uploaded()
    })
  }, this)

  this.allAttachmentsUploaded = ko.computed(function () {
    return this.notUploadedAttachments().length === 0 && !this.messageUploadAttachmentsStarted()
  }, this)

  this.notInlineAttachments = ko.computed(function () {
    return _.filter(this.attachments(), function (oAttach) {
      return !oAttach.linked()
    })
  }, this)
  this.notInlineAttachments.subscribe(function () {
    $html.toggleClass('screen-compose-attachments', this.notInlineAttachments().length > 0)
  }, this)

  this.allowStartSending = ko.computed(function () {
    return !this.saving()
  }, this)
  this.allowStartSending.subscribe(function () {
    if (this.allowStartSending() && this.requiresPostponedSending()) {
      SendingUtils.sendPostponedMail(this.draftUid())
      this.requiresPostponedSending(false)
    }
  }, this)
  this.requiresPostponedSending = ko.observable(false)

  // file uploader
  this.oJua = null

  this.isDraftsCleared = ko.observable(false)

  this.backToListOnSendOrSave = ko.observable(false)

  this.composeShown = ko.computed(function () {
    return (!!this.opened && this.opened()) || (!!this.shown && this.shown())
  }, this)

  this.toolbarControllers = ko.observableArray([])
  this.messageRowControllers = ko.observableArray([])
  this.uploadAttachmentsController = ko.observableArray([])
  this.allControllers = ko.computed(function () {
    return _.union(this.toolbarControllers(), this.messageRowControllers(), this.uploadAttachmentsController())
  }, this)
  this.disableHeadersEdit = ko.computed(function () {
    var bDisableHeadersEdit = false

    _.each(this.allControllers(), function (oController) {
      bDisableHeadersEdit =
        bDisableHeadersEdit || (!!oController.disableHeadersEdit && oController.disableHeadersEdit())
    })

    return bDisableHeadersEdit
  }, this)
  this.disableFromEdit = ko.computed(function () {
    var bDisableFromEdit = false

    _.each(this.allControllers(), function (oController) {
      bDisableFromEdit = bDisableFromEdit || (!!oController.disableFromEdit && oController.disableFromEdit())
    })

    return bDisableFromEdit
  }, this)
  this.messageSignedOrEncrypted = ko.computed(function () {
    var bSignedOrEncrypted = false

    _.each(this.allControllers(), function (oController) {
      if (_.isFunction(oController.pgpEncrypted) && _.isFunction(oController.pgpSecured)) {
        bSignedOrEncrypted = bSignedOrEncrypted || oController.pgpEncrypted() || oController.pgpSecured()
      }
    })

    return bSignedOrEncrypted
  }, this)
  ko.computed(function () {
    var bDisableBodyEdit = false

    _.each(this.allControllers(), function (oController) {
      bDisableBodyEdit = bDisableBodyEdit || (!!oController.disableBodyEdit && oController.disableBodyEdit())
    })
    this.oHtmlEditor.setDisableEdit(bDisableBodyEdit)
  }, this)

  this.draftFolderIsAvailable = ko.computed(function () {
    return !!MailCache.folderList().draftsFolder()
  }, this)
  this.disableAutosave = ko.observable(false)
  // Autosave interval is automatically cleared when compose is not shown or message is sending/saving or
  // it's disabled by compose screen or one of controllers. After changins these parameters autosave
  // interval might be started again.
  if (Settings.AllowAutosaveInDrafts && Settings.AutoSaveIntervalSeconds > 0) {
    this.iAutosaveInterval = -1
    ko.computed(function () {
      var bAllowAutosave =
        this.draftFolderIsAvailable() &&
        this.composeShown() &&
        !this.sending() &&
        !this.saving() &&
        !this.disableAutosave() &&
        !MailCache.disableComposeAutosave()
      _.each(this.allControllers(), function (oController) {
        bAllowAutosave = bAllowAutosave && !(!!oController.disableAutosave && oController.disableAutosave())
      })

      window.clearInterval(this.iAutosaveInterval)

      if (bAllowAutosave) {
        this.iAutosaveInterval = window.setInterval(
          _.bind(this.executeSave, this, true),
          Settings.AutoSaveIntervalSeconds * 1000
        )
      }
    }, this)
  }

  this.backToListCommand = Utils.createCommand(this, this.executeBackToList)
  this.sendCommand = Utils.createCommand(this, this.executeSend, this.isEnableSending)
  this.saveCommand = Utils.createCommand(this, this.executeSaveCommand, this.isEnableSaving)
  this.visibleSaveTemplateControl = ko.observable(false)
  this.saveTemplateCommand = Utils.createCommand(this, this.executeTemplateSaveCommand, this.isEnableSaving)

  this.messageFields = ko.observable(null)
  this.bottomPanel = ko.observable(null)

  this.sHotkeysHintsViewTemplate = !Browser.mobileDevice ? '%ModuleName%_Compose_HotkeysHintsView' : ''
  this.sPopupButtonsViewTemplate = !App.isNewTab() ? '%ModuleName%_Compose_PopupButtonsView' : ''

  this.aHotkeys = [
    { value: 'Ctrl+S', action: TextUtils.i18n('%MODULENAME%/LABEL_SAVE_HOTKEY'), visible: this.draftFolderIsAvailable },
  ].concat(this.oHtmlEditor.getHotKeysDescriptions())

  if (Settings.AllowQuickSendOnCompose) {
    this.aHotkeys.unshift({
      value: 'Ctrl+Enter',
      action: TextUtils.i18n('%MODULENAME%/LABEL_SEND_HOTKEY'),
      visible: ko.observable(true),
    })
  }

  this.bAllowFiles = !!SelectFilesPopup

  this.ignoreHasUnsavedChanges = ko.observable(false)
  this.changedInPreviousWindow = ko.observable(false)

  this.hasUnsavedChanges = ko.computed(function () {
    return !this.ignoreHasUnsavedChanges() && this.isChanged() && this.isEnableSaving()
  }, this)

  this.saveAndCloseTooltip = ko.computed(function () {
    return this.draftFolderIsAvailable() && this.hasUnsavedChanges()
      ? TextUtils.i18n('%MODULENAME%/ACTION_SAVE_CLOSE')
      : TextUtils.i18n('%MODULENAME%/ACTION_CLOSE')
  }, this)

  this.splitterDom = ko.observable()

  this.headersCompressed = ko.observable(false)
  this.allowCcBccSwitchers = ko.computed(function () {
    return !this.disableHeadersEdit() && !this.headersCompressed()
  }, this)

  this.registerOwnToolbarControllers()

  this.setAutoEncryptSubscribes()

  App.broadcastEvent('%ModuleName%::ConstructView::after', { Name: this.ViewConstructorName, View: this })
}

_.extendOwn(CComposeView.prototype, CAbstractScreenView.prototype)
_.extendOwn(CComposeView.prototype, CComposeViewAutoEncrypt.prototype)

CComposeView.prototype.ViewTemplate = App.isNewTab() ? '%ModuleName%_ComposeScreenView' : '%ModuleName%_ComposeView'
CComposeView.prototype.ViewConstructorName = 'CComposeView'

/**
 * Determines if sending a message is allowed.
 */
CComposeView.prototype.isEnableSending = function () {
  var bRecipientIsEmpty = this.toAddr().length === 0 && this.ccAddr().length === 0 && this.bccAddr().length === 0,
    bFoldersLoaded = this.folderList() && this.folderList().iAccountId !== 0
  return bFoldersLoaded && !this.sending() && !bRecipientIsEmpty && this.allAttachmentsUploaded()
}

/**
 * Determines if saving a message is allowed.
 */
CComposeView.prototype.isEnableSaving = function () {
  var bFoldersLoaded = this.folderList() && this.folderList().iAccountId !== 0

  return this.composeShown() && bFoldersLoaded && !this.sending() && !this.saving()
}

/**
 * @param {Object} koAddrDom
 * @param {Object} koAddr
 * @param {Object} koLockAddr
 * @param {string} sFocusedField
 */
CComposeView.prototype.initInputosaurus = function (koAddrDom, koAddr, koLockAddr, sFocusedField) {
  if (koAddrDom() && $(koAddrDom()).length > 0) {
    const suggestParameters = {
        storage: 'all',
        addContactGroups: true,
        addUserGroups: Settings.AllowUserGroupsInComposeAutocomplete,
        addEmailsToGroups: true,
        exceptEmail: '',
        withoutEmptyEmails: true,
      },
      autoCompleteSource = ModulesManager.run('ContactsWebclient', 'getSuggestionsAutocompleteCallback', [
        suggestParameters,
      ])
    var oOptions = {
      width: 'auto',
      parseOnBlur: true,
      autoCompleteSource: _.isFunction(autoCompleteSource) ? autoCompleteSource : function () {},
      autoCompleteDeleteItem:
        ModulesManager.run('ContactsWebclient', 'getSuggestionsAutocompleteDeleteHandler') || function () {},
      autoCompleteAppendTo: $(koAddrDom()).closest('td'),
      change: _.bind(function (ev) {
        koLockAddr(true)
        this.setRecipient(koAddr, ev.target.value)
        koLockAddr(false)
      }, this),
      copy: _.bind(function (sVal) {
        this.inputosaurusBuffer = sVal
      }, this),
      paste: _.bind(function () {
        var sInputosaurusBuffer = this.inputosaurusBuffer || ''
        this.inputosaurusBuffer = ''
        return sInputosaurusBuffer
      }, this),
      focus: _.bind(this.focusedField, this, sFocusedField),
      mobileDevice: Browser.mobileDevice,
    }
    $(koAddrDom()).inputosaurus(_.extendOwn(oOptions, this.getInputosaurusMethods()))
  }
}

/**
 * Colapse from to table.
 */
CComposeView.prototype.changeHeadersCompressed = function () {
  this.headersCompressed(!this.headersCompressed())
}

/**
 * Executes after applying bindings.
 */
CComposeView.prototype.onBind = function () {
  ModulesManager.run('SessionTimeoutWeblient', 'registerFunction', [_.bind(this.executeSave, this, false)])

  if (!App.isMobile()) {
    this.hotKeysBind()
  }
}

CComposeView.prototype.hotKeysBind = function () {
  ;(this.$popupDom || this.$viewDom).on(
    'keydown',
    $.proxy(function (ev) {
      if (ev && ev.ctrlKey && !ev.altKey && !ev.shiftKey) {
        var nKey = ev.keyCode,
          bComputed = this.composeShown() && (!this.minimized || !this.minimized()) && ev && ev.ctrlKey
        if (bComputed && nKey === Enums.Key.s) {
          ev.preventDefault()
          ev.returnValue = false

          if (this.isEnableSaving()) {
            this.saveCommand()
          }
        } else if (Settings.AllowQuickSendOnCompose && bComputed && nKey === Enums.Key.Enter && this.toAddr() !== '') {
          this.sendCommand()
        }
      }
    }, this)
  )
}

CComposeView.prototype.getMessageOnRoute = function () {
  var oParams = LinksUtils.parseCompose(this.routeParams()),
    oAccount = AccountList.getAccountByHash(oParams.AccountHash)
  if (oAccount && this.routeType() !== '' && oParams.MessageFolderName && oParams.MessageUid) {
    MailCache.getMessage(oAccount.id(), oParams.MessageFolderName, oParams.MessageUid, this.onMessageResponse, this)
  }
}

/**
 * Executes if the view model shows. Requests a folder list from the server to know the full names
 * of the folders Drafts and Sent Items.
 */
CComposeView.prototype.onShow = function () {
  // onShow is called before onRoute so reset is called here before anything else
  this.reset()

  var sFocusedField = this.focusedField()

  $(this.splitterDom()).trigger('resize')
  $(this.bottomPanel()).trigger('resize')

  //	if (!this.oHtmlEditor.isInitialized())
  //	{
  // Crea $container must be recreated because compose popup is destroyed after it is closed
  this.oHtmlEditor.init(this.textBody(), this.oHtmlEditor.plainTextMode(), '7')
  this.oHtmlEditor.commit()
  //	}

  this.initUploader()

  this.backToListOnSendOrSave(false)

  this.focusedField(sFocusedField) //oHtmlEditor initialization puts focus on it and changes the variable focusedField

  $html.addClass('screen-compose')

  if (this.oJua) {
    this.oJua.setDragAndDropEnabledStatus(true)
  }

  this.visibleSaveTemplateControl(MailCache.getCurrentTemplateFolders().length > 0)
}

CComposeView.prototype.reset = function () {
  this.setTextBody('', false)

  this.bUploadStatus = false
  window.clearTimeout(this.iUploadAttachmentsTimer)
  this.messageUploadAttachmentsStarted(false)

  this.templateUid('')
  this.templateFolderName(MailCache.getTemplateFolder())
  this.draftUid('')
  this.draftInfo.removeAll()
  this.setDataFromMessage(new CMessageModel())

  this.isDraftsCleared(false)

  this.ignoreHasUnsavedChanges(false)
}

CComposeView.prototype.setTextBody = function (textBody, isPlain) {
  this.oHtmlEditor.plainTextMode(isPlain)
  this.textBody(textBody)
}

/**
 * Executes if routing was changed.
 *
 * @param {Array} aParams
 */
CComposeView.prototype.onRoute = function (aParams) {
  this.setDataFromMessage(new CMessageModel()) // clear before filling up

  var oParams = LinksUtils.parseCompose(aParams)

  if (App.isNewTab()) {
    // should be the first action to set right account id in new tab
    AccountList.changeCurrentAccountByHash(oParams.AccountHash)
  }

  this.routeType(oParams.RouteType)
  switch (this.routeType()) {
    case Enums.ReplyType.ForwardAsAttach:
      this.routeParams(aParams)
      this.fillDefault(oParams)
    case Enums.ReplyType.Reply:
    case Enums.ReplyType.ReplyAll:
    case Enums.ReplyType.Resend:
    case Enums.ReplyType.Forward:
    case 'drafts':
      this.routeParams(aParams)
      if (this.folderList().iAccountId !== 0) {
        this.getMessageOnRoute()
      }
      break
    case 'data':
      var oData = oParams.Object

      if (oData) {
        if (oData.to) {
          this.setRecipient(this.toAddr, oData.to)
        }
        if (oData.subject) {
          this.subject(oData.subject)
        }
        if (oData.selectedSenderId) {
          this.selectedSender(oData.selectedSenderId)
        }
        let isPlain = !oData.isHtml
        let bodyText = '<div></div>'
        if (oData.body) {
          bodyText = oData.isHtml ? '<div>' + oData.body + '</div>' : oData.body
        }
        if (oData.replyToMessage) {
          var oReplyData = SendingUtils.getReplyDataFromMessage(
            oData.replyToMessage,
            Enums.ReplyType.Reply,
            App.currentAccountId(),
            null,
            true
          )
          this.draftInfo(oReplyData.DraftInfo)
          this.draftUid(oReplyData.DraftUid)
          this.setRecipient(this.toAddr, oReplyData.To)
          this.setRecipient(this.ccAddr, oReplyData.Cc)
          this.setRecipient(this.bccAddr, oReplyData.Bcc)
          this.subject(oReplyData.Subject)
          isPlain = false
          bodyText = oReplyData.Text
          this.attachments(oReplyData.Attachments)
          this.inReplyTo(oReplyData.InReplyTo)
          this.references(oReplyData.References)

          this.requestAttachmentsTempName()
        }
        if (oData.attachments) {
          this.addAttachments(oData.attachments)
        }

        this.setTextBody(bodyText, isPlain)
        this.triggerToolbarControllersAfterPopulatingMessage(true, isPlain, bodyText)
        this.commit(true)
      }

      break
    default:
      this.routeParams(aParams)
      this.fillDefault(oParams)
      break
  }
}

CComposeView.prototype.fillDefault = function (oParams) {
  var sSignature = SendingUtils.getSignatureText(this.senderAccountId(), this.selectedFetcherOrIdentity(), true),
    oComposedMessage = MainTab ? MainTab.getComposedMessage(window.name) : null,
    oToAddr = oParams.ToAddr
  if (oComposedMessage) {
    this.setMessageDataInNewTab(oComposedMessage)
    if (this.changedInPreviousWindow()) {
      _.defer(_.bind(this.executeSave, this, true))
    }
  } else if (sSignature !== '') {
    this.setTextBody('<br /><br />' + sSignature + '<br />', false)
  }

  if (oToAddr) {
    this.setRecipient(this.toAddr, oToAddr.to)
    if (oToAddr.hasMailto) {
      this.subject(oToAddr.subject)
      this.setRecipient(this.ccAddr, oToAddr.cc)
      this.setRecipient(this.bccAddr, oToAddr.bcc)
      if (oToAddr.body !== '') {
        this.setTextBody('<div>' + oToAddr.body + '</div>', false)
      }
    }
  }

  if (this.routeType() === Enums.ReplyType.ForwardAsAttach && oParams.Object) {
    this.addMessageAsAttachment(oParams.Object)
  }

  if (this.routeType() === 'attachments' && oParams.Object) {
    this.addAttachments(oParams.Object)
  }

  _.defer(
    _.bind(function () {
      this.focusAfterFilling()
    }, this)
  )

  this.visibleCc(this.ccAddr() !== '')
  this.visibleBcc(this.bccAddr() !== '')
  this.commit(true)
}

CComposeView.prototype.focusToAddr = function () {
  $(this.toAddrDom()).inputosaurus('focus')
}

CComposeView.prototype.focusCcAddr = function () {
  $(this.ccAddrDom()).inputosaurus('focus')
}

CComposeView.prototype.focusBccAddr = function () {
  $(this.bccAddrDom()).inputosaurus('focus')
}

CComposeView.prototype.focusAfterFilling = function () {
  switch (this.focusedField()) {
    case 'to':
      this.focusToAddr()
      break
    case 'cc':
      this.visibleCc(true)
      this.focusCcAddr()
      break
    case 'bcc':
      this.visibleBcc(true)
      this.focusBccAddr()
      break
    case 'subject':
      this.subjectFocused(true)
      break
    case 'text':
      this.oHtmlEditor.setFocus()
      break
    default:
      if (this.toAddr().length === 0) {
        this.focusToAddr()
      } else if (this.subject().length === 0) {
        this.subjectFocused(true)
      } else {
        this.oHtmlEditor.setFocus()
      }
      break
  }
}

/**
 * Executes if view model was hidden.
 */
CComposeView.prototype.onHide = function () {
  if (!_.isFunction(this.closePopup) && this.hasUnsavedChanges()) {
    this.executeSave(true)
  }

  this.headersCompressed(false)

  this.routeParams([])

  this.subjectFocused(false)
  this.focusedField('')

  this.messageUploadAttachmentsStarted(false)

  $html
    .removeClass('screen-compose')
    .removeClass('screen-compose-cc')
    .removeClass('screen-compose-bcc')
    .removeClass('screen-compose-attachments')

  if (this.oJua) {
    this.oJua.setDragAndDropEnabledStatus(false)
  }

  this.recipientsInfo({})
  this.recipientsInfo.valueHasMutated()
}

/**
 * @param {Object} koRecipient
 * @param {string} sRecipient
 */
CComposeView.prototype.setRecipient = function (koRecipient, sRecipient) {
  if (koRecipient() === sRecipient) {
    koRecipient.valueHasMutated()
  } else {
    koRecipient(sRecipient)
  }
}

/**
 * @param {Object} oMessage
 */
CComposeView.prototype.onMessageResponse = function (oMessage) {
  var oReplyData = null

  if (oMessage === null) {
    this.setDataFromMessage(new CMessageModel())
  } else {
    switch (this.routeType()) {
      case Enums.ReplyType.Reply:
      case Enums.ReplyType.ReplyAll:
        SenderSelector.setFetcherOrIdentityByReplyMessage(oMessage)

        oReplyData = SendingUtils.getReplyDataFromMessage(
          oMessage,
          this.routeType(),
          this.senderAccountId(),
          this.selectedFetcherOrIdentity(),
          true
        )

        this.draftInfo(oReplyData.DraftInfo)
        this.draftUid(oReplyData.DraftUid)
        this.setRecipient(this.toAddr, oReplyData.To)
        this.setRecipient(this.ccAddr, oReplyData.Cc)
        this.setRecipient(this.bccAddr, oReplyData.Bcc)
        this.subject(oReplyData.Subject)
        this.setTextBody(oReplyData.Text, false)
        this.attachments(oReplyData.Attachments)
        this.inReplyTo(oReplyData.InReplyTo)
        this.references(oReplyData.References)
        break

      case Enums.ReplyType.ForwardAsAttach:
        oReplyData = SendingUtils.getReplyDataFromMessage(
          oMessage,
          this.routeType(),
          this.senderAccountId(),
          this.selectedFetcherOrIdentity(),
          true
        )
        this.draftInfo(oReplyData.DraftInfo)
        this.draftUid(oReplyData.DraftUid)
        this.inReplyTo(oReplyData.InReplyTo)
        this.references(oReplyData.References)
        break

      case Enums.ReplyType.Forward:
        SenderSelector.setFetcherOrIdentityByReplyMessage(oMessage)

        oReplyData = SendingUtils.getReplyDataFromMessage(
          oMessage,
          this.routeType(),
          this.senderAccountId(),
          this.selectedFetcherOrIdentity(),
          true
        )

        this.draftInfo(oReplyData.DraftInfo)
        this.draftUid(oReplyData.DraftUid)
        this.setRecipient(this.toAddr, oReplyData.To)
        this.setRecipient(this.ccAddr, oReplyData.Cc)
        this.subject(oReplyData.Subject)
        this.setTextBody(oReplyData.Text, false)
        this.attachments(oReplyData.Attachments)
        this.inReplyTo(oReplyData.InReplyTo)
        this.references(oReplyData.References)
        break

      case Enums.ReplyType.Resend:
        this.setDataFromMessage(oMessage)
        break

      case 'drafts':
        if (-1 !== $.inArray(oMessage.folder(), MailCache.getCurrentTemplateFolders())) {
          this.templateUid(oMessage.uid())
          this.templateFolderName(oMessage.folder())
        } else {
          var oFolderList = MailCache.oFolderListItems[oMessage.accountId()],
            sDraftFolder = oFolderList ? oFolderList.draftsFolderFullName() : ''
          if (sDraftFolder === oMessage.folder()) {
            this.draftUid(oMessage.uid())
          }
        }
        this.setDataFromMessage(oMessage)
        break
    }

    if (this.routeType() !== Enums.ReplyType.ForwardAsAttach && this.attachments().length > 0) {
      this.requestAttachmentsTempName()
    }

    this.routeType('')
  }

  this.visibleCc(this.ccAddr() !== '')
  this.visibleBcc(this.bccAddr() !== '')
  this.commit(true)

  _.defer(
    _.bind(function () {
      this.focusAfterFilling()
    }, this)
  )

  if (oMessage) {
    var oParams = {
      AccountId: oMessage.accountId(),
      FolderFullName: oMessage.folder(),
      MessageUid: oMessage.uid(),
      Compose: this,
    }
    if (this.allAttachmentsUploaded()) {
      App.broadcastEvent('%ModuleName%::ComposeMessageLoaded', oParams)
    } else {
      var oSubscription = this.allAttachmentsUploaded.subscribe(function () {
        if (this.allAttachmentsUploaded()) {
          App.broadcastEvent('%ModuleName%::ComposeMessageLoaded', oParams)
        }
        oSubscription.dispose()
      }, this)
    }
  }
}

/**
 * @param {Object} oMessage
 */
CComposeView.prototype.setDataFromMessage = function (oMessage) {
  var sTextBody = '',
    oFetcherOrIdentity = SendingUtils.getFirstFetcherOrIdentityByRecipientsOrDefault(
      oMessage.oFrom.aCollection,
      oMessage.accountId()
    )
  SenderSelector.changeSenderAccountId(oMessage.accountId(), oFetcherOrIdentity)

  if (oMessage.isPlain()) {
    sTextBody = oMessage.textRaw()
  } else {
    sTextBody = oMessage.getConvertedHtml()
  }
  this.draftInfo(oMessage.draftInfo())
  this.inReplyTo(oMessage.inReplyTo())
  this.references(oMessage.references())
  this.setRecipient(this.toAddr, oMessage.oTo.getFull())
  this.setRecipient(this.ccAddr, oMessage.oCc.getFull())
  this.setRecipient(this.bccAddr, oMessage.oBcc.getFull())
  this.subject(oMessage.subject())
  this.attachments(oMessage.attachments())
  this.setTextBody(sTextBody, oMessage.isPlain())
  this.selectedImportance(oMessage.importance())
  this.sendReadingConfirmation(oMessage.readingConfirmationAddressee() !== '')

  var bDraft = !!oMessage.folderObject() && oMessage.folderObject().type() === Enums.FolderTypes.Drafts
  this.triggerToolbarControllersAfterPopulatingMessage(
    bDraft,
    oMessage.isPlain(),
    oMessage.textRaw(),
    oMessage.sensitivity()
  )
}

CComposeView.prototype.triggerToolbarControllersAfterPopulatingMessage = function (
  bDraft,
  bPlain,
  sRawText,
  iSensitivity
) {
  _.each(
    this.allControllers(),
    function (oController) {
      if (_.isFunction(oController.doAfterPopulatingMessage)) {
        oController.doAfterPopulatingMessage({
          bDraft: bDraft,
          bPlain: bPlain,
          sRawText: sRawText,
          iSensitivity: iSensitivity,
        })
      }
    }.bind(this)
  )
}

/**
 * @param {Object} oResponse
 * @param {Object} oRequest
 */
CComposeView.prototype.onDataAsAttachmentUpload = function (oResponse, oRequest) {
  var oParameters = oRequest.Parameters,
    oResult = oResponse.Result,
    sHash = oParameters.Hash,
    oAttachment = _.find(this.attachments(), function (oAttach) {
      return oAttach.hash() === sHash
    })
  this.messageUploadAttachmentsStarted(false)

  if (oAttachment) {
    if (oResult && oResult.Attachment) {
      oAttachment.parseFromUpload(oResult.Attachment)
    } else {
      oAttachment.errorFromUpload()
    }
  }
}

CComposeView.prototype.addAttachments = function (aFiles) {
  _.each(
    aFiles,
    _.bind(function (oFileData) {
      var oAttach = new CAttachmentModel(this.senderAccountId())
      oAttach.parseFromUpload(oFileData)
      this.attachments.push(oAttach)
    }, this)
  )
}

/**
 * @param {Array} aFiles
 */
CComposeView.prototype.addFilesAsAttachment = function (aFiles) {
  var oAttach = null,
    aHashes = []
  _.each(
    aFiles,
    function (oFile) {
      oAttach = new CAttachmentModel(this.senderAccountId())
      oAttach.fileName(oFile.fileName())
      oAttach.hash(oFile.hash())
      oAttach.thumbUrlInQueue(oFile.thumbUrlInQueue())
      oAttach.uploadStarted(true)

      this.attachments.push(oAttach)

      aHashes.push(oFile.hash())
    },
    this
  )

  if (aHashes.length > 0) {
    this.messageUploadAttachmentsStarted(true)

    CoreAjax.send('Files', 'GetFilesForUpload', { Hashes: aHashes }, this.onFilesUpload, this)
  }
}

/**
 * @param {array} attachments
 */
CComposeView.prototype.addUploadingAttachments = function (attachments) {
  if (Array.isArray(attachments) && attachments.length > 0) {
    attachments.forEach((attachment) => {
      this.attachments.push(attachment)
    })
    this.messageUploadAttachmentsStarted(true)
  }
}

/**
 * @param {Object} oResponse
 * @param {Object} oRequest
 */
CComposeView.prototype.onFilesUpload = function (oResponse, oRequest) {
  var oParameters = oRequest.Parameters,
    aResult = oResponse.Result,
    aHashes = oParameters.Hashes
  this.messageUploadAttachmentsStarted(false)
  if (_.isArray(aResult)) {
    _.each(
      aResult,
      function (oFileData) {
        var oAttachment = _.find(this.attachments(), function (oAttach) {
          return oAttach.hash() === oFileData.Hash
        })

        if (oAttachment) {
          oAttachment.parseFromUpload(oFileData)
          oAttachment.hash(oFileData.NewHash)
        }
      },
      this
    )
  } else {
    _.each(
      aHashes,
      function (sHash) {
        var oAttachment = _.find(this.attachments(), function (oAttach) {
          return oAttach.hash() === sHash
        })

        if (oAttachment) {
          oAttachment.errorFromUpload()
        }
      },
      this
    )
  }
}

/**
 * @param {Object} oMessage
 */
CComposeView.prototype.addMessageAsAttachment = function (oMessage) {
  var oAttach = new CAttachmentModel(oMessage.accountId()),
    oParameters = null
  if (oMessage) {
    oAttach.fileName(oMessage.subject() + '.eml')
    oAttach.uploadStarted(true)

    this.attachments.push(oAttach)

    oParameters = {
      AccountID: oMessage.accountId(),
      MessageFolder: oMessage.folder(),
      MessageUid: oMessage.uid(),
      FileName: oAttach.fileName(),
    }

    this.messageUploadAttachmentsStarted(true)

    Ajax.send('SaveMessageAsTempFile', oParameters, this.onSaveMessageAsTempFile, this)
  }
}

/**
 * @param {Object} oResponse
 * @param {Object} oRequest
 */
CComposeView.prototype.onSaveMessageAsTempFile = function (oResponse, oRequest) {
  var oResult = oResponse.Result,
    sFileName = oRequest.Parameters.FileName,
    oAttach = null
  this.messageUploadAttachmentsStarted(false)

  if (oResult) {
    oAttach = _.find(this.attachments(), function (oAttach) {
      return oAttach.fileName() === sFileName && oAttach.uploadStarted()
    })

    if (oAttach) {
      oAttach.parseFromUpload(oResult, oRequest.Parameters.MessageFolder, oRequest.Parameters.MessageUid)
    }
  } else {
    oAttach = _.find(this.attachments(), function (oAttach) {
      return oAttach.fileName() === sFileName && oAttach.uploadStarted()
    })

    if (oAttach) {
      oAttach.errorFromUpload()
    }
  }
}

/**
 * @param {Object} oResponse
 * @param {Object} oRequest
 */
CComposeView.prototype.onContactVCardUpload = function (oResponse, oRequest) {
  var oParameters = oRequest.Parameters,
    oResult = oResponse.Result,
    oAttach = null
  this.messageUploadAttachmentsStarted(false)

  if (oResult) {
    oAttach = _.find(this.attachments(), function (oAttach) {
      return oAttach.fileName() === oResult.Name && oAttach.uploadStarted()
    })

    if (oAttach) {
      oAttach.parseFromUpload(oResult)
    }
  } else {
    oAttach = _.find(this.attachments(), function (oAttach) {
      return oAttach.fileName() === oParameters.Name && oAttach.uploadStarted()
    })

    if (oAttach) {
      oAttach.errorFromUpload()
    }
  }
}

CComposeView.prototype.requestAttachmentsTempName = function () {
  var aHash = _.map(this.attachments(), function (oAttach) {
    oAttach.uploadUid(oAttach.hash())
    oAttach.uploadStarted(true)
    return oAttach.hash()
  })
  if (aHash.length > 0) {
    this.messageUploadAttachmentsStarted(true)
    Ajax.send(
      'SaveAttachmentsAsTempFiles',
      { AccountID: this.senderAccountId(), Attachments: aHash },
      this.onMessageUploadAttachmentsResponse,
      this
    )
  }
}

/**
 * @param {Object} oResponse
 * @param {Object} oRequest
 */
CComposeView.prototype.onMessageUploadAttachmentsResponse = function (oResponse, oRequest) {
  var aHashes = oRequest.Parameters.Attachments

  this.messageUploadAttachmentsStarted(false)

  if (oResponse.Result) {
    _.each(oResponse.Result, _.bind(this.setAttachTempNameByHash, this))
  } else {
    _.each(
      aHashes,
      function (sHash) {
        var oAttachment = _.find(this.attachments(), function (oAttach) {
          return oAttach.hash() === sHash
        })

        if (oAttachment) {
          oAttachment.errorFromUpload()
        }
      },
      this
    )
    Screens.showError(TextUtils.i18n('%MODULENAME%/ERROR_UPLOAD_FORWARD_ATTACHMENTS'))
  }
}

/**
 * @param {string} sHash
 * @param {string} sTempName
 */
CComposeView.prototype.setAttachTempNameByHash = function (sHash, sTempName) {
  _.each(this.attachments(), function (oAttach) {
    if (oAttach.hash() === sHash) {
      oAttach.tempName(sTempName)
      oAttach.uploadStarted(false)
    }
  })
}

/**
 * @param {Object} oParameters
 */
CComposeView.prototype.setMessageDataInNewTab = function (oParameters) {
  this.templateUid(oParameters.templateUid)
  this.templateFolderName(oParameters.templateFolderName)
  this.draftInfo(oParameters.draftInfo)
  this.draftUid(oParameters.draftUid)
  this.inReplyTo(oParameters.inReplyTo)
  this.references(oParameters.references)
  this.setRecipient(this.toAddr, oParameters.toAddr)
  this.setRecipient(this.ccAddr, oParameters.ccAddr)
  this.setRecipient(this.bccAddr, oParameters.bccAddr)
  this.subject(oParameters.subject)
  this.attachments(
    _.map(
      oParameters.attachments,
      function (oRawAttach) {
        var oAttach = new CAttachmentModel(oParameters.senderAccountId)
        oAttach.parse(oRawAttach)
        return oAttach
      },
      this
    )
  )
  this.setTextBody(oParameters.textBody, oParameters.plainTextMode)
  this.selectedImportance(oParameters.selectedImportance)
  this.sendReadingConfirmation(oParameters.sendReadingConfirmation)
  this.changedInPreviousWindow(oParameters.changedInPreviousWindow)

  _.each(this.allControllers(), function (oController) {
    if (_.isFunction(oController.doAfterApplyingMainTabParameters)) {
      oController.doAfterApplyingMainTabParameters(oParameters)
    }
  })

  SenderSelector.changeSenderAccountId(oParameters.senderAccountId, oParameters.selectedFetcherOrIdentity)
  this.focusedField(oParameters.focusedField)
}

/**
 * @param {boolean=} bOnlyCurrentWindow = false
 */
CComposeView.prototype.commit = function (bOnlyCurrentWindow) {
  this.toAddr.commit()
  this.ccAddr.commit()
  this.bccAddr.commit()
  this.subject.commit()
  this.selectedImportance.commit()
  this.sendReadingConfirmation.commit()
  _.each(this.allControllers(), function (oController) {
    if (_.isFunction(oController.commit)) {
      oController.commit()
    }
  })
  this.oHtmlEditor.commit()
  this.attachmentsChanged(false)
  if (!bOnlyCurrentWindow) {
    this.changedInPreviousWindow(false)
  }
}

CComposeView.prototype.isChanged = function () {
  var bToAddrChanged = this.toAddr.changed(),
    bCcAddrChanged = this.ccAddr.changed(),
    bBccAddrChanged = this.bccAddr.changed(),
    bSubjectChanged = this.subject.changed(),
    bImportanceChanged = this.selectedImportance.changed(),
    bReadConfChanged = this.sendReadingConfirmation.changed(),
    bControllersChanged = false,
    bHtmlChanged = this.oHtmlEditor.textChanged(),
    bAttachmentsChanged = this.attachmentsChanged(),
    bChangedInPreviousWindow = this.changedInPreviousWindow()
  _.each(this.allControllers(), function (oController) {
    if (_.isFunction(oController.isChanged)) {
      bControllersChanged = bControllersChanged || oController.isChanged()
    }
  })

  return (
    bToAddrChanged ||
    bCcAddrChanged ||
    bBccAddrChanged ||
    bSubjectChanged ||
    bImportanceChanged ||
    bReadConfChanged ||
    bControllersChanged ||
    bHtmlChanged ||
    bAttachmentsChanged ||
    bChangedInPreviousWindow
  )
}

CComposeView.prototype.executeBackToList = function () {
  if (App.isNewTab()) {
    window.close()
  } else if (!!this.shown && this.shown()) {
    Routing.setPreviousHash()
  }
  this.backToListOnSendOrSave(false)
}

/**
 * Creates new attachment for upload.
 *
 * @param {string} sFileUid
 * @param {Object} oFileData
 */
CComposeView.prototype.onFileUploadSelect = function (sFileUid, oFileData) {
  var oAttach

  if (FilesUtils.showErrorIfAttachmentSizeLimit(oFileData.FileName, Types.pInt(oFileData.Size))) {
    return false
  }
  oAttach = new CAttachmentModel(this.senderAccountId())
  oAttach.onUploadSelect(sFileUid, oFileData)
  this.attachments.push(oAttach)

  return true
}

/**
 * Returns attachment found by uid.
 *
 * @param {string} sFileUid
 */
CComposeView.prototype.getAttachmentByUid = function (sFileUid) {
  return _.find(this.attachments(), function (oAttach) {
    return oAttach.uploadUid() === sFileUid
  })
}

/**
 * Finds attachment by uid. Calls it's function to start upload.
 *
 * @param {string} sFileUid
 */
CComposeView.prototype.onFileUploadStart = function (sFileUid) {
  var oAttach = this.getAttachmentByUid(sFileUid)

  if (oAttach) {
    oAttach.onUploadStart()
  }
}

/**
 * Finds attachment by uid. Calls it's function to progress upload.
 *
 * @param {string} sFileUid
 * @param {number} iUploadedSize
 * @param {number} iTotalSize
 */
CComposeView.prototype.onFileUploadProgress = function (sFileUid, iUploadedSize, iTotalSize) {
  var oAttach = this.getAttachmentByUid(sFileUid)

  if (oAttach) {
    oAttach.onUploadProgress(iUploadedSize, iTotalSize)
  }
}

/**
 * Finds attachment by uid. Calls it's function to complete upload.
 *
 * @param {string} sFileUid
 * @param {boolean} bResponseReceived
 * @param {Object} oResult
 */
CComposeView.prototype.onFileUploadComplete = function (sFileUid, bResponseReceived, oResult) {
  var oAttach = this.getAttachmentByUid(sFileUid)

  if (oAttach) {
    oAttach.onUploadComplete(sFileUid, bResponseReceived, oResult)
  }
}

/**
 * Finds attachment by uid. Calls it's function to cancel upload.
 *
 * @param {string} sFileUid
 */
CComposeView.prototype.onFileRemove = function (sFileUid) {
  var oAttach = this.getAttachmentByUid(sFileUid)

  if (this.oJua) {
    this.oJua.cancel(sFileUid)
  }

  this.attachments.remove(oAttach)
}

/**
 * Initializes file uploader.
 */
CComposeView.prototype.initUploader = function () {
  // this.oJua must be re-initialized because compose popup is destroyed after it is closed
  if (this.composeShown() && this.composeUploaderButton()) {
    this.oJua = new CJua({
      action: '?/Api/',
      name: 'jua-uploader',
      queueSize: 2,
      clickElement: this.composeUploaderButton(),
      hiddenElementsPosition: UserSettings.IsRTL ? 'right' : 'left',
      dragAndDropElement: this.composeUploaderDropPlace(),
      disableAjaxUpload: false,
      disableFolderDragAndDrop: false,
      disableDragAndDrop: false,
      hidden: _.extendOwn(
        {
          Module: Settings.ServerModuleName,
          Method: 'UploadAttachment',
          Parameters: function () {
            return JSON.stringify({
              AccountID: MailCache.currentAccountId(),
            })
          },
        },
        App.getCommonRequestParameters()
      ),
    })

    this.oJua
      .on('onDragEnter', _.bind(this.composeUploaderDragOver, this, true))
      .on('onDragLeave', _.bind(this.composeUploaderDragOver, this, false))
      .on('onBodyDragEnter', _.bind(this.composeUploaderBodyDragOver, this, true))
      .on('onBodyDragLeave', _.bind(this.composeUploaderBodyDragOver, this, false))
      .on('onProgress', _.bind(this.onFileUploadProgress, this))
      .on('onSelect', _.bind(this.onFileUploadSelect, this))
      .on('onStart', _.bind(this.onFileUploadStart, this))
      .on('onComplete', _.bind(this.onFileUploadComplete, this))

    this.allowDragNDrop(this.oJua.isDragAndDropSupported())
  }
}

/**
 * @param {boolean} bRemoveSignatureAnchor
 * @param {boolean} bSaveTemplate
 */
CComposeView.prototype.getSendSaveParameters = function (bRemoveSignatureAnchor, bSaveTemplate) {
  var oAttachments = SendingUtils.convertAttachmentsForSending(this.attachments()),
    oParameters = null
  _.each(this.oHtmlEditor.getUploadedImagesData(), function (oAttach) {
    oAttachments[oAttach.TempName] = [oAttach.Name, oAttach.CID, '1', '1']
  })

  oParameters = {
    AccountID: this.senderAccountId(),
    IdentityID:
      this.selectedFetcherOrIdentity() && this.selectedFetcherOrIdentity().IDENTITY
        ? this.selectedFetcherOrIdentity().id()
        : '',
    AliasID:
      this.selectedFetcherOrIdentity() && this.selectedFetcherOrIdentity().ALIAS
        ? this.selectedFetcherOrIdentity().id()
        : '',
    FetcherID:
      this.selectedFetcherOrIdentity() && this.selectedFetcherOrIdentity().FETCHER
        ? this.selectedFetcherOrIdentity().id()
        : '',
    DraftInfo: this.draftInfo(),
    DraftUid: this.draftUid(),
    To: this.toAddr(),
    Cc: this.ccAddr(),
    Bcc: this.bccAddr(),
    Subject: this.subject(),
    Text: this.oHtmlEditor.getText(bRemoveSignatureAnchor),
    IsHtml: !this.oHtmlEditor.plainTextMode(),
    Importance: this.selectedImportance(),
    SendReadingConfirmation: this.sendReadingConfirmation(),
    Attachments: oAttachments,
    InReplyTo: this.inReplyTo(),
    References: this.references(),
  }

  _.each(this.allControllers(), function (oController) {
    if (_.isFunction(oController.doAfterPreparingSendMessageParameters)) {
      oController.doAfterPreparingSendMessageParameters(oParameters)
    }
  })

  if (this.templateFolderName() !== '' && bSaveTemplate) {
    oParameters.DraftFolder = this.templateFolderName()
    oParameters.DraftUid = this.templateUid()
  }

  return oParameters
}

/**
 * @param {Object} oResponse
 * @param {Object} oRequest
 */
CComposeView.prototype.onSendOrSaveMessageResponse = function (oResponse, oRequest) {
  var oResData = SendingUtils.onSendOrSaveMessageResponse(oResponse, oRequest, this.requiresPostponedSending()),
    oParameters = oRequest.Parameters
  this.commit()

  switch (oResData.Method) {
    case 'SaveMessage':
      if (
        oResData.Result &&
        oParameters.DraftUid === this.templateUid() &&
        oParameters.DraftFolder === this.templateFolderName()
      ) {
        this.templateUid(Types.pString(oResData.NewUid))
        if (this.composeShown() && this instanceof CComposeView) {
          // it is screen, not popup
          Routing.replaceHashDirectly(
            LinksUtils.getComposeFromMessage(
              'drafts',
              MailCache.currentAccountId(),
              oParameters.DraftFolder,
              this.templateUid()
            )
          )
        }
      } else if (oResData.Result && oParameters.DraftUid === this.draftUid()) {
        this.draftUid(Types.pString(oResData.NewUid))
        if (this.composeShown() && this instanceof CComposeView) {
          // it is screen, not popup
          Routing.replaceHashDirectly(
            LinksUtils.getComposeFromMessage(
              'drafts',
              MailCache.currentAccountId(),
              oParameters.DraftFolder,
              this.draftUid()
            )
          )
        }
      }
      this.saving(false)
      break
    case 'SendMessage':
      if (oResData.Result) {
        if (this.backToListOnSendOrSave()) {
          if (_.isFunction(this.closePopup)) {
            this.closePopup()
          } else {
            this.executeBackToList()
          }
        }
      }
      this.sending(false)
      break
  }
}

CComposeView.prototype.verifyDataForSending = function () {
  var aToIncorrect = AddressUtils.getIncorrectEmailsFromAddressString(this.toAddr()),
    aCcIncorrect = AddressUtils.getIncorrectEmailsFromAddressString(this.ccAddr()),
    aBccIncorrect = AddressUtils.getIncorrectEmailsFromAddressString(this.bccAddr()),
    aIncorrect = _.union(aToIncorrect, aCcIncorrect, aBccIncorrect),
    aEncodedIncorrect = _.map(aIncorrect, function (sIncorrect) {
      return TextUtils.encodeHtml(sIncorrect)
    }),
    sWarning = TextUtils.i18n('%MODULENAME%/ERROR_INPUT_CORRECT_EMAILS') + aEncodedIncorrect.join(', ')
  if (aIncorrect.length > 0) {
    Popups.showPopup(AlertPopup, [sWarning])
    return false
  }

  return true
}

/**
 * @param {mixed} mParam
 */
CComposeView.prototype.executeSend = function (mParam) {
  var bCancelSend = false,
    fContinueSending = _.bind(function () {
      this.sending(true)
      this.requiresPostponedSending(!this.allowStartSending())

      SendingUtils.send(
        'SendMessage',
        this.getSendSaveParameters(true),
        true,
        this.onSendOrSaveMessageResponse,
        this,
        this.requiresPostponedSending()
      )

      this.backToListOnSendOrSave(true)
    }, this)
  if (this.autoEncryptSignMessage()) {
    this.encryptSignAndSend()
  } else if (this.isEnableSending() && this.verifyDataForSending()) {
    _.each(this.allControllers(), function (oController) {
      if (_.isFunction(oController.doBeforeSend)) {
        bCancelSend = bCancelSend || oController.doBeforeSend(fContinueSending)
      }
    })

    if (!bCancelSend) {
      fContinueSending()
    }
  }
}

CComposeView.prototype.executeSaveCommand = function () {
  if (this.draftFolderIsAvailable()) {
    this.executeSave(false)
  }
}

CComposeView.prototype.executeTemplateSaveCommand = function () {
  this.executeSave(false, true, true)
}

/**
 * @param {boolean=} bAutosave = false
 * @param {boolean=} bWaitResponse = true
 * @param {boolean=} bSaveTemplate = false
 */
CComposeView.prototype.executeSave = function (bAutosave, bWaitResponse, bSaveTemplate) {
  bAutosave = !!bAutosave
  bWaitResponse = bWaitResponse === undefined ? true : bWaitResponse
  bSaveTemplate = !!bSaveTemplate

  var fOnSaveMessageResponse = bWaitResponse
      ? this.onSendOrSaveMessageResponse
      : SendingUtils.onSendOrSaveMessageResponse,
    oContext = bWaitResponse ? this : SendingUtils,
    fSave = _.bind(function (bSave) {
      if (bSave) {
        this.saving(bWaitResponse)
        SendingUtils.send(
          'SaveMessage',
          this.getSendSaveParameters(false, bSaveTemplate),
          !bAutosave,
          fOnSaveMessageResponse,
          oContext
        )
      }
    }, this),
    bCancelSaving = false
  if (this.isEnableSaving()) {
    if (!bAutosave || this.isChanged()) {
      if (!bAutosave) {
        _.each(
          this.allControllers(),
          function (oController) {
            if (_.isFunction(oController.doBeforeSave)) {
              bCancelSaving = bCancelSaving || oController.doBeforeSave(fSave)
            }
          },
          this
        )
      }
      if (!bCancelSaving) {
        fSave(true)
      }
    }

    this.backToListOnSendOrSave(true)
  }
}

/**
 * Changes visibility of bcc field.
 */
CComposeView.prototype.changeBccVisibility = function () {
  this.visibleBcc(!this.visibleBcc())

  if (this.visibleBcc()) {
    this.focusBccAddr()
  } else {
    this.focusToAddr()
  }
}

/**
 * Changes visibility of bcc field.
 */
CComposeView.prototype.changeCcVisibility = function () {
  this.visibleCc(!this.visibleCc())

  if (this.visibleCc()) {
    this.focusCcAddr()
  } else {
    this.focusToAddr()
  }
}

CComposeView.prototype.getMessageDataForNewTab = function () {
  var aAttachments = _.map(this.attachments(), function (oAttach) {
      return {
        FileName: oAttach.fileName(),
        TempName: oAttach.tempName(),
        MimeType: oAttach.mimeType(),
        MimePartIndex: oAttach.mimePartIndex(),
        EstimatedSize: oAttach.size(),
        CID: oAttach.cid(),
        ContentLocation: oAttach.contentLocation(),
        IsInline: oAttach.inline(),
        IsLinked: oAttach.linked(),
        Hash: oAttach.hash(),
      }
    }),
    oParameters = null
  oParameters = {
    accountId: this.senderAccountId(),
    templateUid: this.templateUid(),
    templateFolderName: this.templateFolderName(),
    draftInfo: this.draftInfo(),
    draftUid: this.draftUid(),
    inReplyTo: this.inReplyTo(),
    references: this.references(),
    senderAccountId: this.senderAccountId(),
    selectedFetcherOrIdentity: this.selectedFetcherOrIdentity(),
    toAddr: this.toAddr(),
    ccAddr: this.ccAddr(),
    bccAddr: this.bccAddr(),
    subject: this.subject(),
    attachments: aAttachments,
    plainTextMode: this.oHtmlEditor.plainTextMode(),
    textBody: this.oHtmlEditor.getText(),
    selectedImportance: this.selectedImportance(),
    sendReadingConfirmation: this.sendReadingConfirmation(),
    changedInPreviousWindow: this.isChanged(),
    focusedField: this.focusedField(),
  }

  _.each(this.allControllers(), function (oController) {
    if (_.isFunction(oController.doAfterPreparingMainTabParameters)) {
      oController.doAfterPreparingMainTabParameters(oParameters)
    }
  })

  return oParameters
}

CComposeView.prototype.openInNewWindow = function () {
  var sWinName = 'id' + Math.random().toString(),
    oMessageParametersFromCompose = {},
    oWin = null,
    sHash = Routing.buildHashFromArray(LinksUtils.getCompose())
  this.ignoreHasUnsavedChanges(true)
  oMessageParametersFromCompose = this.getMessageDataForNewTab()

  if (this.draftUid().length > 0 && !this.isChanged()) {
    sHash = Routing.buildHashFromArray(
      LinksUtils.getComposeFromMessage(
        'drafts',
        MailCache.currentAccountId(),
        MailCache.folderList().draftsFolderFullName(),
        this.draftUid(),
        true
      )
    )
    oWin = WindowOpener.openTab('?message-newtab' + sHash)
  } else if (this.templateUid().length > 0 && !this.isChanged()) {
    sHash = Routing.buildHashFromArray(
      LinksUtils.getComposeFromMessage(
        'drafts',
        MailCache.currentAccountId(),
        this.templateFolderName(),
        this.templateUid(),
        true
      )
    )
    oWin = WindowOpener.openTab('?message-newtab' + sHash)
  } else if (!this.isChanged()) {
    if (this.routeParams().length > 0) {
      sHash = Routing.buildHashFromArray(_.union([Settings.HashModuleName + '-compose'], this.routeParams()))
    }
    oWin = WindowOpener.openTab('?message-newtab' + sHash)
  } else {
    MainTabExtMethods.passComposedMessage(sWinName, oMessageParametersFromCompose)
    oWin = WindowOpener.openTab('?message-newtab' + sHash, sWinName)
  }

  this.commit()

  if (_.isFunction(this.closePopup)) {
    this.closePopup()
  } else {
    this.executeBackToList()
  }
}

CComposeView.prototype.onShowFilesPopupClick = function () {
  if (this.bAllowFiles) {
    Popups.showPopup(SelectFilesPopup, [_.bind(this.addFilesAsAttachment, this)])
  }
}

CComposeView.prototype.registerOwnToolbarControllers = function () {
  this.registerToolbarController({
    ViewTemplate: '%ModuleName%_Compose_BackButtonView',
    sId: 'back',
    bOnlyMobile: true,
    backToListCommand: this.backToListCommand,
  })
  this.registerToolbarController({
    ViewTemplate: '%ModuleName%_Compose_SendButtonView',
    sId: 'send',
    bAllowMobile: true,
    sendCommand: this.sendCommand,
    toolbarControllers: ko.computed(function () {
      return _.filter(this.toolbarControllers(), function (oController) {
        return oController.bSendButton
      })
    }, this),
  })
  this.registerToolbarController({
    ViewTemplate: '%ModuleName%_Compose_SaveButtonView',
    sId: 'save',
    bAllowMobile: true,
    visible: this.draftFolderIsAvailable,
    saveCommand: this.saveCommand,
  })
  this.registerToolbarController({
    ViewTemplate: '%ModuleName%_Compose_SaveTemplateButtonView',
    sId: 'save-template',
    bAllowMobile: false,
    visible: this.visibleSaveTemplateControl,
    saveTemplateCommand: this.saveTemplateCommand,
  })
  this.registerToolbarController({
    ViewTemplate: '%ModuleName%_Compose_ImportanceDropdownView',
    sId: 'importance',
    selectedImportance: this.selectedImportance,
  })
  this.registerToolbarController({
    ViewTemplate: '%ModuleName%_Compose_ConfirmationCheckboxView',
    sId: 'confirmation',
    sendReadingConfirmation: this.sendReadingConfirmation,
  })
}

/**
 * @param {Object} oController
 */
CComposeView.prototype.registerToolbarController = function (oController) {
  var bAllowRegister = App.isMobile() ? oController.bAllowMobile : !oController.bOnlyMobile,
    iLastIndex = Settings.ComposeToolbarOrder.length
  if (bAllowRegister) {
    oController.bSendButton = !!oController.bSendButton
    this.toolbarControllers.push(oController)
    this.toolbarControllers(
      _.sortBy(this.toolbarControllers(), function (oContr) {
        var iIndex = _.indexOf(Settings.ComposeToolbarOrder, oContr.sId)
        return iIndex !== -1 ? iIndex : iLastIndex
      })
    )
    if (_.isFunction(oController.assignComposeExtInterface)) {
      oController.assignComposeExtInterface(this.getExtInterface())
    }
  }
}

CComposeView.prototype.registerMessageRowController = function (oController) {
  var bAllowRegister = App.isMobile() ? oController.bAllowMobile : !oController.bOnlyMobile
  if (bAllowRegister) {
    this.messageRowControllers.push(oController)
    if (_.isFunction(oController.assignComposeExtInterface)) {
      oController.assignComposeExtInterface(this.getExtInterface())
    }
  }
}

CComposeView.prototype.registerUploadAttachmentsController = function (controller) {
  const allowRegister = App.isMobile() ? controller.bAllowMobile : !controller.bOnlyMobile
  if (allowRegister) {
    this.uploadAttachmentsController.push(controller)
    if (_.isFunction(controller.assignComposeExtInterface)) {
      controller.assignComposeExtInterface(this.getExtInterface())
    }
  }
}

/**
 * @returns {Object}
 */
CComposeView.prototype.getExtInterface = function () {
  return {
    isHtml: _.bind(function () {
      return !this.oHtmlEditor.plainTextMode()
    }, this),
    hasAttachments: _.bind(function () {
      return this.notInlineAttachments().length > 0
    }, this),
    getPlainText: _.bind(this.oHtmlEditor.getPlainText, this.oHtmlEditor),
    koTextChange: this.oHtmlEditor.textChanged,
    getFromEmail: _.bind(function () {
      return this.selectedFetcherOrIdentity() ? this.selectedFetcherOrIdentity().email() : AccountList.getEmail()
    }, this),
    getRecipientEmails: _.bind(function () {
      return this.recipientEmails()
    }, this),
    getRecipientsInfo: _.bind(function () {
      return _.values(this.recipientsInfo())
    }, this),
    getSelectedSender: _.bind(this.selectedSender, this),
    saveSilently: _.bind(this.executeSave, this, true),
    setPlainTextMode: _.bind(this.oHtmlEditor.plainTextMode, this, true),
    setPlainText: _.bind(function (sText) {
      this.setTextBody(sText, true)
    }, this),
    setHtmlTextMode: _.bind(this.oHtmlEditor.plainTextMode, this, false),
    setHtmlText: _.bind(function (sHtml) {
      this.setTextBody(sHtml, false)
    }, this),
    undoHtml: _.bind(this.oHtmlEditor.undoAndClearRedo, this.oHtmlEditor),
    getSubject: _.bind(function () {
      return this.subject()
    }, this),
    koSubject: this.subject,
    getAutoEncryptSignMessage: function () {
      return this.autoEncryptSignMessage()
    }.bind(this),
    getRecipientsEmpty: function () {
      return this.toAddr().length === 0 && this.ccAddr().length === 0 && this.bccAddr().length === 0
    }.bind(this),
    getSendSaveParameters: function () {
      return this.getSendSaveParameters()
    }.bind(this),
    isEnableSending: function () {
      return this.isEnableSending()
    }.bind(this),
    isEnableSaving: function () {
      return this.isEnableSaving()
    }.bind(this),
    getDraftFolderFullName: function (iAccountID) {
      var oFolderList = MailCache.oFolderListItems[iAccountID]
      return oFolderList ? oFolderList.draftsFolderFullName() : ''
    },
    addUploadingAttachments: _.bind(this.addUploadingAttachments, this),
    onFilesUpload: _.bind(this.onFilesUpload, this),
    koSenderAccountId: this.senderAccountId,
    koAllAttachmentsUploaded: this.allAttachmentsUploaded,
    clearFolderCache: function (iAccountId, sDraftFolder) {
      if (MainTab) {
        MainTab.removeMessagesFromCacheForFolder(iAccountId, sDraftFolder)
      } else {
        MailCache.removeMessagesFromCacheForFolder(iAccountId, sDraftFolder)
      }
    },
    commitAndClose: function () {
      this.commit()
      if (_.isFunction(this.closePopup)) {
        this.closePopup()
      } else {
        this.executeBackToList()
      }
    }.bind(this),
  }
}

module.exports = CComposeView