describe('scroll when focus line is out of viewport', function () {
  before(function (done) {
    helper.newPad(function(){
      cleanPad(function(){
        forceUseMonospacedFont();
        scrollWhenPlaceCaretInTheLastLineOfViewport();
        createPadWithSeveralLines(function(){
          resizeEditorHeight();
          done();
        });
      });
    });
    this.timeout(20000);
  });

  context('when user presses any arrow keys on a line above the viewport', function(){
    context('and scroll percentage config is set to 0.2 on settings.json', function(){
      var lineCloseOfTopOfPad = 10;
      before(function (done) {
        setScrollPercentageWhenFocusLineIsOutOfViewport(0.2, true);
        scrollEditorToBottomOfPad();

        placeCaretInTheBeginningOfLine(lineCloseOfTopOfPad, function(){ // place caret in the 10th line
          // warning: even pressing right arrow, the caret does not change of position
          // the column where the caret is, it has not importance, only the line
          pressAndReleaseRightArrow();
          done();
        });
      });

      it('keeps the focus line scrolled 20% from the top of the viewport', function (done) {
        // default behavior is to put the line in the top of viewport, but as
        // scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.2, we have an extra 20% of lines scrolled
        // (2 lines, which are the 20% of the 10 that are visible on viewport)
        var firstLineOfViewport = getFirstLineVisibileOfViewport();
        expect(lineCloseOfTopOfPad).to.be(firstLineOfViewport + 2);
        done();
      });
    });
  });

  context('when user presses any arrow keys on a line below the viewport', function(){
    context('and scroll percentage config is set to 0.7 on settings.json', function(){
      var lineCloseToBottomOfPad = 50;
      before(function (done) {
        setScrollPercentageWhenFocusLineIsOutOfViewport(0.7);

        // firstly, scroll to make the lineCloseToBottomOfPad visible. After that, scroll to make it out of viewport
        scrollEditorToTopOfPad();
        placeCaretAtTheEndOfLine(lineCloseToBottomOfPad); // place caret in the 50th line
        setTimeout(function() {
          // warning: even pressing right arrow, the caret does not change of position
          pressAndReleaseLeftArrow();
          done();
        }, 1000);
      });

      it('keeps the focus line scrolled 70% from the bottom of the viewport', function (done) {
        // default behavior is to put the line in the top of viewport, but as
        // scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.7, we have an extra 70% of lines scrolled
        // (7 lines, which are the 70% of the 10 that are visible on viewport)
        var lastLineOfViewport = getLastLineVisibleOfViewport();
        expect(lineCloseToBottomOfPad).to.be(lastLineOfViewport - 7);
        done();
      });
    });
  });

  context('when user presses arrow up on the first line of the viewport', function(){
    context('and percentageToScrollWhenUserPressesArrowUp is set to 0.3', function () {
      var lineOnTopOfViewportWhenThePadIsScrolledDown;
      before(function (done) {
        setPercentageToScrollWhenUserPressesArrowUp(0.3);

        // we need some room to make the scroll up
        scrollEditorToBottomOfPad();
        lineOnTopOfViewportWhenThePadIsScrolledDown = 91;
        placeCaretAtTheEndOfLine(lineOnTopOfViewportWhenThePadIsScrolledDown);
        setTimeout(function() {
          // warning: even pressing up arrow, the caret does not change of position
          pressAndReleaseUpArrow();
          done();
        }, 1000);
      });

      it('keeps the focus line scrolled 30% of the top of the viewport', function (done) {
        // default behavior is to put the line in the top of viewport, but as
        // PercentageToScrollWhenUserPressesArrowUp is set to 0.3, we have an extra 30% of lines scrolled
        // (3 lines, which are the 30% of the 10 that are visible on viewport)
        var firstLineOfViewport = getFirstLineVisibileOfViewport();
        expect(firstLineOfViewport).to.be(lineOnTopOfViewportWhenThePadIsScrolledDown - 3);
        done();
      })
    });
  });

  context('when user edits the last line of viewport', function(){
    context('and scroll percentage config is set to 0 on settings.json', function(){
      var lastLineOfViewportBeforeEnter = 10;
      before(function () {
        // the default value
        resetScrollPercentageWhenFocusLineIsOutOfViewport();

        // make sure the last line on viewport is the 10th one
        scrollEditorToTopOfPad();
        placeCaretAtTheEndOfLine(lastLineOfViewportBeforeEnter);
        pressEnter();
      });

      it('keeps the focus line on the bottom of the viewport', function (done) {
        var lastLineOfViewportAfterEnter = getLastLineVisibleOfViewport();
        expect(lastLineOfViewportAfterEnter).to.be(lastLineOfViewportBeforeEnter + 1);
        done();
      });
    });

    context('and scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.3', function(){ // this value is arbitrary
      var lastLineOfViewportBeforeEnter = 9;
      before(function () {
        setScrollPercentageWhenFocusLineIsOutOfViewport(0.3);

        // make sure the last line on viewport is the 10th one
        scrollEditorToTopOfPad();
        placeCaretAtTheEndOfLine(lastLineOfViewportBeforeEnter);
        pressBackspace();
      });

      it('scrolls 30% of viewport up', function (done) {
        var lastLineOfViewportAfterEnter = getLastLineVisibleOfViewport();
        // default behavior is to scroll one line at the bottom of viewport, but as
        // scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.3, we have an extra 30% of lines scrolled
        // (3 lines, which are the 30% of the 10 that are visible on viewport)
        expect(lastLineOfViewportAfterEnter).to.be(lastLineOfViewportBeforeEnter + 3);
        done();
      });
    });

    context('and it is set to a value that overflow the interval [0, 1]', function(){
      var lastLineOfViewportBeforeEnter = 10;
      before(function(){
        var scrollPercentageWhenFocusLineIsOutOfViewport = 1.5;
        scrollEditorToTopOfPad();
        placeCaretAtTheEndOfLine(lastLineOfViewportBeforeEnter);
        setScrollPercentageWhenFocusLineIsOutOfViewport(scrollPercentageWhenFocusLineIsOutOfViewport);
        pressEnter();
      });

      it('keeps the default behavior of moving the focus line on the bottom of the viewport', function (done) {
        var lastLineOfViewportAfterEnter = getLastLineVisibleOfViewport();
        expect(lastLineOfViewportAfterEnter).to.be(lastLineOfViewportBeforeEnter + 1);
        done();
      });
    });
  });

  context('when user edits a line above the viewport', function(){
    context('and scroll percentage config is set to 0 on settings.json', function(){
      var lineCloseOfTopOfPad = 10;
      before(function () {
        // the default value
        setScrollPercentageWhenFocusLineIsOutOfViewport(0);

        // firstly, scroll to make the lineCloseOfTopOfPad visible. After that, scroll to make it out of viewport
        scrollEditorToTopOfPad();
        placeCaretAtTheEndOfLine(lineCloseOfTopOfPad); // place caret in the 10th line
        scrollEditorToBottomOfPad();
        pressBackspace(); // edit the line where the caret is, which is above the viewport
      });

      it('keeps the focus line on the top of the viewport', function (done) {
        var firstLineOfViewportAfterEnter = getFirstLineVisibileOfViewport();
        expect(firstLineOfViewportAfterEnter).to.be(lineCloseOfTopOfPad);
        done();
      });
    });

    context('and scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.2', function(){ // this value is arbitrary
      var lineCloseToBottomOfPad = 50;
      before(function () {
        // we force the line edited to be above the top of the viewport
        setScrollPercentageWhenFocusLineIsOutOfViewport(0.2, true); // set scroll jump to 20%
        scrollEditorToTopOfPad();
        placeCaretAtTheEndOfLine(lineCloseToBottomOfPad);
        scrollEditorToBottomOfPad();
        pressBackspace(); // edit line
      });

      it('scrolls 20% of viewport down', function (done) {
        // default behavior is to scroll one line at the top of viewport, but as
        // scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.2, we have an extra 20% of lines scrolled
        // (2 lines, which are the 20% of the 10 that are visible on viewport)
        var firstLineVisibileOfViewport = getFirstLineVisibileOfViewport();
        expect(lineCloseToBottomOfPad).to.be(firstLineVisibileOfViewport + 2);
        done();
      });
    });
  });

  context('when user places the caret at the last line visible of viewport', function(){
    var lastLineVisible;
    context('and scroll percentage config is set to 0 on settings.json', function(){
      before(function (done) {
        // reset to the default value
        resetScrollPercentageWhenFocusLineIsOutOfViewport();

        placeCaretInTheBeginningOfLine(0, function(){ // reset caret position
          scrollEditorToTopOfPad();
          lastLineVisible = getLastLineVisibleOfViewport();
          placeCaretInTheBeginningOfLine(lastLineVisible, done); // place caret in the 9th line
        });

      });

      it('does not scroll', function(done){
        setTimeout(function() {
          var lastLineOfViewport = getLastLineVisibleOfViewport();
          var lineDoesNotScroll = lastLineOfViewport === lastLineVisible;
          expect(lineDoesNotScroll).to.be(true);
          done();
        }, 1000);
      });
    });
    context('and scroll percentage config is set to 0.5 on settings.json', function(){
      before(function (done) {
        setScrollPercentageWhenFocusLineIsOutOfViewport(0.5);
        scrollEditorToTopOfPad();
        placeCaretInTheBeginningOfLine(0, function(){ // reset caret position
          // this timeout inside a callback is ugly but it necessary to give time to aceSelectionChange
          // realizes that the selection has been changed
          setTimeout(function() {
            lastLineVisible = getLastLineVisibleOfViewport();
            placeCaretInTheBeginningOfLine(lastLineVisible, done); // place caret in the 9th line
          }, 1000);
        });
      });

      it('scrolls line to 50% of the viewport', function(done){
        helper.waitFor(function(){
          var lastLineOfViewport = getLastLineVisibleOfViewport();
          var lastLinesScrolledFiveLinesUp = lastLineOfViewport - 5 === lastLineVisible;
          return lastLinesScrolledFiveLinesUp;
        }).done(done);
      });
    });
  });

  // This is a special case. When user is selecting a text with arrow down or arrow left we have
  // to keep the last line selected on focus
  context('when the first line selected is out of the viewport and user presses shift arrow down', function(){
    var lastLineOfPad = 99;
    before(function (done) {
      scrollEditorToTopOfPad();

      // make a selection bigger than the viewport height
      var $firstLineOfSelection = getLine(0);
      var $lastLineOfSelection = getLine(lastLineOfPad);
      var lengthOfLastLine = $lastLineOfSelection.text().length;
      helper.selectLines($firstLineOfSelection, $lastLineOfSelection, 0, lengthOfLastLine);

      // place the last line selected on the viewport
      scrollEditorToBottomOfPad();

      // press a key to make the selection goes down
      // although we can't simulate the extending of selection. It's possible to send a key event
      // which is captured on ace2_inner scroll function.
      pressAndReleaseLeftArrow(true);
      done();
    });

    it('keeps the last line selected on focus', function (done) {
      var lastLineOfSelectionIsVisible = isLineOnViewport(lastLineOfPad);
      expect(lastLineOfSelectionIsVisible).to.be(true);
      done();
    });
  });

  // In this scenario we avoid the bouncing scroll. E.g Let's suppose we have a big line that is
  // the size of the viewport, and its top is above the viewport. When user presses '<-', this line
  // will scroll down because the top is out of the viewport. When it scrolls down, the bottom of
  // line gets below the viewport so when user presses '<-' again it scrolls up to make the bottom
  // of line visible. If user presses arrow keys more than one time, the editor will keep scrolling up and down
  context('when the line height is bigger than the scroll amount percentage * viewport height', function(){
    var scrollOfEditorBeforePressKey;
    var BIG_LINE_NUMBER = 0;
    var MIDDLE_OF_BIG_LINE = 51;
    before(function (done) {
      createPadWithALineHigherThanViewportHeight(this, BIG_LINE_NUMBER, function(){
        setScrollPercentageWhenFocusLineIsOutOfViewport(0.5); // set any value to force scroll to outside to viewport
        var $bigLine = getLine(BIG_LINE_NUMBER);

        // each line has about 5 chars, we place the caret in the middle of the line
        helper.selectLines($bigLine, $bigLine, MIDDLE_OF_BIG_LINE, MIDDLE_OF_BIG_LINE);

        scrollEditorToLeaveTopAndBottomOfBigLineOutOfViewport($bigLine);
        scrollOfEditorBeforePressKey = getEditorScroll();

        // press a key to force to scroll
        pressAndReleaseRightArrow();
        done();
      });
    });

    // reset pad to the original text
    after(function (done) {
      this.timeout(5000);
      cleanPad(function(){
        createPadWithSeveralLines(function(){
          resetEditorWidth();
          done();
        });
      });
    });

    // as the editor.line is inside of the viewport, it should not scroll
    it('should not scroll', function (done) {
      var scrollOfEditorAfterPressKey = getEditorScroll();
      expect(scrollOfEditorAfterPressKey).to.be(scrollOfEditorBeforePressKey);
      done();
    });
  });

  // Some plugins, for example the ep_page_view, change the editor dimensions. This plugin, for example,
  // adds padding-top to the ace_outer, which changes the viewport height
  describe('integration with plugins which changes the margin of editor', function(){
    context('when editor dimensions changes', function(){
      before(function () {
        // reset the size of editor. Now we show more than 10 lines as in the other tests
        resetResizeOfEditorHeight();
        scrollEditorToTopOfPad();

        // height of the editor viewport
        var editorHeight = getEditorHeight();

        // add a big padding-top, 50% of the viewport
        var paddingTopOfAceOuter = editorHeight/2;
        var chrome$ = helper.padChrome$;
        var $outerIframe = chrome$('iframe');
        $outerIframe.css('padding-top', paddingTopOfAceOuter);

        // we set a big value to check if the scroll is made
        setScrollPercentageWhenFocusLineIsOutOfViewport(1);
      });

      context('and user places the caret in the last line visible of the pad', function(){
        var lastLineVisible;
        beforeEach(function (done) {
          lastLineVisible = getLastLineVisibleOfViewport();
          placeCaretInTheBeginningOfLine(lastLineVisible, done);
        });

        it('scrolls the line where caret is', function(done){
          helper.waitFor(function(){
            var firstLineVisibileOfViewport = getFirstLineVisibileOfViewport();
            var linesScrolled = firstLineVisibileOfViewport !== 0;
            return linesScrolled;
          }).done(done);
        });
      });
    });
  });

  /* ********************* Helper functions/constants ********************* */
  var TOP_OF_PAGE = 0;
  var BOTTOM_OF_PAGE = 5000; // we use a big value to force the page to be scrolled all the way down
  var LINES_OF_PAD = 100;
  var ENTER = 13;
  var BACKSPACE = 8;
  var LEFT_ARROW = 37;
  var UP_ARROW = 38;
  var RIGHT_ARROW = 39;
  var LINES_ON_VIEWPORT = 10;
  var WIDTH_OF_EDITOR_RESIZED = 100;
  var LONG_TEXT_CHARS = 100;

  var cleanPad = function(callback) {
    var inner$ = helper.padInner$;
    var $padContent = inner$('#innerdocbody');
    $padContent.html('');

    // wait for Etherpad to re-create first line
    helper.waitFor(function(){
      var lineNumber = inner$('div').length;
      return lineNumber === 1;
    }, 2000).done(callback);
  };

  var createPadWithSeveralLines = function(done) {
    var line = '<span>a</span><br>';
    var $firstLine = helper.padInner$('div').first();
    var lines = line.repeat(LINES_OF_PAD); //arbitrary number, we need to create lines that is over the viewport
    $firstLine.html(lines);

    helper.waitFor(function(){
      var linesCreated = helper.padInner$('div').length;
      return linesCreated === LINES_OF_PAD;
    }, 4000).done(done);
  };

  var createPadWithALineHigherThanViewportHeight = function(test, line, done) {
    var viewportHeight = 160; //10 lines * 16px (height of line)
    test.timeout(5000);
    cleanPad(function(){
      // make the editor smaller to make test easier
      // with that width the each line has about 5 chars
      resizeEditorWidth();

      // we create a line with 100 chars, which makes about 20 lines
      setLongTextOnLine(line);
      helper.waitFor(function () {
        var $firstLine = getLine(line);

        var heightOfLine = $firstLine.get(0).getBoundingClientRect().height;
        return heightOfLine >= viewportHeight;
      }, 4000).done(done);
    });
  };

  var setLongTextOnLine = function(line) {
    var $line = getLine(line);
    var longText = 'a'.repeat(LONG_TEXT_CHARS);
    $line.html(longText);
  };

  // resize the editor to make the tests easier
  var resizeEditorHeight = function() {
    var chrome$ = helper.padChrome$;
    chrome$('#editorcontainer').css('height', getSizeOfViewport());
  };

  // this makes about 5 chars per line
  var resizeEditorWidth = function() {
    var chrome$ = helper.padChrome$;
    chrome$('#editorcontainer').css('width', WIDTH_OF_EDITOR_RESIZED);
  };

  var resetResizeOfEditorHeight = function() {
    var chrome$ = helper.padChrome$;
    chrome$('#editorcontainer').css('height', '');
  };

  var resetEditorWidth = function () {
    var chrome$ = helper.padChrome$;
    chrome$('#editorcontainer').css('width', '');
  };

  var getEditorHeight = function() {
    var chrome$ = helper.padChrome$;
    var $editor = chrome$('#editorcontainer');
    var editorHeight = $editor.get(0).clientHeight;
    return editorHeight;
  };

  var getSizeOfViewport = function() {
    return getLinePositionOnViewport(LINES_ON_VIEWPORT) - getLinePositionOnViewport(0);
  };

  var scrollPageTo = function(value) {
    var outer$ = helper.padOuter$;
    var $ace_outer = outer$('#outerdocbody').parent();
    $ace_outer.parent().scrollTop(value);
  };

  var scrollEditorToTopOfPad = function() {
    scrollPageTo(TOP_OF_PAGE);
  };

  var scrollEditorToBottomOfPad = function() {
    scrollPageTo(BOTTOM_OF_PAGE);
  };

  var scrollEditorToLeaveTopAndBottomOfBigLineOutOfViewport = function ($bigLine) {
    var lineHeight = $bigLine.get(0).getBoundingClientRect().height;
    var middleOfLine = lineHeight/2;
    scrollPageTo(middleOfLine);
  };

  var getLine = function(lineNum) {
    var inner$ = helper.padInner$;
    var $line = inner$('div').eq(lineNum);
    return $line;
  };

  var placeCaretAtTheEndOfLine = function(lineNum) {
    var $targetLine = getLine(lineNum);
    var lineLength = $targetLine.text().length;
    helper.selectLines($targetLine, $targetLine, lineLength, lineLength);
  };

  var placeCaretInTheBeginningOfLine = function(lineNum, cb) {
    var $targetLine = getLine(lineNum);
    helper.selectLines($targetLine, $targetLine, 0, 0);
    helper.waitFor(function() {
      var $lineWhereCaretIs = getLineWhereCaretIs();
      return $targetLine.get(0) === $lineWhereCaretIs.get(0);
    }).done(cb);
  };

  var getLineWhereCaretIs = function() {
    var inner$ = helper.padInner$;
    var nodeWhereCaretIs = inner$.document.getSelection().anchorNode;
    var $lineWhereCaretIs = $(nodeWhereCaretIs).closest('div');
    return $lineWhereCaretIs;
  };

  var getFirstLineVisibileOfViewport = function() {
    return _.find(_.range(0, LINES_OF_PAD - 1), isLineOnViewport);
  };

  var getLastLineVisibleOfViewport = function() {
    return _.find(_.range(LINES_OF_PAD - 1, 0, -1), isLineOnViewport);
  };

  var pressKey = function(keyCode, shiftIsPressed){
    var inner$ = helper.padInner$;
    var evtType;
    if(inner$(window)[0].bowser.firefox || inner$(window)[0].bowser.modernIE){ // if it's a mozilla or IE
      evtType = 'keypress';
    }else{
      evtType = 'keydown';
    }
    var e = inner$.Event(evtType);
    e.shiftKey = shiftIsPressed;
    e.keyCode = keyCode;
    e.which = keyCode; // etherpad listens to 'which'
    inner$('#innerdocbody').trigger(e);
  };

  var releaseKey = function(keyCode){
    var inner$ = helper.padInner$;
    var evtType = 'keyup';
    var e = inner$.Event(evtType);
    e.keyCode = keyCode;
    e.which = keyCode; // etherpad listens to 'which'
    inner$('#innerdocbody').trigger(e);
  };

  var pressEnter = function() {
    pressKey(ENTER);
  };

  var pressBackspace = function() {
    pressKey(BACKSPACE);
  };

  var pressAndReleaseUpArrow = function() {
    pressKey(UP_ARROW);
    releaseKey(UP_ARROW);
  };

  var pressAndReleaseRightArrow = function() {
    pressKey(RIGHT_ARROW);
    releaseKey(RIGHT_ARROW);
  };

  var pressAndReleaseLeftArrow = function(shiftIsPressed) {
    pressKey(LEFT_ARROW, shiftIsPressed);
    releaseKey(LEFT_ARROW);
  };

  var isLineOnViewport = function(lineNumber) {
    // in the function scrollNodeVerticallyIntoView from ace2_inner.js, iframePadTop is used to calculate
    // how much scroll is needed. Although the name refers to padding-top, this value is not set on the
    // padding-top.
    var iframePadTop = 8;
    var $line = getLine(lineNumber);
    var linePosition = $line.get(0).getBoundingClientRect();

    // position relative to the current viewport
    var linePositionTopOnViewport = linePosition.top - getEditorScroll() + iframePadTop;
    var linePositionBottomOnViewport = linePosition.bottom - getEditorScroll();

    var lineBellowTop = linePositionBottomOnViewport > 0;
    var lineAboveBottom = linePositionTopOnViewport < getClientHeightVisible();
    var isVisible = lineBellowTop && lineAboveBottom;

    return isVisible;
  };

  var getEditorScroll = function () {
    var outer$ = helper.padOuter$;
    var scrollTopFirefox = outer$('#outerdocbody').parent().scrollTop(); // works only on firefox
    var scrollTop = outer$('#outerdocbody').scrollTop() || scrollTopFirefox;
    return scrollTop;
  };

  // clientHeight includes padding, so we have to subtract it and consider only the visible viewport
  var getClientHeightVisible = function () {
    var outer$ = helper.padOuter$;
    var $ace_outer = outer$('#outerdocbody').parent();
    var ace_outerHeight = $ace_outer.get(0).clientHeight;
    var ace_outerPaddingTop = getIntValueOfCSSProperty($ace_outer, 'padding-top');
    var paddingAddedWhenPageViewIsEnable = getPaddingAddedWhenPageViewIsEnable();
    var clientHeight = ace_outerHeight - ( ace_outerPaddingTop + paddingAddedWhenPageViewIsEnable);

    return clientHeight;
  };

  // ep_page_view changes the dimensions of the editor. We have to guarantee
  // the viewport height is calculated right
  var getPaddingAddedWhenPageViewIsEnable = function () {
    var chrome$ = helper.padChrome$;
    var $outerIframe = chrome$('iframe');
    var paddingAddedWhenPageViewIsEnable = parseInt($outerIframe.css('padding-top'));
    return paddingAddedWhenPageViewIsEnable;
  };

  var getIntValueOfCSSProperty = function($element, property){
    var valueString = $element.css(property);
    return parseInt(valueString) || 0;
  };

  var forceUseMonospacedFont = function () {
    helper.padChrome$.window.clientVars.padOptions.useMonospaceFont = true;
  };

  var setScrollPercentageWhenFocusLineIsOutOfViewport = function(value, editionAboveViewport) {
    var scrollSettings = helper.padChrome$.window.clientVars.scrollWhenFocusLineIsOutOfViewport;
    if (editionAboveViewport) {
      scrollSettings.percentage.editionAboveViewport = value;
    }else{
      scrollSettings.percentage.editionBelowViewport = value;
    }
  };

  var resetScrollPercentageWhenFocusLineIsOutOfViewport = function() {
    var scrollSettings = helper.padChrome$.window.clientVars.scrollWhenFocusLineIsOutOfViewport;
    scrollSettings.percentage.editionAboveViewport = 0;
    scrollSettings.percentage.editionBelowViewport = 0;
  };

  var setPercentageToScrollWhenUserPressesArrowUp = function (value) {
    var scrollSettings = helper.padChrome$.window.clientVars.scrollWhenFocusLineIsOutOfViewport;
    scrollSettings.percentageToScrollWhenUserPressesArrowUp = value;
  };

  var scrollWhenPlaceCaretInTheLastLineOfViewport = function() {
    var scrollSettings = helper.padChrome$.window.clientVars.scrollWhenFocusLineIsOutOfViewport;
    scrollSettings.scrollWhenCaretIsInTheLastLineOfViewport = true;
  };

  var getLinePositionOnViewport = function(lineNumber) {
    var $line = getLine(lineNumber);
    var linePosition = $line.get(0).getBoundingClientRect();

    // position relative to the current viewport
    return linePosition.top - getEditorScroll();
  };
});

