#include "python_text_area.h"
#include "app.h"
#include <escher/palette.h>
#include <ion/unicode/utf8_helper.h>
#include <python/port/port.h>
#include "../global_preferences.h"

extern "C" {
#include "py/nlr.h"
#include "py/lexer.h"
}
#include <stdlib.h>
#include <algorithm>

namespace Code {

constexpr KDColor CommentColor = Palette::CodeComment;
constexpr KDColor NumberColor =  Palette::CodeNumber;
constexpr KDColor KeywordColor = Palette::CodeKeyword;
// constexpr KDColor BuiltinColor = KDColor::RGB24(0x0086B3);
constexpr KDColor OperatorColor = Palette::CodeOperator;
constexpr KDColor StringColor = Palette::CodeString;
constexpr KDColor BackgroundColor = Palette::CodeBackground;
constexpr KDColor HighlightColor = Palette::CodeBackgroundSelected;
constexpr KDColor AutocompleteColor = KDColor::RGB24(0xC6C6C6); // TODO Palette change

static inline KDColor TokenColor(mp_token_kind_t tokenKind) {
  if (!GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()) {
    return Palette::CodeText;
  }
  if (tokenKind == MP_TOKEN_STRING) {
    return StringColor;
  }
  if (tokenKind == MP_TOKEN_INTEGER || tokenKind == MP_TOKEN_FLOAT_OR_IMAG) {
    return NumberColor;
  }
  static_assert(MP_TOKEN_ELLIPSIS + 1 == MP_TOKEN_KW_FALSE
      && MP_TOKEN_KW_FALSE      + 1 == MP_TOKEN_KW_NONE
      && MP_TOKEN_KW_NONE       + 1 == MP_TOKEN_KW_TRUE
      && MP_TOKEN_KW_TRUE       + 1 == MP_TOKEN_KW___DEBUG__
      && MP_TOKEN_KW___DEBUG__  + 1 == MP_TOKEN_KW_AND
      && MP_TOKEN_KW_AND        + 1 == MP_TOKEN_KW_AS
      && MP_TOKEN_KW_AS         + 1 == MP_TOKEN_KW_ASSERT
      /* Here there are keywords that depend on MICROPY_PY_ASYNC_AWAIT, we do
       * not test them */
      && MP_TOKEN_KW_BREAK      + 1 == MP_TOKEN_KW_CLASS
      && MP_TOKEN_KW_CLASS      + 1 == MP_TOKEN_KW_CONTINUE
      && MP_TOKEN_KW_CONTINUE   + 1 == MP_TOKEN_KW_DEF
      && MP_TOKEN_KW_DEF        + 1 == MP_TOKEN_KW_DEL
      && MP_TOKEN_KW_DEL        + 1 == MP_TOKEN_KW_ELIF
      && MP_TOKEN_KW_ELIF       + 1 == MP_TOKEN_KW_ELSE
      && MP_TOKEN_KW_ELSE       + 1 == MP_TOKEN_KW_EXCEPT
      && MP_TOKEN_KW_EXCEPT     + 1 == MP_TOKEN_KW_FINALLY
      && MP_TOKEN_KW_FINALLY    + 1 == MP_TOKEN_KW_FOR
      && MP_TOKEN_KW_FOR        + 1 == MP_TOKEN_KW_FROM
      && MP_TOKEN_KW_FROM       + 1 == MP_TOKEN_KW_GLOBAL
      && MP_TOKEN_KW_GLOBAL     + 1 == MP_TOKEN_KW_IF
      && MP_TOKEN_KW_IF         + 1 == MP_TOKEN_KW_IMPORT
      && MP_TOKEN_KW_IMPORT     + 1 == MP_TOKEN_KW_IN
      && MP_TOKEN_KW_IN         + 1 == MP_TOKEN_KW_IS
      && MP_TOKEN_KW_IS         + 1 == MP_TOKEN_KW_LAMBDA
      && MP_TOKEN_KW_LAMBDA     + 1 == MP_TOKEN_KW_NONLOCAL
      && MP_TOKEN_KW_NONLOCAL   + 1 == MP_TOKEN_KW_NOT
      && MP_TOKEN_KW_NOT        + 1 == MP_TOKEN_KW_OR
      && MP_TOKEN_KW_OR         + 1 == MP_TOKEN_KW_PASS
      && MP_TOKEN_KW_PASS       + 1 == MP_TOKEN_KW_RAISE
      && MP_TOKEN_KW_RAISE      + 1 == MP_TOKEN_KW_RETURN
      && MP_TOKEN_KW_RETURN     + 1 == MP_TOKEN_KW_TRY
      && MP_TOKEN_KW_TRY        + 1 == MP_TOKEN_KW_WHILE
      && MP_TOKEN_KW_WHILE      + 1 == MP_TOKEN_KW_WITH
      && MP_TOKEN_KW_WITH       + 1 == MP_TOKEN_KW_YIELD
      && MP_TOKEN_KW_YIELD      + 1 == MP_TOKEN_OP_ASSIGN
      && MP_TOKEN_OP_ASSIGN     + 1 == MP_TOKEN_OP_TILDE,
    "MP_TOKEN order changed, so Code::PythonTextArea::TokenColor might need to change too.");
  if (tokenKind >= MP_TOKEN_KW_FALSE && tokenKind <= MP_TOKEN_KW_YIELD) {
    return KeywordColor;
  }
  static_assert(MP_TOKEN_OP_TILDE       + 1 == MP_TOKEN_OP_LESS
      && MP_TOKEN_OP_LESS               + 1 == MP_TOKEN_OP_MORE
      && MP_TOKEN_OP_MORE               + 1 == MP_TOKEN_OP_DBL_EQUAL
      && MP_TOKEN_OP_DBL_EQUAL          + 1 == MP_TOKEN_OP_LESS_EQUAL
      && MP_TOKEN_OP_LESS_EQUAL         + 1 == MP_TOKEN_OP_MORE_EQUAL
      && MP_TOKEN_OP_MORE_EQUAL         + 1 == MP_TOKEN_OP_NOT_EQUAL
      && MP_TOKEN_OP_NOT_EQUAL          + 1 == MP_TOKEN_OP_PIPE
      && MP_TOKEN_OP_PIPE               + 1 == MP_TOKEN_OP_CARET
      && MP_TOKEN_OP_CARET              + 1 == MP_TOKEN_OP_AMPERSAND
      && MP_TOKEN_OP_AMPERSAND          + 1 == MP_TOKEN_OP_DBL_LESS
      && MP_TOKEN_OP_DBL_LESS           + 1 == MP_TOKEN_OP_DBL_MORE
      && MP_TOKEN_OP_DBL_MORE           + 1 == MP_TOKEN_OP_PLUS
      && MP_TOKEN_OP_PLUS               + 1 == MP_TOKEN_OP_MINUS
      && MP_TOKEN_OP_MINUS              + 1 == MP_TOKEN_OP_STAR
      && MP_TOKEN_OP_STAR               + 1 == MP_TOKEN_OP_AT
      && MP_TOKEN_OP_AT                 + 1 == MP_TOKEN_OP_DBL_SLASH
      && MP_TOKEN_OP_DBL_SLASH          + 1 == MP_TOKEN_OP_SLASH
      && MP_TOKEN_OP_SLASH              + 1 == MP_TOKEN_OP_PERCENT
      && MP_TOKEN_OP_PERCENT            + 1 == MP_TOKEN_OP_DBL_STAR
      && MP_TOKEN_OP_DBL_STAR           + 1 == MP_TOKEN_DEL_PIPE_EQUAL
      && MP_TOKEN_DEL_PIPE_EQUAL        + 1 == MP_TOKEN_DEL_CARET_EQUAL
      && MP_TOKEN_DEL_CARET_EQUAL       + 1 == MP_TOKEN_DEL_AMPERSAND_EQUAL
      && MP_TOKEN_DEL_AMPERSAND_EQUAL   + 1 == MP_TOKEN_DEL_DBL_LESS_EQUAL
      && MP_TOKEN_DEL_DBL_LESS_EQUAL    + 1 == MP_TOKEN_DEL_DBL_MORE_EQUAL
      && MP_TOKEN_DEL_DBL_MORE_EQUAL    + 1 == MP_TOKEN_DEL_PLUS_EQUAL
      && MP_TOKEN_DEL_PLUS_EQUAL        + 1 == MP_TOKEN_DEL_MINUS_EQUAL
      && MP_TOKEN_DEL_MINUS_EQUAL       + 1 == MP_TOKEN_DEL_STAR_EQUAL
      && MP_TOKEN_DEL_STAR_EQUAL        + 1 == MP_TOKEN_DEL_AT_EQUAL
      && MP_TOKEN_DEL_AT_EQUAL          + 1 == MP_TOKEN_DEL_DBL_SLASH_EQUAL
      && MP_TOKEN_DEL_DBL_SLASH_EQUAL   + 1 == MP_TOKEN_DEL_SLASH_EQUAL
      && MP_TOKEN_DEL_SLASH_EQUAL       + 1 == MP_TOKEN_DEL_PERCENT_EQUAL
      && MP_TOKEN_DEL_PERCENT_EQUAL     + 1 == MP_TOKEN_DEL_DBL_STAR_EQUAL
      && MP_TOKEN_DEL_DBL_STAR_EQUAL    + 1 == MP_TOKEN_DEL_PAREN_OPEN
      && MP_TOKEN_DEL_PAREN_OPEN        + 1 == MP_TOKEN_DEL_PAREN_CLOSE
      && MP_TOKEN_DEL_PAREN_CLOSE       + 1 == MP_TOKEN_DEL_BRACKET_OPEN
      && MP_TOKEN_DEL_BRACKET_OPEN      + 1 == MP_TOKEN_DEL_BRACKET_CLOSE
      && MP_TOKEN_DEL_BRACKET_CLOSE     + 1 == MP_TOKEN_DEL_BRACE_OPEN
      && MP_TOKEN_DEL_BRACE_OPEN        + 1 == MP_TOKEN_DEL_BRACE_CLOSE
      && MP_TOKEN_DEL_BRACE_CLOSE       + 1 == MP_TOKEN_DEL_COMMA
      && MP_TOKEN_DEL_COMMA             + 1 == MP_TOKEN_DEL_COLON
      && MP_TOKEN_DEL_COLON             + 1 == MP_TOKEN_DEL_PERIOD
      && MP_TOKEN_DEL_PERIOD            + 1 == MP_TOKEN_DEL_SEMICOLON
      && MP_TOKEN_DEL_SEMICOLON         + 1 == MP_TOKEN_DEL_EQUAL
      && MP_TOKEN_DEL_EQUAL             + 1 == MP_TOKEN_DEL_MINUS_MORE,
    "MP_TOKEN order changed, so Code::PythonTextArea::TokenColor might need to change too.");

  if ((tokenKind >= MP_TOKEN_OP_TILDE && tokenKind <= MP_TOKEN_DEL_DBL_STAR_EQUAL)
      || tokenKind == MP_TOKEN_DEL_EQUAL
      || tokenKind == MP_TOKEN_DEL_MINUS_MORE
      || tokenKind == MP_TOKEN_OP_ASSIGN)
  {
    return OperatorColor;
  }
  return Palette::CodeText;
}

static inline size_t TokenLength(mp_lexer_t * lex, const char * tokenPosition) {
  /* The lexer stores the beginning of the current token and of the next token,
   * so we just use that. */
  if (lex->line > 1) {
    /* The next token is on the next line, so we cannot just make the difference
     * of the columns. */
    return UTF8Helper::CodePointSearch(tokenPosition, '\n') - tokenPosition;
  }
  return lex->column - lex->tok_column;
}

PythonTextArea::AutocompletionType PythonTextArea::autocompletionType(const char * autocompletionLocation, const char ** autocompletionLocationBeginning, const char ** autocompletionLocationEnd) const {
  const char * location = autocompletionLocation != nullptr ? autocompletionLocation : cursorLocation();
  const char * beginningOfToken = nullptr;

  /* If there is already autocompleting, the cursor must be at the end of an
   * identifier. Trying to compute autocompletionType will fail: because of the
   * autocompletion text, the cursor seems to be in the middle of an identifier. */
  AutocompletionType autocompleteType = isAutocompleting() ? AutocompletionType::EndOfIdentifier : AutocompletionType::NoIdentifier;
  if (autocompletionLocationBeginning == nullptr && autocompletionLocationEnd == nullptr) {
    return autocompleteType;
  }
  nlr_buf_t nlr;
  if (nlr_push(&nlr) == 0) {
    const char * firstNonSpace = UTF8Helper::BeginningOfWord(m_contentView.editedText(), location);
    mp_lexer_t * lex = mp_lexer_new_from_str_len(0, firstNonSpace, UTF8Helper::EndOfWord(location) - firstNonSpace, 0);

    const char * tokenStart;
    const char * tokenEnd;
    _mp_token_kind_t currentTokenKind = lex->tok_kind;

    while (currentTokenKind != MP_TOKEN_NEWLINE && currentTokenKind != MP_TOKEN_END) {
      tokenStart = firstNonSpace + lex->tok_column - 1;
      tokenEnd = tokenStart + TokenLength(lex, tokenStart);

      if (location < tokenStart) {
        // The location for autocompletion is not in an identifier
        assert(autocompleteType == AutocompletionType::NoIdentifier);
        break;
      }
      if (location <= tokenEnd) {
        if (currentTokenKind == MP_TOKEN_NAME
            || (currentTokenKind >= MP_TOKEN_KW_FALSE
              && currentTokenKind <= MP_TOKEN_KW_YIELD))
        {
          /* The location for autocompletion is in the middle or at the end of
           * an identifier. */
          beginningOfToken = tokenStart;
          /* If autocompleteType is already EndOfIdentifier, we are
           * autocompleting, so we do not need to update autocompleteType. If we
           * recomputed autocompleteType now, we might wrongly think that it is
           * MiddleOfIdentifier because of the autocompetion text.
           * Example : fin|ally -> the lexer is at the end of "fin", but because
           * we are autocompleting with "ally", the lexer thinks the cursor is
           * in the middle of an identifier. */
          if (autocompleteType != AutocompletionType::EndOfIdentifier) {
            autocompleteType = location < tokenEnd ? AutocompletionType::MiddleOfIdentifier : AutocompletionType::EndOfIdentifier;
          }
        }
        break;
      }
      mp_lexer_to_next(lex);
      currentTokenKind = lex->tok_kind;
    }
    mp_lexer_free(lex);
    nlr_pop();
  }
  if (autocompletionLocationBeginning != nullptr) {
    *autocompletionLocationBeginning = beginningOfToken;
  }
  if (autocompletionLocationEnd != nullptr) {
    *autocompletionLocationEnd = location;
  }
  assert(!isAutocompleting() || autocompleteType == AutocompletionType::EndOfIdentifier);
  return autocompleteType;
}

const char * PythonTextArea::ContentView::textToAutocomplete() const {
  return UTF8Helper::BeginningOfWord(editedText(), cursorLocation());
}

void PythonTextArea::ContentView::loadSyntaxHighlighter() {
  m_pythonDelegate->initPythonWithUser(this);
}

void PythonTextArea::ContentView::unloadSyntaxHighlighter() {
  m_pythonDelegate->deinitPython();
}

void PythonTextArea::ContentView::clearRect(KDContext * ctx, KDRect rect) const {
  ctx->fillRect(rect, BackgroundColor);
}

#define LOG_DRAWING 0
#if LOG_DRAWING
#include <stdio.h>
#define LOG_DRAW(...) printf(__VA_ARGS__)
#else
#define LOG_DRAW(...)
#endif

void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char * text, size_t byteLength, int fromColumn, int toColumn, const char * selectionStart, const char * selectionEnd) const {
  LOG_DRAW("Drawing \"%.*s\"\n", byteLength, text);

  assert(m_pythonDelegate->isPythonUser(this));

  /* We're using the MicroPython lexer to do syntax highlighting on a per-line
   * basis. This can work, however the MicroPython lexer won't accept a line
   * starting with a whitespace. So we're discarding leading whitespaces
   * beforehand. */
  const char * firstNonSpace = UTF8Helper::NotCodePointSearch(text, ' ');
  if (firstNonSpace != text) {
    // Color the discarded leading whitespaces
    const char * spacesStart = UTF8Helper::CodePointAtGlyphOffset(text, fromColumn);
    drawStringAt(
        ctx,
        line,
        fromColumn,
        spacesStart,
        std::min(text + byteLength, firstNonSpace) - spacesStart,
        StringColor,
        BackgroundColor,
        selectionStart,
        selectionEnd,
        HighlightColor);
  }
  if (UTF8Helper::CodePointIs(firstNonSpace, UCodePointNull)) {
    return;
  }

  const char * autocompleteStart = m_autocomplete ? m_cursorLocation : nullptr;

  nlr_buf_t nlr;
  if (nlr_push(&nlr) == 0) {
    mp_lexer_t * lex = mp_lexer_new_from_str_len(0, firstNonSpace, byteLength - (firstNonSpace - text), 0);
    LOG_DRAW("Pop token %d\n", lex->tok_kind);

    const char * tokenFrom = firstNonSpace;
    size_t tokenLength = 0;
    const char * tokenEnd = firstNonSpace;
    while (lex->tok_kind != MP_TOKEN_NEWLINE && lex->tok_kind != MP_TOKEN_END) {
      tokenFrom = firstNonSpace + lex->tok_column - 1;
      if (tokenFrom != tokenEnd) {
        // We passed over white spaces, we need to color them
        drawStringAt(
            ctx,
            line,
            UTF8Helper::GlyphOffsetAtCodePoint(text, tokenEnd),
            tokenEnd,
            std::min(text + byteLength, tokenFrom) - tokenEnd,
            StringColor,
            BackgroundColor,
            selectionStart,
            selectionEnd,
            HighlightColor);
      }
      tokenLength = TokenLength(lex, tokenFrom);
      tokenEnd = tokenFrom + tokenLength;

      // If the token is being autocompleted, use DefaultColor
      KDColor color = (tokenFrom <= autocompleteStart && autocompleteStart < tokenEnd) ? Palette::CodeText : TokenColor(lex->tok_kind);

      LOG_DRAW("Draw \"%.*s\" for token %d\n", tokenLength, tokenFrom, lex->tok_kind);
      drawStringAt(ctx, line,
        UTF8Helper::GlyphOffsetAtCodePoint(text, tokenFrom),
        tokenFrom,
        tokenLength,
        color,
        BackgroundColor,
        selectionStart,
        selectionEnd,
        HighlightColor);

      mp_lexer_to_next(lex);
      LOG_DRAW("Pop token %d\n", lex->tok_kind);
    }

    tokenFrom += tokenLength;

    KDColor color = CommentColor;
    if (!GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()) {
      color = Palette::CodeText;
    }
    // Even if the token is being autocompleted, use CommentColor
    if (tokenFrom < text + byteLength) {
      LOG_DRAW("Draw comment \"%.*s\" from %d\n", byteLength - (tokenFrom - text), firstNonSpace, tokenFrom);
      drawStringAt(ctx, line,
          UTF8Helper::GlyphOffsetAtCodePoint(text, tokenFrom),
          tokenFrom,
          text + byteLength - tokenFrom,
          color,
          BackgroundColor,
          selectionStart,
          selectionEnd,
          HighlightColor);
    }

    mp_lexer_free(lex);
    nlr_pop();
  }

  // Redraw the autocompleted word in the right color
  if (m_autocomplete && autocompleteStart >= text && autocompleteStart < text + byteLength) {
    assert(m_autocompletionEnd != nullptr && m_autocompletionEnd > autocompleteStart);
    drawStringAt(
        ctx,
        line,
        UTF8Helper::GlyphOffsetAtCodePoint(text, autocompleteStart),
        autocompleteStart,
        std::min(text + byteLength, m_autocompletionEnd) - autocompleteStart,
        AutocompleteColor,
        BackgroundColor,
        nullptr,
        nullptr,
        HighlightColor);
  }
}

KDRect PythonTextArea::ContentView::dirtyRectFromPosition(const char * position, bool includeFollowingLines) const {
  /* Mark the whole line as dirty.
   * TextArea has a very conservative approach and only dirties the surroundings
   * of the current character. That works for plain text, but when doing syntax
   * highlighting, you may want to redraw the surroundings as well. For example,
   * if editing "def foo" into "df foo", you'll want to redraw "df". */
  KDRect baseDirtyRect = TextArea::ContentView::dirtyRectFromPosition(position, includeFollowingLines);
  return KDRect(
    bounds().x(),
    baseDirtyRect.y(),
    bounds().width(),
    baseDirtyRect.height()
  );
}

bool PythonTextArea::handleEvent(Ion::Events::Event event) {
  if (m_contentView.isAutocompleting()) {
    // Handle event with autocompletion
    if (event == Ion::Events::Right
        || event == Ion::Events::ShiftRight
        || event == Ion::Events::OK)
    {
      m_contentView.reloadRectFromPosition(m_contentView.cursorLocation(), false);
      acceptAutocompletion(event != Ion::Events::ShiftRight);
      if (event != Ion::Events::ShiftRight) {
        // Do not process the event more
        scrollToCursor();
        return true;
      }
    } else if (event == Ion::Events::Toolbox
        || event == Ion::Events::Var
        || event == Ion::Events::Shift
        || event == Ion::Events::Alpha
        || event == Ion::Events::OnOff)
    {
    } else if(event == Ion::Events::Up
        || event == Ion::Events::Down)
    {
      cycleAutocompletion(event == Ion::Events::Down);
      return true;
    } else {
      removeAutocompletion();
      m_contentView.reloadRectFromPosition(m_contentView.cursorLocation(), false);
      if (event == Ion::Events::Back) {
        // Do not process the event more
        return true;
      }
    }
  }
  bool result = TextArea::handleEvent(event);
  if (event == Ion::Events::Backspace && !m_contentView.isAutocompleting() && selectionIsEmpty()) {
    /* We want to add autocompletion when we are editing a word (after adding or
     * deleting text). So if nothing is selected, we add the autocompletion if
     * the event is backspace, as autocompletion has already been added if the
     * event added text, in handleEventWithText. */
    addAutocompletion();
  }
  return result;
}

bool PythonTextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) {
  if (*text == 0) {
    return false;
  }
  if (m_contentView.isAutocompleting()) {
    removeAutocompletion();
  }
  bool result = TextArea::handleEventWithText(text, indentation, forceCursorRightOfText);
  addAutocompletion();
  return result;
}

void PythonTextArea::removeAutocompletion() {
  assert(m_contentView.isAutocompleting());
  removeAutocompletionText();
  m_contentView.setAutocompleting(false);
}

void PythonTextArea::removeAutocompletionText() {
  assert(m_contentView.isAutocompleting());
  assert(m_contentView.autocompletionEnd() != nullptr);
  const char * autocompleteStart = m_contentView.cursorLocation();
  const char * autocompleteEnd = m_contentView.autocompletionEnd();
  assert(autocompleteEnd != nullptr && autocompleteEnd > autocompleteStart);
  m_contentView.removeText(autocompleteStart, autocompleteEnd);
}

void PythonTextArea::addAutocompletion() {
  assert(!m_contentView.isAutocompleting());
  const char * autocompletionTokenBeginning = nullptr;
  const char * autocompletionLocation = const_cast<char *>(cursorLocation());
  m_autocompletionResultIndex = 0;
  if (autocompletionType(autocompletionLocation, &autocompletionTokenBeginning) != AutocompletionType::EndOfIdentifier) {
    // The cursor is not at the end of an identifier.
    return;
  }

  // First load variables and functions that complete the textToAutocomplete
  const int scriptIndex = m_contentView.pythonDelegate()->menuController()->editedScriptIndex();
  m_contentView.pythonDelegate()->variableBoxController()->loadFunctionsAndVariables(scriptIndex, autocompletionTokenBeginning, autocompletionLocation - autocompletionTokenBeginning);

  addAutocompletionTextAtIndex(0);
}

bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIndexToUpdate) {
  // If Autocomplete disable, skip this step
  if(!GlobalPreferences::sharedGlobalPreferences()->autocomplete()) {
    return false;
  }

  // The variable box should be loaded at this point
  const char * autocompletionTokenBeginning = nullptr;
  const char * autocompletionLocation = const_cast<char *>(cursorLocation());
  AutocompletionType type = autocompletionType(autocompletionLocation, &autocompletionTokenBeginning); // Done to get autocompletionTokenBeginning
  assert(type == AutocompletionType::EndOfIdentifier);
  (void)type; // Silence warnings
  VariableBoxController * varBox = m_contentView.pythonDelegate()->variableBoxController();
  int textToInsertLength = 0;
  bool addParentheses = false;
  const char * textToInsert = varBox->autocompletionAlternativeAtIndex(autocompletionLocation - autocompletionTokenBeginning, &textToInsertLength, &addParentheses, nextIndex, currentIndexToUpdate);

  if (textToInsert == nullptr) {
    return false;
  }

  if (textToInsertLength > 0) {
    // Try to insert the text (this might fail if the buffer is full)
    if (!m_contentView.insertTextAtLocation(textToInsert, const_cast<char *>(autocompletionLocation), textToInsertLength)) {
      return false;
    }
    autocompletionLocation += textToInsertLength;
    m_contentView.setAutocompleting(true);
    m_contentView.setAutocompletionEnd(autocompletionLocation);
  }

  // Try to insert the parentheses if needed
  const char * parentheses = ScriptNodeCell::k_parentheses;
  constexpr int parenthesesLength = 2;
  assert(strlen(parentheses) == parenthesesLength);
  /* If couldInsertText is false, we should not try to add the parentheses as
   * there was already not enough space to add the autocompletion. */
  if (addParentheses && m_contentView.insertTextAtLocation(parentheses, const_cast<char *>(autocompletionLocation), parenthesesLength)) {
    m_contentView.setAutocompleting(true);
    m_contentView.setAutocompletionEnd(autocompletionLocation + parenthesesLength);
    return true;
  }
  return (textToInsertLength > 0);
}

void PythonTextArea::cycleAutocompletion(bool downwards) {
  assert(m_contentView.isAutocompleting());
  removeAutocompletionText();
  addAutocompletionTextAtIndex(m_autocompletionResultIndex + (downwards ? 1 : -1), &m_autocompletionResultIndex);
}

void PythonTextArea::acceptAutocompletion(bool moveCursorToEndOfAutocompletion) {
  assert(m_contentView.isAutocompleting());

  // Save the cursor location
  const char * previousCursorLocation = cursorLocation();

  removeAutocompletion();

  m_contentView.pythonDelegate()->variableBoxController()->setSender(this);
  m_contentView.pythonDelegate()->variableBoxController()->insertAutocompletionResultAtIndex(m_autocompletionResultIndex);

  // insertAutocompletionResultAtIndex already added the autocompletion

  // If we did not want to move the cursor, restore its position.
  if (!moveCursorToEndOfAutocompletion) {
    setCursorLocation(previousCursorLocation);
  }
}

}
