/* eslint-env browser, jquery */
/* global CodeMirror, Cookies, moment, editor, ui, Spinner,
   modeType, Idle, serverurl, key, gapi, Dropbox, FilePicker
   ot, MediaUploader, hex2rgb, num_loaded, Visibility */
require('../vendor/showup/showup')
require('../css/index.css')
require('../css/extra.css')
require('../css/slide-preview.css')
require('../css/site.css')
require('highlight.js/styles/github-gist.css')
var toMarkdown = require('to-markdown')
var saveAs = require('file-saver').saveAs
var randomColor = require('randomcolor')
var _ = require('lodash')
var List = require('list.js')
import {
    checkLoginStateChanged,
    setloginStateChangeEvent
} from './lib/common/login'
import {
    debug,
    DROPBOX_APP_KEY,
    GOOGLE_API_KEY,
    GOOGLE_CLIENT_ID,
    noteid,
    noteurl,
    urlpath,
    version
} from './lib/config'
import {
    autoLinkify,
    deduplicatedHeaderId,
    exportToHTML,
    exportToRawHTML,
    finishView,
    generateToc,
    isValidURL,
    md,
    parseMeta,
    postProcess,
    renderFilename,
    renderTOC,
    renderTags,
    renderTitle,
    scrollToHash,
    smoothHashScroll,
    updateLastChange,
    updateLastChangeUser,
    updateOwner
} from './extra'
import {
    clearMap,
    setupSyncAreas,
    syncScrollToEdit,
    syncScrollToView
} from './syncscroll'
import {
    writeHistory,
    deleteServerHistory,
    getHistory,
    saveHistory,
    removeHistory
} from './history'
var renderer = require('./render')
var preventXSS = renderer.preventXSS
var defaultTextHeight = 20
var viewportMargin = 20
var mac = CodeMirror.keyMap['default'] === CodeMirror.keyMap.macDefault
var defaultEditorMode = 'gfm'
var defaultExtraKeys = {
  'F10': function (cm) {
    cm.setOption('fullScreen', !cm.getOption('fullScreen'))
  },
  'Esc': function (cm) {
    if (cm.getOption('keyMap').substr(0, 3) === 'vim') return CodeMirror.Pass
    else if (cm.getOption('fullScreen')) cm.setOption('fullScreen', false)
  },
  'Cmd-S': function () {
    return false
  },
  'Ctrl-S': function () {
    return false
  },
  'Enter': 'newlineAndIndentContinueMarkdownList',
  'Tab': function (cm) {
    var tab = '\t'
    var spaces = Array(parseInt(cm.getOption('indentUnit')) + 1).join(' ')
        // auto indent whole line when in list or blockquote
    var cursor = cm.getCursor()
    var line = cm.getLine(cursor.line)
    var regex = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))/
    var match
    var multiple = cm.getSelection().split('\n').length > 1 || cm.getSelections().length > 1
    if (multiple) {
      cm.execCommand('defaultTab')
    } else if ((match = regex.exec(line)) !== null) {
      var ch = match[1].length
      var pos = {
        line: cursor.line,
        ch: ch
      }
      if (cm.getOption('indentWithTabs')) { cm.replaceRange(tab, pos, pos, '+input') } else { cm.replaceRange(spaces, pos, pos, '+input') }
    } else {
      if (cm.getOption('indentWithTabs')) { cm.execCommand('defaultTab') } else {
        cm.replaceSelection(spaces)
      }
    }
  },
  'Cmd-Left': 'goLineLeftSmart',
  'Cmd-Right': 'goLineRight',
  'Ctrl-C': function (cm) {
    if (!mac && cm.getOption('keyMap').substr(0, 3) === 'vim') document.execCommand('copy')
    else return CodeMirror.Pass
  },
  'Ctrl-*': function (cm) {
    wrapTextWith(cm, '*')
  },
  'Shift-Ctrl-8': function (cm) {
    wrapTextWith(cm, '*')
  },
  'Ctrl-_': function (cm) {
    wrapTextWith(cm, '_')
  },
  'Shift-Ctrl--': function (cm) {
    wrapTextWith(cm, '_')
  },
  'Ctrl-~': function (cm) {
    wrapTextWith(cm, '~')
  },
  'Shift-Ctrl-`': function (cm) {
    wrapTextWith(cm, '~')
  },
  'Ctrl-^': function (cm) {
    wrapTextWith(cm, '^')
  },
  'Shift-Ctrl-6': function (cm) {
    wrapTextWith(cm, '^')
  },
  'Ctrl-+': function (cm) {
    wrapTextWith(cm, '+')
  },
  'Shift-Ctrl-=': function (cm) {
    wrapTextWith(cm, '+')
  },
  'Ctrl-=': function (cm) {
    wrapTextWith(cm, '=')
  },
  'Shift-Ctrl-Backspace': function (cm) {
    wrapTextWith(cm, 'Backspace')
  }
}
var wrapSymbols = ['*', '_', '~', '^', '+', '=']
function wrapTextWith (cm, symbol) {
  if (!cm.getSelection()) {
    return CodeMirror.Pass
  } else {
    var ranges = cm.listSelections()
    for (var i = 0; i < ranges.length; i++) {
      var range = ranges[i]
      if (!range.empty()) {
        var from = range.from()
        var to = range.to()
        if (symbol !== 'Backspace') {
          cm.replaceRange(symbol, to, to, '+input')
          cm.replaceRange(symbol, from, from, '+input')
                    // workaround selection range not correct after add symbol
          var _ranges = cm.listSelections()
          var anchorIndex = window.editor.indexFromPos(_ranges[i].anchor)
          var headIndex = window.editor.indexFromPos(_ranges[i].head)
          if (anchorIndex > headIndex) {
            _ranges[i].anchor.ch--
          } else {
            _ranges[i].head.ch--
          }
          cm.setSelections(_ranges)
        } else {
          var preEndPos = {
            line: to.line,
            ch: to.ch + 1
          }
          var preText = cm.getRange(to, preEndPos)
          var preIndex = wrapSymbols.indexOf(preText)
          var postEndPos = {
            line: from.line,
            ch: from.ch - 1
          }
          var postText = cm.getRange(postEndPos, from)
          var postIndex = wrapSymbols.indexOf(postText)
                    // check if surround symbol are list in array and matched
          if (preIndex > -1 && postIndex > -1 && preIndex === postIndex) {
            cm.replaceRange('', to, preEndPos, '+delete')
            cm.replaceRange('', postEndPos, from, '+delete')
          }
        }
      }
    }
  }
}
var idleTime = 300000 // 5 mins
var updateViewDebounce = 100
var cursorMenuThrottle = 50
var cursorActivityDebounce = 50
var cursorAnimatePeriod = 100
var supportContainers = ['success', 'info', 'warning', 'danger']
var supportCodeModes = ['javascript', 'typescript', 'jsx', 'htmlmixed', 'htmlembedded', 'css', 'xml', 'clike', 'clojure', 'ruby', 'python', 'shell', 'php', 'sql', 'haskell', 'coffeescript', 'yaml', 'pug', 'lua', 'cmake', 'nginx', 'perl', 'sass', 'r', 'dockerfile', 'tiddlywiki', 'mediawiki', 'go']
var supportCharts = ['sequence', 'flow', 'graphviz', 'mermaid']
var supportHeaders = [
  {
    text: '# h1',
    search: '#'
  },
  {
    text: '## h2',
    search: '##'
  },
  {
    text: '### h3',
    search: '###'
  },
  {
    text: '#### h4',
    search: '####'
  },
  {
    text: '##### h5',
    search: '#####'
  },
  {
    text: '###### h6',
    search: '######'
  },
  {
    text: '###### tags: `example`',
    search: '###### tags:'
  }
]
var supportReferrals = [
  {
    text: '[reference link]',
    search: '[]'
  },
  {
    text: '[reference]: https:// "title"',
    search: '[]:'
  },
  {
    text: '[^footnote link]',
    search: '[^]'
  },
  {
    text: '[^footnote reference]: https:// "title"',
    search: '[^]:'
  },
  {
    text: '^[inline footnote]',
    search: '^[]'
  },
  {
    text: '[link text][reference]',
    search: '[][]'
  },
  {
    text: '[link text](https:// "title")',
    search: '[]()'
  },
  {
    text: '![image alt][reference]',
    search: '![][]'
  },
  {
    text: '',
    search: '![]()'
  },
  {
    text: '',
    search: '![]()'
  },
  {
    text: '[TOC]',
    search: '[]'
  }
]
var supportExternals = [
  {
    text: '{%youtube youtubeid %}',
    search: 'youtube'
  },
  {
    text: '{%vimeo vimeoid %}',
    search: 'vimeo'
  },
  {
    text: '{%gist gistid %}',
    search: 'gist'
  },
  {
    text: '{%slideshare slideshareid %}',
    search: 'slideshare'
  },
  {
    text: '{%speakerdeck speakerdeckid %}',
    search: 'speakerdeck'
  },
  {
    text: '{%pdf pdfurl %}',
    search: 'pdf'
  }
]
var supportExtraTags = [
  {
    text: '[name tag]',
    search: '[]',
    command: function () {
      return '[name=' + window.personalInfo.name + ']'
    }
  },
  {
    text: '[time tag]',
    search: '[]',
    command: function () {
      return '[time=' + moment().format('llll') + ']'
    }
  },
  {
    text: '[my color tag]',
    search: '[]',
    command: function () {
      return '[color=' + window.personalInfo.color + ']'
    }
  },
  {
    text: '[random color tag]',
    search: '[]',
    command: function () {
      var color = randomColor()
      return '[color=' + color + ']'
    }
  }
]
window.modeType = {
  edit: {
    name: 'edit'
  },
  view: {
    name: 'view'
  },
  both: {
    name: 'both'
  }
}
var statusType = {
  connected: {
    msg: 'CONNECTED',
    label: 'label-warning',
    fa: 'fa-wifi'
  },
  online: {
    msg: 'ONLINE',
    label: 'label-primary',
    fa: 'fa-users'
  },
  offline: {
    msg: 'OFFLINE',
    label: 'label-danger',
    fa: 'fa-plug'
  }
}
var defaultMode = modeType.view
// global vars
window.loaded = false
window.needRefresh = false
window.isDirty = false
window.editShown = false
window.visibleXS = false
window.visibleSM = false
window.visibleMD = false
window.visibleLG = false
window.isTouchDevice = 'ontouchstart' in document.documentElement
window.currentMode = defaultMode
window.currentStatus = statusType.offline
window.lastInfo = {
  needRestore: false,
  cursor: null,
  scroll: null,
  edit: {
    scroll: {
      left: null,
      top: null
    },
    cursor: {
      line: null,
      ch: null
    },
    selections: null
  },
  view: {
    scroll: {
      left: null,
      top: null
    }
  },
  history: null
}
window.personalInfo = {}
window.onlineUsers = []
window.fileTypes = {
  'pl': 'perl',
  'cgi': 'perl',
  'js': 'javascript',
  'php': 'php',
  'sh': 'bash',
  'rb': 'ruby',
  'html': 'html',
  'py': 'python'
}
// editor settings
var textit = document.getElementById('textit')
if (!textit) throw new Error('There was no textit area!')
window.editor = CodeMirror.fromTextArea(textit, {
  mode: defaultEditorMode,
  backdrop: defaultEditorMode,
  keyMap: 'sublime',
  viewportMargin: viewportMargin,
  styleActiveLine: true,
  lineNumbers: true,
  lineWrapping: true,
  showCursorWhenSelecting: true,
  highlightSelectionMatches: true,
  indentUnit: 4,
  continueComments: 'Enter',
  theme: 'one-dark',
  inputStyle: 'textarea',
  matchBrackets: true,
  autoCloseBrackets: true,
  matchTags: {
    bothTags: true
  },
  autoCloseTags: true,
  foldGutter: true,
  gutters: ['CodeMirror-linenumbers', 'authorship-gutters', 'CodeMirror-foldgutter'],
  extraKeys: defaultExtraKeys,
  flattenSpans: true,
  addModeClass: true,
  readOnly: true,
  autoRefresh: true,
  otherCursors: true,
  placeholder: "← Start by entering a title here\n===\nVisit /features if you don't know what to do.\nHappy hacking :)"
})
var inlineAttach = window.inlineAttachment.editors.codemirror4.attach(editor)
defaultTextHeight = parseInt($('.CodeMirror').css('line-height'))
var statusBarTemplate = null
var statusBar = null
var statusCursor = null
var statusFile = null
var statusIndicators = null
var statusLength = null
var statusTheme = null
var statusSpellcheck = null
function getStatusBarTemplate (callback) {
  $.get(serverurl + '/views/statusbar.html', function (template) {
    statusBarTemplate = template
    if (callback) callback()
  })
}
getStatusBarTemplate()
function addStatusBar () {
  if (!statusBarTemplate) {
    getStatusBarTemplate(addStatusBar)
    return
  }
  statusBar = $(statusBarTemplate)
  statusCursor = statusBar.find('.status-cursor')
  statusFile = statusBar.find('.status-file')
  statusIndicators = statusBar.find('.status-indicators')
  statusBar.find('.status-indent')
  statusBar.find('.status-keymap')
  statusLength = statusBar.find('.status-length')
  statusTheme = statusBar.find('.status-theme')
  statusSpellcheck = statusBar.find('.status-spellcheck')
  statusBar.find('.status-preferences')
  editor.addPanel(statusBar[0], {
    position: 'bottom'
  })
  setIndent()
  setKeymap()
  setTheme()
  setSpellcheck()
  setPreferences()
}
function setIndent () {
  var cookieIndentType = Cookies.get('indent_type')
  var cookieTabSize = parseInt(Cookies.get('tab_size'))
  var cookieSpaceUnits = parseInt(Cookies.get('space_units'))
  if (cookieIndentType) {
    if (cookieIndentType === 'tab') {
      editor.setOption('indentWithTabs', true)
      if (cookieTabSize) { editor.setOption('indentUnit', cookieTabSize) }
    } else if (cookieIndentType === 'space') {
      editor.setOption('indentWithTabs', false)
      if (cookieSpaceUnits) { editor.setOption('indentUnit', cookieSpaceUnits) }
    }
  }
  if (cookieTabSize) { editor.setOption('tabSize', cookieTabSize) }
  var type = statusIndicators.find('.indent-type')
  var widthLabel = statusIndicators.find('.indent-width-label')
  var widthInput = statusIndicators.find('.indent-width-input')
  function setType () {
    if (editor.getOption('indentWithTabs')) {
      Cookies.set('indent_type', 'tab', {
        expires: 365
      })
      type.text('Tab Size:')
    } else {
      Cookies.set('indent_type', 'space', {
        expires: 365
      })
      type.text('Spaces:')
    }
  }
  setType()
  function setUnit () {
    var unit = editor.getOption('indentUnit')
    if (editor.getOption('indentWithTabs')) {
      Cookies.set('tab_size', unit, {
        expires: 365
      })
    } else {
      Cookies.set('space_units', unit, {
        expires: 365
      })
    }
    widthLabel.text(unit)
  }
  setUnit()
  type.click(function () {
    if (editor.getOption('indentWithTabs')) {
      editor.setOption('indentWithTabs', false)
      cookieSpaceUnits = parseInt(Cookies.get('space_units'))
      if (cookieSpaceUnits) { editor.setOption('indentUnit', cookieSpaceUnits) }
    } else {
      editor.setOption('indentWithTabs', true)
      cookieTabSize = parseInt(Cookies.get('tab_size'))
      if (cookieTabSize) {
        editor.setOption('indentUnit', cookieTabSize)
        editor.setOption('tabSize', cookieTabSize)
      }
    }
    setType()
    setUnit()
  })
  widthLabel.click(function () {
    if (widthLabel.is(':visible')) {
      widthLabel.addClass('hidden')
      widthInput.removeClass('hidden')
      widthInput.val(editor.getOption('indentUnit'))
      widthInput.select()
    } else {
      widthLabel.removeClass('hidden')
      widthInput.addClass('hidden')
    }
  })
  widthInput.on('change', function () {
    var val = parseInt(widthInput.val())
    if (!val) val = editor.getOption('indentUnit')
    if (val < 1) val = 1
    else if (val > 10) val = 10
    if (editor.getOption('indentWithTabs')) {
      editor.setOption('tabSize', val)
    }
    editor.setOption('indentUnit', val)
    setUnit()
  })
  widthInput.on('blur', function () {
    widthLabel.removeClass('hidden')
    widthInput.addClass('hidden')
  })
}
function setKeymap () {
  var cookieKeymap = Cookies.get('keymap')
  if (cookieKeymap) { editor.setOption('keyMap', cookieKeymap) }
  var label = statusIndicators.find('.ui-keymap-label')
  var sublime = statusIndicators.find('.ui-keymap-sublime')
  var emacs = statusIndicators.find('.ui-keymap-emacs')
  var vim = statusIndicators.find('.ui-keymap-vim')
  function setKeymapLabel () {
    var keymap = editor.getOption('keyMap')
    Cookies.set('keymap', keymap, {
      expires: 365
    })
    label.text(keymap)
    restoreOverrideEditorKeymap()
    setOverrideBrowserKeymap()
  }
  setKeymapLabel()
  sublime.click(function () {
    editor.setOption('keyMap', 'sublime')
    setKeymapLabel()
  })
  emacs.click(function () {
    editor.setOption('keyMap', 'emacs')
    setKeymapLabel()
  })
  vim.click(function () {
    editor.setOption('keyMap', 'vim')
    setKeymapLabel()
  })
}
function setTheme () {
  var cookieTheme = Cookies.get('theme')
  if (cookieTheme) {
    editor.setOption('theme', cookieTheme)
  }
  var themeToggle = statusTheme.find('.ui-theme-toggle')
  themeToggle.click(function () {
    var theme = editor.getOption('theme')
    if (theme === 'one-dark') {
      theme = 'default'
    } else {
      theme = 'one-dark'
    }
    editor.setOption('theme', theme)
    Cookies.set('theme', theme, {
      expires: 365
    })
    checkTheme()
  })
  function checkTheme () {
    var theme = editor.getOption('theme')
    if (theme === 'one-dark') {
      themeToggle.removeClass('active')
    } else {
      themeToggle.addClass('active')
    }
  }
  checkTheme()
}
function setSpellcheck () {
  var cookieSpellcheck = Cookies.get('spellcheck')
  if (cookieSpellcheck) {
    var mode = null
    if (cookieSpellcheck === 'true' || cookieSpellcheck === true) {
      mode = 'spell-checker'
    } else {
      mode = defaultEditorMode
    }
    if (mode && mode !== editor.getOption('mode')) {
      editor.setOption('mode', mode)
    }
  }
  var spellcheckToggle = statusSpellcheck.find('.ui-spellcheck-toggle')
  spellcheckToggle.click(function () {
    var mode = editor.getOption('mode')
    if (mode === defaultEditorMode) {
      mode = 'spell-checker'
    } else {
      mode = defaultEditorMode
    }
    if (mode && mode !== editor.getOption('mode')) {
      editor.setOption('mode', mode)
    }
    Cookies.set('spellcheck', (mode === 'spell-checker'), {
      expires: 365
    })
    checkSpellcheck()
  })
  function checkSpellcheck () {
    var mode = editor.getOption('mode')
    if (mode === defaultEditorMode) {
      spellcheckToggle.removeClass('active')
    } else {
      spellcheckToggle.addClass('active')
    }
  }
  checkSpellcheck()
  // workaround spellcheck might not activate beacuse the ajax loading
  /* eslint-disable camelcase */
  if (num_loaded < 2) {
    var spellcheckTimer = setInterval(function () {
      if (num_loaded >= 2) {
        if (editor.getOption('mode') === 'spell-checker') { editor.setOption('mode', 'spell-checker') }
        clearInterval(spellcheckTimer)
      }
    }, 100)
  }
  /* eslint-endable camelcase */
}
var jumpToAddressBarKeymapName = mac ? 'Cmd-L' : 'Ctrl-L'
var jumpToAddressBarKeymapValue = null
function resetEditorKeymapToBrowserKeymap () {
  var keymap = editor.getOption('keyMap')
  if (!jumpToAddressBarKeymapValue) {
    jumpToAddressBarKeymapValue = CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName]
    delete CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName]
  }
}
function restoreOverrideEditorKeymap () {
  var keymap = editor.getOption('keyMap')
  if (jumpToAddressBarKeymapValue) {
    CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] = jumpToAddressBarKeymapValue
    jumpToAddressBarKeymapValue = null
  }
}
function setOverrideBrowserKeymap () {
  var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]')
  if (overrideBrowserKeymap.is(':checked')) {
    Cookies.set('preferences-override-browser-keymap', true, {
      expires: 365
    })
    restoreOverrideEditorKeymap()
  } else {
    Cookies.remove('preferences-override-browser-keymap')
    resetEditorKeymapToBrowserKeymap()
  }
}
function setPreferences () {
  var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]')
  var cookieOverrideBrowserKeymap = Cookies.get('preferences-override-browser-keymap')
  if (cookieOverrideBrowserKeymap && cookieOverrideBrowserKeymap === 'true') {
    overrideBrowserKeymap.prop('checked', true)
  } else {
    overrideBrowserKeymap.prop('checked', false)
  }
  setOverrideBrowserKeymap()
  overrideBrowserKeymap.change(function () {
    setOverrideBrowserKeymap()
  })
}
var selection = null
function updateStatusBar () {
  if (!statusBar) return
  var cursor = editor.getCursor()
  var cursorText = 'Line ' + (cursor.line + 1) + ', Columns ' + (cursor.ch + 1)
  if (selection) {
    var anchor = selection.anchor
    var head = selection.head
    var start = head.line <= anchor.line ? head : anchor
    var end = head.line >= anchor.line ? head : anchor
    var selectionText = ' — Selected '
    var selectionCharCount = Math.abs(head.ch - anchor.ch)
        // borrow from brackets EditorStatusBar.js
    if (start.line !== end.line) {
      var lines = end.line - start.line + 1
      if (end.ch === 0) {
        lines--
      }
      selectionText += lines + ' lines'
    } else if (selectionCharCount > 0) { selectionText += selectionCharCount + ' columns' }
    if (start.line !== end.line || selectionCharCount > 0) { cursorText += selectionText }
  }
  statusCursor.text(cursorText)
  var fileText = ' — ' + editor.lineCount() + ' Lines'
  statusFile.text(fileText)
  var docLength = editor.getValue().length
  statusLength.text('Length ' + docLength)
  if (docLength > (docmaxlength * 0.95)) {
    statusLength.css('color', 'red')
    statusLength.attr('title', 'Your almost reach note max length limit.')
  } else if (docLength > (docmaxlength * 0.8)) {
    statusLength.css('color', 'orange')
    statusLength.attr('title', 'You nearly fill the note, consider to make more pieces.')
  } else {
    statusLength.css('color', 'white')
    statusLength.attr('title', 'You could write up to ' + docmaxlength + ' characters in this note.')
  }
}
// ui vars
window.ui = {
  spinner: $('.ui-spinner'),
  content: $('.ui-content'),
  toolbar: {
    shortStatus: $('.ui-short-status'),
    status: $('.ui-status'),
    new: $('.ui-new'),
    publish: $('.ui-publish'),
    extra: {
      revision: $('.ui-extra-revision'),
      slide: $('.ui-extra-slide')
    },
    download: {
      markdown: $('.ui-download-markdown'),
      html: $('.ui-download-html'),
      rawhtml: $('.ui-download-raw-html'),
      pdf: $('.ui-download-pdf-beta')
    },
    export: {
      dropbox: $('.ui-save-dropbox'),
      googleDrive: $('.ui-save-google-drive'),
      gist: $('.ui-save-gist'),
      snippet: $('.ui-save-snippet')
    },
    import: {
      dropbox: $('.ui-import-dropbox'),
      googleDrive: $('.ui-import-google-drive'),
      gist: $('.ui-import-gist'),
      snippet: $('.ui-import-snippet'),
      clipboard: $('.ui-import-clipboard')
    },
    mode: $('.ui-mode'),
    edit: $('.ui-edit'),
    view: $('.ui-view'),
    both: $('.ui-both'),
    uploadImage: $('.ui-upload-image')
  },
  infobar: {
    lastchange: $('.ui-lastchange'),
    lastchangeuser: $('.ui-lastchangeuser'),
    nolastchangeuser: $('.ui-no-lastchangeuser'),
    permission: {
      permission: $('.ui-permission'),
      label: $('.ui-permission-label'),
      freely: $('.ui-permission-freely'),
      editable: $('.ui-permission-editable'),
      locked: $('.ui-permission-locked'),
      private: $('.ui-permission-private'),
      limited: $('.ui-permission-limited'),
      protected: $('.ui-permission-protected')
    },
    delete: $('.ui-delete-note')
  },
  toc: {
    toc: $('.ui-toc'),
    affix: $('.ui-affix-toc'),
    label: $('.ui-toc-label'),
    dropdown: $('.ui-toc-dropdown')
  },
  area: {
    edit: $('.ui-edit-area'),
    view: $('.ui-view-area'),
    codemirror: $('.ui-edit-area .CodeMirror'),
    codemirrorScroll: $('.ui-edit-area .CodeMirror .CodeMirror-scroll'),
    codemirrorSizer: $('.ui-edit-area .CodeMirror .CodeMirror-sizer'),
    codemirrorSizerInner: $('.ui-edit-area .CodeMirror .CodeMirror-sizer > div'),
    markdown: $('.ui-view-area .markdown-body'),
    resize: {
      handle: $('.ui-resizable-handle'),
      syncToggle: $('.ui-sync-toggle')
    }
  },
  modal: {
    snippetImportProjects: $('#snippetImportModalProjects'),
    snippetImportSnippets: $('#snippetImportModalSnippets'),
    revision: $('#revisionModal')
  }
}
// page actions
var opts = {
  lines: 11, // The number of lines to draw
  length: 20, // The length of each line
  width: 2, // The line thickness
  radius: 30, // The radius of the inner circle
  corners: 0, // Corner roundness (0..1)
  rotate: 0, // The rotation offset
  direction: 1, // 1: clockwise, -1: counterclockwise
  color: '#000', // #rgb or #rrggbb or array of colors
  speed: 1.1, // Rounds per second
  trail: 60, // Afterglow percentage
  shadow: false, // Whether to render a shadow
  hwaccel: true, // Whether to use hardware acceleration
  className: 'spinner', // The CSS class to assign to the spinner
  zIndex: 2e9, // The z-index (defaults to 2000000000)
  top: '50%', // Top position relative to parent
  left: '50%' // Left position relative to parent
}
/* eslint-disable no-unused-vars */
var spinner = new Spinner(opts).spin(ui.spinner[0])
/* eslint-enable no-unused-vars */
// idle
var idle = new Idle({
  onAway: function () {
    idle.isAway = true
    emitUserStatus()
    updateOnlineStatus()
  },
  onAwayBack: function () {
    idle.isAway = false
    emitUserStatus()
    updateOnlineStatus()
    setHaveUnreadChanges(false)
    updateTitleReminder()
  },
  awayTimeout: idleTime
})
ui.area.codemirror.on('touchstart', function () {
  idle.onActive()
})
var haveUnreadChanges = false
function setHaveUnreadChanges (bool) {
  if (!window.loaded) return
  if (bool && (idle.isAway || Visibility.hidden())) {
    haveUnreadChanges = true
  } else if (!bool && !idle.isAway && !Visibility.hidden()) {
    haveUnreadChanges = false
  }
}
function updateTitleReminder () {
  if (!window.loaded) return
  if (haveUnreadChanges) {
    document.title = '• ' + renderTitle(ui.area.markdown)
  } else {
    document.title = renderTitle(ui.area.markdown)
  }
}
function setRefreshModal (status) {
  $('#refreshModal').modal('show')
  $('#refreshModal').find('.modal-body > div').hide()
  $('#refreshModal').find('.' + status).show()
}
function setNeedRefresh () {
  window.needRefresh = true
  editor.setOption('readOnly', true)
  socket.disconnect()
  showStatus(statusType.offline)
}
setloginStateChangeEvent(function () {
  setRefreshModal('user-state-changed')
  setNeedRefresh()
})
// visibility
var wasFocus = false
Visibility.change(function (e, state) {
  var hidden = Visibility.hidden()
  if (hidden) {
    if (editorHasFocus()) {
      wasFocus = true
      editor.getInputField().blur()
    }
  } else {
    if (wasFocus) {
      if (!window.visibleXS) {
        editor.focus()
        editor.refresh()
      }
      wasFocus = false
    }
    setHaveUnreadChanges(false)
  }
  updateTitleReminder()
})
// when page ready
$(document).ready(function () {
  idle.checkAway()
  checkResponsive()
    // if in smaller screen, we don't need advanced scrollbar
  var scrollbarStyle
  if (window.visibleXS) {
    scrollbarStyle = 'native'
  } else {
    scrollbarStyle = 'overlay'
  }
  if (scrollbarStyle !== editor.getOption('scrollbarStyle')) {
    editor.setOption('scrollbarStyle', scrollbarStyle)
    clearMap()
  }
  checkEditorStyle()
    /* we need this only on touch devices */
  if (window.isTouchDevice) {
        /* cache dom references */
    var $body = jQuery('body')
        /* bind events */
    $(document)
            .on('focus', 'textarea, input', function () {
              $body.addClass('fixfixed')
            })
            .on('blur', 'textarea, input', function () {
              $body.removeClass('fixfixed')
            })
  }
    // showup
  $().showUp('.navbar', {
    upClass: 'navbar-hide',
    downClass: 'navbar-show'
  })
    // tooltip
  $('[data-toggle="tooltip"]').tooltip()
    // shortcuts
    // allow on all tags
  key.filter = function (e) { return true }
  key('ctrl+alt+e', function (e) {
    changeMode(modeType.edit)
  })
  key('ctrl+alt+v', function (e) {
    changeMode(modeType.view)
  })
  key('ctrl+alt+b', function (e) {
    changeMode(modeType.both)
  })
    // toggle-dropdown
  $(document).on('click', '.toggle-dropdown .dropdown-menu', function (e) {
    e.stopPropagation()
  })
})
// when page resize
$(window).resize(function () {
  checkLayout()
  checkEditorStyle()
  checkTocStyle()
  checkCursorMenu()
  windowResize()
})
// when page unload
$(window).on('unload', function () {
    // updateHistoryInner();
})
$(window).on('error', function () {
    // setNeedRefresh();
})
setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown)
function autoSyncscroll () {
  if (editorHasFocus()) {
    syncScrollToView()
  } else {
    syncScrollToEdit()
  }
}
var windowResizeDebounce = 200
var windowResize = _.debounce(windowResizeInner, windowResizeDebounce)
function windowResizeInner (callback) {
  checkLayout()
  checkResponsive()
  checkEditorStyle()
  checkTocStyle()
  checkCursorMenu()
    // refresh editor
  if (window.loaded) {
    if (editor.getOption('scrollbarStyle') === 'native') {
      setTimeout(function () {
        clearMap()
        autoSyncscroll()
        updateScrollspy()
        if (callback && typeof callback === 'function') { callback() }
      }, 1)
    } else {
            // force it load all docs at once to prevent scroll knob blink
      editor.setOption('viewportMargin', Infinity)
      setTimeout(function () {
        clearMap()
        autoSyncscroll()
        editor.setOption('viewportMargin', viewportMargin)
                // add or update user cursors
        for (var i = 0; i < window.onlineUsers.length; i++) {
          if (window.onlineUsers[i].id !== window.personalInfo.id) { buildCursor(window.onlineUsers[i]) }
        }
        updateScrollspy()
        if (callback && typeof callback === 'function') { callback() }
      }, 1)
    }
  }
}
function checkLayout () {
  var navbarHieght = $('.navbar').outerHeight()
  $('body').css('padding-top', navbarHieght + 'px')
}
function editorHasFocus () {
  return $(editor.getInputField()).is(':focus')
}
// 768-792px have a gap
function checkResponsive () {
  window.visibleXS = $('.visible-xs').is(':visible')
  window.visibleSM = $('.visible-sm').is(':visible')
  window.visibleMD = $('.visible-md').is(':visible')
  window.visibleLG = $('.visible-lg').is(':visible')
  if (window.visibleXS && window.currentMode === modeType.both) {
    if (editorHasFocus()) { changeMode(modeType.edit) } else { changeMode(modeType.view) }
  }
  emitUserStatus()
}
var lastEditorWidth = 0
var previousFocusOnEditor = null
function checkEditorStyle () {
  var desireHeight = statusBar ? (ui.area.edit.height() - statusBar.outerHeight()) : ui.area.edit.height()
    // set editor height and min height based on scrollbar style and mode
  var scrollbarStyle = editor.getOption('scrollbarStyle')
  if (scrollbarStyle === 'overlay' || window.currentMode === modeType.both) {
    ui.area.codemirrorScroll.css('height', desireHeight + 'px')
    ui.area.codemirrorScroll.css('min-height', '')
    checkEditorScrollbar()
  } else if (scrollbarStyle === 'native') {
    ui.area.codemirrorScroll.css('height', '')
    ui.area.codemirrorScroll.css('min-height', desireHeight + 'px')
  }
    // workaround editor will have wrong doc height when editor height changed
  editor.setSize(null, ui.area.edit.height())
    // make editor resizable
  if (!ui.area.resize.handle.length) {
    ui.area.edit.resizable({
      handles: 'e',
      maxWidth: $(window).width() * 0.7,
      minWidth: $(window).width() * 0.2,
      create: function (e, ui) {
        $(this).parent().on('resize', function (e) {
          e.stopPropagation()
        })
      },
      start: function (e) {
        editor.setOption('viewportMargin', Infinity)
      },
      resize: function (e) {
        ui.area.resize.syncToggle.stop(true, true).show()
        checkTocStyle()
      },
      stop: function (e) {
        lastEditorWidth = ui.area.edit.width()
                // workaround that scroll event bindings
        window.preventSyncScrollToView = 2
        window.preventSyncScrollToEdit = true
        editor.setOption('viewportMargin', viewportMargin)
        if (editorHasFocus()) {
          windowResizeInner(function () {
            ui.area.codemirrorScroll.scroll()
          })
        } else {
          windowResizeInner(function () {
            ui.area.view.scroll()
          })
        }
        checkEditorScrollbar()
      }
    })
    ui.area.resize.handle = $('.ui-resizable-handle')
  }
  if (!ui.area.resize.syncToggle.length) {
    ui.area.resize.syncToggle = $('')
    ui.area.resize.syncToggle.hover(function () {
      previousFocusOnEditor = editorHasFocus()
    }, function () {
      previousFocusOnEditor = null
    })
    ui.area.resize.syncToggle.click(function () {
      window.syncscroll = !window.syncscroll
      checkSyncToggle()
    })
    ui.area.resize.handle.append(ui.area.resize.syncToggle)
    ui.area.resize.syncToggle.hide()
    ui.area.resize.handle.hover(function () {
      ui.area.resize.syncToggle.stop(true, true).delay(200).fadeIn(100)
    }, function () {
      ui.area.resize.syncToggle.stop(true, true).delay(300).fadeOut(300)
    })
  }
}
function checkSyncToggle () {
  if (window.syncscroll) {
    if (previousFocusOnEditor) {
      window.preventSyncScrollToView = false
      syncScrollToView()
    } else {
      window.preventSyncScrollToEdit = false
      syncScrollToEdit()
    }
    ui.area.resize.syncToggle.find('i').removeClass('fa-unlink').addClass('fa-link')
  } else {
    ui.area.resize.syncToggle.find('i').removeClass('fa-link').addClass('fa-unlink')
  }
}
var checkEditorScrollbar = _.debounce(function () {
  editor.operation(checkEditorScrollbarInner)
}, 50)
function checkEditorScrollbarInner () {
    // workaround simple scroll bar knob
    // will get wrong position when editor height changed
  var scrollInfo = editor.getScrollInfo()
  editor.scrollTo(null, scrollInfo.top - 1)
  editor.scrollTo(null, scrollInfo.top)
}
function checkTocStyle () {
    // toc right
  var paddingRight = parseFloat(ui.area.markdown.css('padding-right'))
  var right = ($(window).width() - (ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - paddingRight))
  ui.toc.toc.css('right', right + 'px')
    // affix toc left
  var newbool
  var rightMargin = (ui.area.markdown.parent().outerWidth() - ui.area.markdown.outerWidth()) / 2
    // for ipad or wider device
  if (rightMargin >= 133) {
    newbool = true
    var affixLeftMargin = (ui.toc.affix.outerWidth() - ui.toc.affix.width()) / 2
    var left = ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - affixLeftMargin
    ui.toc.affix.css('left', left + 'px')
    ui.toc.affix.css('width', rightMargin + 'px')
  } else {
    newbool = false
  }
    // toc scrollspy
  ui.toc.toc.removeClass('scrollspy-body, scrollspy-view')
  ui.toc.affix.removeClass('scrollspy-body, scrollspy-view')
  if (window.currentMode === modeType.both) {
    ui.toc.toc.addClass('scrollspy-view')
    ui.toc.affix.addClass('scrollspy-view')
  } else if (window.currentMode !== modeType.both && !newbool) {
    ui.toc.toc.addClass('scrollspy-body')
    ui.toc.affix.addClass('scrollspy-body')
  } else {
    ui.toc.toc.addClass('scrollspy-view')
    ui.toc.affix.addClass('scrollspy-body')
  }
  if (newbool !== enoughForAffixToc) {
    enoughForAffixToc = newbool
    generateScrollspy()
  }
}
function showStatus (type, num) {
  window.currentStatus = type
  var shortStatus = ui.toolbar.shortStatus
  var status = ui.toolbar.status
  var label = $('')
  var fa = $('')
  var msg = ''
  var shortMsg = ''
  shortStatus.html('')
  status.html('')
  switch (window.currentStatus) {
    case statusType.connected:
      label.addClass(statusType.connected.label)
      fa.addClass(statusType.connected.fa)
      msg = statusType.connected.msg
      break
    case statusType.online:
      label.addClass(statusType.online.label)
      fa.addClass(statusType.online.fa)
      shortMsg = num
      msg = num + ' ' + statusType.online.msg
      break
    case statusType.offline:
      label.addClass(statusType.offline.label)
      fa.addClass(statusType.offline.fa)
      msg = statusType.offline.msg
      break
  }
  label.append(fa)
  var shortLabel = label.clone()
  shortLabel.append(' ' + shortMsg)
  shortStatus.append(shortLabel)
  label.append(' ' + msg)
  status.append(label)
}
function toggleMode () {
  switch (window.currentMode) {
    case modeType.edit:
      changeMode(modeType.view)
      break
    case modeType.view:
      changeMode(modeType.edit)
      break
    case modeType.both:
      changeMode(modeType.view)
      break
  }
}
var lastMode = null
function changeMode (type) {
    // lock navbar to prevent it hide after changeMode
  lockNavbar()
  saveInfo()
  if (type) {
    lastMode = window.currentMode
    window.currentMode = type
  }
  var responsiveClass = 'col-lg-6 col-md-6 col-sm-6'
  var scrollClass = 'ui-scrollable'
  ui.area.codemirror.removeClass(scrollClass)
  ui.area.edit.removeClass(responsiveClass)
  ui.area.view.removeClass(scrollClass)
  ui.area.view.removeClass(responsiveClass)
  switch (window.currentMode) {
    case modeType.edit:
      ui.area.edit.show()
      ui.area.view.hide()
      if (!window.editShown) {
        editor.refresh()
        window.editShown = true
      }
      break
    case modeType.view:
      ui.area.edit.hide()
      ui.area.view.show()
      break
    case modeType.both:
      ui.area.codemirror.addClass(scrollClass)
      ui.area.edit.addClass(responsiveClass).show()
      ui.area.view.addClass(scrollClass)
      ui.area.view.show()
      break
  }
    // save mode to url
  if (history.replaceState && window.loaded) history.replaceState(null, '', serverurl + '/' + noteid + '?' + window.currentMode.name)
  if (window.currentMode === modeType.view) {
    editor.getInputField().blur()
  }
  if (window.currentMode === modeType.edit || window.currentMode === modeType.both) {
    ui.toolbar.uploadImage.fadeIn()
        // add and update status bar
    if (!statusBar) {
      addStatusBar()
      updateStatusBar()
    }
        // work around foldGutter might not init properly
    editor.setOption('foldGutter', false)
    editor.setOption('foldGutter', true)
  } else {
    ui.toolbar.uploadImage.fadeOut()
  }
  if (window.currentMode !== modeType.edit) {
    $(document.body).css('background-color', 'white')
    updateView()
  } else {
    $(document.body).css('background-color', ui.area.codemirror.css('background-color'))
  }
    // check resizable editor style
  if (window.currentMode === modeType.both) {
    if (lastEditorWidth > 0) { ui.area.edit.css('width', lastEditorWidth + 'px') } else { ui.area.edit.css('width', '') }
    ui.area.resize.handle.show()
  } else {
    ui.area.edit.css('width', '')
    ui.area.resize.handle.hide()
  }
  windowResizeInner()
  restoreInfo()
  if (lastMode === modeType.view && window.currentMode === modeType.both) {
    window.preventSyncScrollToView = 2
    syncScrollToEdit(null, true)
  }
  if (lastMode === modeType.edit && window.currentMode === modeType.both) {
    window.preventSyncScrollToEdit = 2
    syncScrollToView(null, true)
  }
  if (lastMode === modeType.both && window.currentMode !== modeType.both) {
    window.preventSyncScrollToView = false
    window.preventSyncScrollToEdit = false
  }
  if (lastMode !== modeType.edit && window.currentMode === modeType.edit) {
    editor.refresh()
  }
  $(document.body).scrollspy('refresh')
  ui.area.view.scrollspy('refresh')
  ui.toolbar.both.removeClass('active')
  ui.toolbar.edit.removeClass('active')
  ui.toolbar.view.removeClass('active')
  var modeIcon = ui.toolbar.mode.find('i')
  modeIcon.removeClass('fa-pencil').removeClass('fa-eye')
  if (ui.area.edit.is(':visible') && ui.area.view.is(':visible')) { // both
    ui.toolbar.both.addClass('active')
    modeIcon.addClass('fa-eye')
  } else if (ui.area.edit.is(':visible')) { // edit
    ui.toolbar.edit.addClass('active')
    modeIcon.addClass('fa-eye')
  } else if (ui.area.view.is(':visible')) { // view
    ui.toolbar.view.addClass('active')
    modeIcon.addClass('fa-pencil')
  }
  unlockNavbar()
}
function lockNavbar () {
  $('.navbar').addClass('locked')
}
var unlockNavbar = _.debounce(function () {
  $('.navbar').removeClass('locked')
}, 200)
function showMessageModal (title, header, href, text, success) {
  var modal = $('.message-modal')
  modal.find('.modal-title').html(title)
  modal.find('.modal-body h5').html(header)
  if (href) { modal.find('.modal-body a').attr('href', href).text(text) } else { modal.find('.modal-body a').removeAttr('href').text(text) }
  modal.find('.modal-footer button').removeClass('btn-default btn-success btn-danger')
  if (success) { modal.find('.modal-footer button').addClass('btn-success') } else { modal.find('.modal-footer button').addClass('btn-danger') }
  modal.modal('show')
}
// check if dropbox app key is set and load scripts
if (DROPBOX_APP_KEY) {
  $('