 toArray = (obj, cond=null) ->
   if _.isArray(cond ? obj) then obj else [obj]
 
+
+# Main
+# -------------------------
+getVimState = (args...) ->
+  [editor, file, callback] = []
+  switch args.length
+    when 1 then [callback] = args
+    when 2 then [file, callback] = args
+
+  waitsForPromise ->
+    atom.packages.activatePackage(packageName)
+
+  waitsForPromise ->
+    file = atom.project.resolvePath(file) if file
+    atom.workspace.open(file).then (e) ->
+      editor = e
+
+  runs ->
+    pack = atom.packages.getActivePackage(packageName)
+    main = pack.mainModule
+    vimState = main.getEditorState(editor)
+    {editorElement} = vimState
+    editorElement.addEventListener 'keydown', (e) ->
+      atom.keymaps.handleKeyboardEvent(e)
+
+    callback(vimState, new VimEditor(vimState))
+
 class VimEditor
   constructor: (@vimState) ->
     {@editor, @editorElement} = @vimState

    if mocked
      unmockPlatform(element)

  ensureOptionsOrdered = [
    'text', 'selectedText', 'selectedTextOrdered'
    'cursor', 'cursors', 'cursorBuffer'
    'register', 'numCursors', 'selectedScreenRange',
    'selectedBufferRange',
    'selectedBufferRangeOrdered',
    'selectedBufferRangeStartRow',
    'selectedBufferRangeEndRow',
    'selectionIsReversed',
    'scrollTop',
    'mode',
  ]
  ensure: (args...) =>
    switch args.length
      when 1 then [options] = args
      when 2 then [keystroke, options] = args
    @validateOptions(options, ensureOptionsOrdered, 'Invalid ensure option')
    # Input
    unless _.isEmpty(keystroke)
      @keystroke(keystroke, {element: @editorElement})

    for name in ensureOptionsOrdered when options[name]?
      method = 'ensure' + _.capitalize(_.camelize(name))
      this[method](options[name])

  ensureText: (text) ->
    expect(@editor.getText()).toEqual(text)

  ensureSelectedText: (text) ->
    selections = @editor.getSelections()
    texts = (s.getText() for s in selections)
    expect(texts).toEqual(toArray(text))

  ensureSelectedTextOrdered: (text) ->
    selections = @editor.getSelectionsOrderedByBufferPosition()
    texts = (s.getText() for s in selections)
    expect(texts).toEqual(toArray(text))

  ensureCursor: (cursor) ->
    cursor = if cursor instanceof Point or not _.isArray(cursor[0])
      [cursor]
    else
      cursor
    points = @editor.getCursorScreenPositions()
    expect(points).toEqual(toArray(cursor, cursor))

  ensureCursors: (cursors) ->
    points = @editor.getCursorScreenPositions()
    expect(points).toEqual(cursors)

  ensureCursorBuffer: (cursor) ->
    points = @editor.getCursorBufferPositions()
    expect(points).toEqual(toArray(cursor, cursor[0]))

  ensureRegister: (register) ->
    if _.isObject(register)
      for name, value of register
        reg = @vimState.register.get(name)
        for prop, _value of value
          expect(reg[prop]).toEqual(_value)
    else
      expect(@vimState.register.get('"').text).toBe register

  ensureNumCursors: (number) ->
    expect(@editor.getCursors()).toHaveLength number

  ensureSelectedScreenRange: (range) ->
    actual = @editor.getSelectedScreenRanges()
    expected = toArray(range, range[0][0])
    expect(actual).toEqual(expected)

  ensureSelectedBufferRange: (range) ->
    if (range instanceof Range) or
        (not (range[0] instanceof Range)) and (not _.isArray(range[0][0]))
      range = [range]
    actual = @editor.getSelectedBufferRanges()
    expect(actual).toEqual(range)

  ensureSelectedBufferRangeOrdered: (range) ->
    if (range instanceof Range) or
        (not (range[0] instanceof Range)) and (not _.isArray(range[0][0]))
      range = [range]
    actual = @editor.getSelectionsOrderedByBufferPosition().map (e) -> e.getBufferRange()
    expect(actual).toEqual(range)

  ensureSelectedBufferRangeStartRow: (row) ->
    {start} = @editor.getSelectedBufferRange()
    expect(start.row).toEqual row

  ensureSelectedBufferRangeEndRow: (row) ->
    {end} = @editor.getSelectedBufferRange()
    expect(end.row).toEqual row

  ensureSelectionIsReversed: (reversed) ->
    actual = @editor.getLastSelection().isReversed()
    expect(actual).toBe(reversed)

  ensureScrollTop: (scrollTop) ->
    actual = @editorElement.getScrollTop()
    expect(actual).toEqual scrollTop

  ensureMode: (mode) ->
    mode = toArray(mode)
    expect(@vimState.isMode(mode...)).toBe(true)

    mode[0] = "#{mode[0]}-mode"
    mode = mode.filter((m) -> m)
    expect(@editorElement.classList.contains('vim-mode-plus')).toBe(true)
    for m in mode
      expect(@editorElement.classList.contains(m)).toBe(true)
    shouldNotContainClasses = _.difference(supportedModeClass, mode)
    for m in shouldNotContainClasses
      expect(@editorElement.classList.contains(m)).toBe(false)

dispatch = (target, command) ->
  atom.commands.dispatch(target, command)

module.exports = {getVimState, getView, dispatch}

    'text', 'cursor', 'cursorBuffer', 'addCursor', 'addCursors', 'register'
    'selectedBufferRange'
  ]
  # Public
  set: (options) =>
    @validateOptions(options, setOptionsOrdered, 'Invalid set options')
    for name in setOptionsOrdered when options[name]?
      method = 'set' + _.capitalize(_.camelize(name))
      this[method](options[name])

  setText: (text) ->
    @editor.setText(text)

  setCursor: (point) ->
    @editor.setCursorScreenPosition(point)

  setCursorBuffer: (cursor) ->
    @editor.setCursorBufferPosition(cursor)

  setAddCursor: (point) ->
    @editor.addCursorAtBufferPosition(point)

  setAddCursors: (points) ->
    @setAddCursor(point) for point in points

  setRegister: (register) ->
    if _.isObject(register)
      for name, value of register
        @vimState.register.set(name, value)
    else
      @vimState.register.set '"', text: register

  setSelectedBufferRange: (range) ->
    @editor.setSelectedBufferRange(range)

  ensureOptionsOrdered = [
    'text', 'selectedText', 'selectedTextOrdered'
    'cursor', 'cursors', 'cursorBuffer'
    'register', 'numCursors', 'selectedScreenRange',
    'selectedBufferRange',
    'selectedBufferRangeOrdered',
    'selectedBufferRangeStartRow',
    'selectedBufferRangeEndRow',
    'selectionIsReversed',
    'scrollTop',
    'mode',
  ]
  # Public
  ensure: (args...) =>
    switch args.length
      when 1 then [options] = args
      when 2 then [keystroke, options] = args
    @validateOptions(options, ensureOptionsOrdered, 'Invalid ensure option')
    # Input
    unless _.isEmpty(keystroke)
      @keystroke(keystroke, {element: @editorElement})

    for name in ensureOptionsOrdered when options[name]?
      method = 'ensure' + _.capitalize(_.camelize(name))
      this[method](options[name])

  ensureText: (text) ->
    expect(@editor.getText()).toEqual(text)

  ensureSelectedText: (text) ->
    selections = @editor.getSelections()
    texts = (s.getText() for s in selections)
    expect(texts).toEqual(toArray(text))

  ensureSelectedTextOrdered: (text) ->
    selections = @editor.getSelectionsOrderedByBufferPosition()
    texts = (s.getText() for s in selections)
    expect(texts).toEqual(toArray(text))

  ensureCursor: (cursor) ->
    cursor = if cursor instanceof Point or not _.isArray(cursor[0])
      [cursor]
    else
      cursor
    points = @editor.getCursorScreenPositions()
    expect(points).toEqual(toArray(cursor, cursor))

  ensureCursors: (cursors) ->
    points = @editor.getCursorScreenPositions()
    expect(points).toEqual(cursors)

  ensureCursorBuffer: (cursor) ->
    points = @editor.getCursorBufferPositions()
    expect(points).toEqual(toArray(cursor, cursor[0]))

  ensureRegister: (register) ->
    if _.isObject(register)
      for name, value of register
        reg = @vimState.register.get(name)
        for prop, _value of value
          expect(reg[prop]).toEqual(_value)
    else
      expect(@vimState.register.get('"').text).toBe register

  ensureNumCursors: (number) ->
    expect(@editor.getCursors()).toHaveLength number

  ensureSelectedScreenRange: (range) ->
    actual = @editor.getSelectedScreenRanges()
    expected = toArray(range, range[0][0])
    expect(actual).toEqual(expected)

  ensureSelectedBufferRange: (range) ->
    if (range instanceof Range) or
        (not (range[0] instanceof Range)) and (not _.isArray(range[0][0]))
      range = [range]
    actual = @editor.getSelectedBufferRanges()
    expect(actual).toEqual(range)

  ensureSelectedBufferRangeOrdered: (range) ->
    if (range instanceof Range) or
        (not (range[0] instanceof Range)) and (not _.isArray(range[0][0]))
      range = [range]
    actual = @editor.getSelectionsOrderedByBufferPosition().map (e) -> e.getBufferRange()
    expect(actual).toEqual(range)

  ensureSelectedBufferRangeStartRow: (row) ->
    {start} = @editor.getSelectedBufferRange()
    expect(start.row).toEqual row

  ensureSelectedBufferRangeEndRow: (row) ->
    {end} = @editor.getSelectedBufferRange()
    expect(end.row).toEqual row

  ensureSelectionIsReversed: (reversed) ->
    actual = @editor.getLastSelection().isReversed()
    expect(actual).toBe(reversed)

  ensureScrollTop: (scrollTop) ->
    actual = @editorElement.getScrollTop()
    expect(actual).toEqual scrollTop

  ensureMode: (mode) ->
    mode = toArray(mode)
    expect(@vimState.isMode(mode...)).toBe(true)

    mode[0] = "#{mode[0]}-mode"
    mode = mode.filter((m) -> m)
    expect(@editorElement.classList.contains('vim-mode-plus')).toBe(true)
    for m in mode
      expect(@editorElement.classList.contains(m)).toBe(true)
    shouldNotContainClasses = _.difference(supportedModeClass, mode)
    for m in shouldNotContainClasses
      expect(@editorElement.classList.contains(m)).toBe(false)

  # Public
  keystroke: (keys, {element}={}) =>
    # keys must be String or Array
    # Not support Object for keys to avoid ambiguity.
