Project = require 'project'
Buffer = require 'text-buffer'
EditSession = require 'edit-session'

describe "LanguageMode", ->
  [editSession, buffer, languageMode] = []

  afterEach ->
    editSession.destroy()

  describe "javascript", ->
    beforeEach ->
      atom.activatePackage('javascript-tmbundle', sync: true)
      editSession = project.open('sample.js', autoIndent: false)
      {buffer, languageMode} = editSession

    describe ".minIndentLevelForRowRange(startRow, endRow)", ->
      it "returns the minimum indent level for the given row range", ->
        expect(languageMode.minIndentLevelForRowRange(4, 7)).toBe 2
        expect(languageMode.minIndentLevelForRowRange(5, 7)).toBe 2
        expect(languageMode.minIndentLevelForRowRange(5, 6)).toBe 3
        expect(languageMode.minIndentLevelForRowRange(9, 11)).toBe 1
        expect(languageMode.minIndentLevelForRowRange(10, 10)).toBe 0

    describe ".toggleLineCommentsForBufferRows(start, end)", ->
      it "comments/uncomments lines in the given range", ->
        languageMode.toggleLineCommentsForBufferRows(4, 7)
        expect(buffer.lineForRow(4)).toBe "    // while(items.length > 0) {"
        expect(buffer.lineForRow(5)).toBe "    //   current = items.shift();"
        expect(buffer.lineForRow(6)).toBe "    //   current < pivot ? left.push(current) : right.push(current);"
        expect(buffer.lineForRow(7)).toBe "    // }"

        languageMode.toggleLineCommentsForBufferRows(4, 5)
        expect(buffer.lineForRow(4)).toBe "    while(items.length > 0) {"
        expect(buffer.lineForRow(5)).toBe "      current = items.shift();"
        expect(buffer.lineForRow(6)).toBe "    //   current < pivot ? left.push(current) : right.push(current);"
        expect(buffer.lineForRow(7)).toBe "    // }"

    describe "fold suggestion", ->
      describe ".doesBufferRowStartFold(bufferRow)", ->
        it "returns true only when the buffer row starts a foldable region", ->
          expect(languageMode.doesBufferRowStartFold(0)).toBeTruthy()
          expect(languageMode.doesBufferRowStartFold(1)).toBeTruthy()
          expect(languageMode.doesBufferRowStartFold(2)).toBeFalsy()
          expect(languageMode.doesBufferRowStartFold(3)).toBeFalsy()

      describe ".rowRangeForCodeFoldAtBufferRow(bufferRow)", ->
        it "returns the start/end rows of the foldable region starting at the given row", ->
          expect(languageMode.rowRangeForCodeFoldAtBufferRow(0)).toEqual [0, 12]
          expect(languageMode.rowRangeForCodeFoldAtBufferRow(1)).toEqual [1, 9]
          expect(languageMode.rowRangeForCodeFoldAtBufferRow(2)).toBeNull()
          expect(languageMode.rowRangeForCodeFoldAtBufferRow(4)).toEqual [4, 7]

    describe "suggestedIndentForBufferRow", ->
      it "returns the suggested indentation based on auto-indent/outdent rules", ->
        expect(languageMode.suggestedIndentForBufferRow(0)).toBe 0
        expect(languageMode.suggestedIndentForBufferRow(1)).toBe 1
        expect(languageMode.suggestedIndentForBufferRow(2)).toBe 2
        expect(languageMode.suggestedIndentForBufferRow(9)).toBe 1

  describe "coffeescript", ->
    beforeEach ->
      atom.activatePackage('coffee-script-tmbundle', sync: true)
      editSession = project.open('coffee.coffee', autoIndent: false)
      {buffer, languageMode} = editSession

    describe ".toggleLineCommentsForBufferRows(start, end)", ->
      it "comments/uncomments lines in the given range", ->
        languageMode.toggleLineCommentsForBufferRows(4, 6)
        expect(buffer.lineForRow(4)).toBe "    # pivot = items.shift()"
        expect(buffer.lineForRow(5)).toBe "    # left = []"
        expect(buffer.lineForRow(6)).toBe "    # right = []"

        languageMode.toggleLineCommentsForBufferRows(4, 5)
        expect(buffer.lineForRow(4)).toBe "    pivot = items.shift()"
        expect(buffer.lineForRow(5)).toBe "    left = []"
        expect(buffer.lineForRow(6)).toBe "    # right = []"

      it "comments/uncomments lines when empty line", ->
        languageMode.toggleLineCommentsForBufferRows(4, 7)
        expect(buffer.lineForRow(4)).toBe "    # pivot = items.shift()"
        expect(buffer.lineForRow(5)).toBe "    # left = []"
        expect(buffer.lineForRow(6)).toBe "    # right = []"
        expect(buffer.lineForRow(7)).toBe "    # "

        languageMode.toggleLineCommentsForBufferRows(4, 5)
        expect(buffer.lineForRow(4)).toBe "    pivot = items.shift()"
        expect(buffer.lineForRow(5)).toBe "    left = []"
        expect(buffer.lineForRow(6)).toBe "    # right = []"
        expect(buffer.lineForRow(7)).toBe "    # "

    describe "fold suggestion", ->
      describe ".doesBufferRowStartFold(bufferRow)", ->
        it "returns true only when the buffer row starts a foldable region", ->
          expect(languageMode.doesBufferRowStartFold(0)).toBeTruthy()
          expect(languageMode.doesBufferRowStartFold(1)).toBeTruthy()
          expect(languageMode.doesBufferRowStartFold(2)).toBeFalsy()
          expect(languageMode.doesBufferRowStartFold(3)).toBeFalsy()
          expect(languageMode.doesBufferRowStartFold(19)).toBeTruthy()

      describe ".rowRangeForCodeFoldAtBufferRow(bufferRow)", ->
        it "returns the start/end rows of the foldable region starting at the given row", ->
          expect(languageMode.rowRangeForCodeFoldAtBufferRow(0)).toEqual [0, 20]
          expect(languageMode.rowRangeForCodeFoldAtBufferRow(1)).toEqual [1, 17]
          expect(languageMode.rowRangeForCodeFoldAtBufferRow(2)).toBeNull()
          expect(languageMode.rowRangeForCodeFoldAtBufferRow(19)).toEqual [19, 20]

  describe "css", ->
    beforeEach ->
      atom.activatePackage('css-tmbundle', sync: true)
      editSession = project.open('css.css', autoIndent: false)
      {buffer, languageMode} = editSession

    describe ".toggleLineCommentsForBufferRows(start, end)", ->
      it "comments/uncomments lines in the given range", ->
        languageMode.toggleLineCommentsForBufferRows(0, 1)
        expect(buffer.lineForRow(0)).toBe "/*body {"
        expect(buffer.lineForRow(1)).toBe "  font-size: 1234px;*/"
        expect(buffer.lineForRow(2)).toBe "  width: 110%;"
        expect(buffer.lineForRow(3)).toBe "  font-weight: bold !important;"

        languageMode.toggleLineCommentsForBufferRows(2, 2)
        expect(buffer.lineForRow(0)).toBe "/*body {"
        expect(buffer.lineForRow(1)).toBe "  font-size: 1234px;*/"
        expect(buffer.lineForRow(2)).toBe "/*  width: 110%;*/"
        expect(buffer.lineForRow(3)).toBe "  font-weight: bold !important;"

        languageMode.toggleLineCommentsForBufferRows(0, 1)
        expect(buffer.lineForRow(0)).toBe "body {"
        expect(buffer.lineForRow(1)).toBe "  font-size: 1234px;"
        expect(buffer.lineForRow(2)).toBe "/*  width: 110%;*/"
        expect(buffer.lineForRow(3)).toBe "  font-weight: bold !important;"

      it "uncomments lines with leading whitespace", ->
        buffer.change([[2, 0], [2, Infinity]], "  /*width: 110%;*/")
        languageMode.toggleLineCommentsForBufferRows(2, 2)
        expect(buffer.lineForRow(2)).toBe "  width: 110%;"

      it "uncomments lines with trailing whitespace", ->
        buffer.change([[2, 0], [2, Infinity]], "/*width: 110%;*/  ")
        languageMode.toggleLineCommentsForBufferRows(2, 2)
        expect(buffer.lineForRow(2)).toBe "width: 110%;  "

      it "uncomments lines with leading and trailing whitespace", ->
        buffer.change([[2, 0], [2, Infinity]], "   /*width: 110%;*/ ")
        languageMode.toggleLineCommentsForBufferRows(2, 2)
        expect(buffer.lineForRow(2)).toBe "   width: 110%; "

  describe "less", ->
    beforeEach ->
      atom.activatePackage('less-tmbundle', sync: true)
      atom.activatePackage('css-tmbundle', sync: true)
      editSession = project.open('sample.less', autoIndent: false)
      {buffer, languageMode} = editSession

    describe "when commenting lines", ->
      it "only uses the `commentEnd` pattern if it comes from the same grammar as the `commentStart`", ->
        languageMode.toggleLineCommentsForBufferRows(0, 0)
        expect(buffer.lineForRow(0)).toBe "// @color: #4D926F;"

  describe "folding", ->

    beforeEach ->
      atom.activatePackage('javascript-tmbundle', sync: true)
      editSession = project.open('sample.js', autoIndent: false)
      {buffer, languageMode} = editSession

    it "maintains cursor buffer position when a folding/unfolding", ->
      editSession.setCursorBufferPosition([5,5])
      languageMode.foldAll()
      expect(editSession.getCursorBufferPosition()).toEqual([5,5])

    describe ".unfoldAll()", ->
      it "unfolds every folded line", ->
        initialScreenLineCount = editSession.getScreenLineCount()
        languageMode.foldBufferRow(0)
        languageMode.foldBufferRow(1)
        expect(editSession.getScreenLineCount()).toBeLessThan initialScreenLineCount
        languageMode.unfoldAll()
        expect(editSession.getScreenLineCount()).toBe initialScreenLineCount

    describe ".foldAll()", ->
      it "folds every foldable line", ->
        languageMode.foldAll()

        fold1 = editSession.lineForScreenRow(0).fold
        expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 12]
        fold1.destroy()

        fold2 = editSession.lineForScreenRow(1).fold
        expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [1, 9]
        fold2.destroy()

        fold3 = editSession.lineForScreenRow(4).fold
        expect([fold3.getStartRow(), fold3.getEndRow()]).toEqual [4, 7]

    describe ".foldBufferRow(bufferRow)", ->
      describe "when bufferRow can be folded", ->
        it "creates a fold based on the syntactic region starting at the given row", ->
          languageMode.foldBufferRow(1)
          fold = editSession.lineForScreenRow(1).fold
          expect(fold.getStartRow()).toBe 1
          expect(fold.getEndRow()).toBe 9

      describe "when bufferRow can't be folded", ->
        it "searches upward for the first row that begins a syntatic region containing the given buffer row (and folds it)", ->
          languageMode.foldBufferRow(8)
          fold = editSession.lineForScreenRow(1).fold
          expect(fold.getStartRow()).toBe 1
          expect(fold.getEndRow()).toBe 9

      describe "when the bufferRow is already folded", ->
        it "searches upward for the first row that begins a syntatic region containing the folded row (and folds it)", ->
          languageMode.foldBufferRow(2)
          expect(editSession.lineForScreenRow(1).fold).toBeDefined()
          expect(editSession.lineForScreenRow(0).fold).not.toBeDefined()

          languageMode.foldBufferRow(1)
          expect(editSession.lineForScreenRow(0).fold).toBeDefined()

      describe "when the bufferRow is in a multi-line comment", ->
        it "searches upward and downward for surrounding comment lines and folds them as a single fold", ->
          buffer.insert([1,0], "  //this is a comment\n  // and\n  //more docs\n\n//second comment")
          languageMode.foldBufferRow(1)
          fold = editSession.lineForScreenRow(1).fold
          expect(fold.getStartRow()).toBe 1
          expect(fold.getEndRow()).toBe 3

      describe "when the bufferRow is a single-line comment", ->
        it "searches upward for the first row that begins a syntatic region containing the folded row (and folds it)", ->
          buffer.insert([1,0], "  //this is a single line comment\n")
          languageMode.foldBufferRow(1)
          fold = editSession.lineForScreenRow(0).fold
          expect(fold.getStartRow()).toBe 0
          expect(fold.getEndRow()).toBe 13

    describe ".unfoldBufferRow(bufferRow)", ->
      describe "when bufferRow can be unfolded", ->
        it "destroys a fold based on the syntactic region starting at the given row", ->
          languageMode.foldBufferRow(1)
          expect(editSession.lineForScreenRow(1).fold).toBeDefined()

          languageMode.unfoldBufferRow(1)
          expect(editSession.lineForScreenRow(1).fold).toBeUndefined()

      describe "when bufferRow can't be unfolded", ->
        it "does not throw an error", ->
          expect(editSession.lineForScreenRow(1).fold).toBeUndefined()
          languageMode.unfoldBufferRow(1)
          expect(editSession.lineForScreenRow(1).fold).toBeUndefined()

  describe "folding with comments", ->
    beforeEach ->
      atom.activatePackage('javascript-tmbundle', sync: true)
      editSession = project.open('sample-with-comments.js', autoIndent: false)
      {buffer, languageMode} = editSession

    describe ".unfoldAll()", ->
      it "unfolds every folded line", ->
        initialScreenLineCount = editSession.getScreenLineCount()
        languageMode.foldBufferRow(0)
        languageMode.foldBufferRow(5)
        expect(editSession.getScreenLineCount()).toBeLessThan initialScreenLineCount
        languageMode.unfoldAll()
        expect(editSession.getScreenLineCount()).toBe initialScreenLineCount

    describe ".foldAll()", ->
      it "folds every foldable line", ->
        languageMode.foldAll()

        fold1 = editSession.lineForScreenRow(0).fold
        expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 19]
        fold1.destroy()

        fold2 = editSession.lineForScreenRow(1).fold
        expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [1, 4]

        fold3 = editSession.lineForScreenRow(2).fold.destroy()

        fold4 = editSession.lineForScreenRow(3).fold
        expect([fold4.getStartRow(), fold4.getEndRow()]).toEqual [6, 8]

    describe ".foldAllAtIndentLevel()", ->
      it "folds every foldable range at a given indentLevel", ->
        languageMode.foldAllAtIndentLevel(2)

        fold1 = editSession.lineForScreenRow(6).fold
        expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [6, 8]
        fold1.destroy()

        fold2 = editSession.lineForScreenRow(11).fold
        expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [11, 14]
        fold2.destroy()

      it "does not fold anything but the indentLevel", ->
        languageMode.foldAllAtIndentLevel(0)

        fold1 = editSession.lineForScreenRow(0).fold
        expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 19]
        fold1.destroy()

        fold2 = editSession.lineForScreenRow(5).fold
        expect(fold2).toBeFalsy()






