#include <escher/expression_field.h>
#include <poincare/preferences.h>
#include <assert.h>
#include <algorithm>

constexpr KDCoordinate ExpressionField::k_maximalHeight;
constexpr KDCoordinate ExpressionField::k_minimalHeight;

ExpressionField::ExpressionField(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, TextFieldDelegate * textFieldDelegate, LayoutFieldDelegate * layoutFieldDelegate) :
  Responder(parentResponder),
  View(),
  m_inputViewMemoizedHeight(0),
  m_textField(parentResponder, nullptr, k_textFieldBufferSize, k_textFieldBufferSize, inputEventHandlerDelegate, textFieldDelegate, KDFont::LargeFont, 0.0f, 0.5f, Palette::PrimaryText, Palette::ExpressionInputBackground),
  m_layoutField(parentResponder, inputEventHandlerDelegate, layoutFieldDelegate)
{
  // Initialize text field
  m_textField.setMargins(0, k_horizontalMargin, 0, k_horizontalMargin);
  m_textField.setBackgroundColor(Palette::ExpressionInputBackground);
  // Initialize layout field
  m_layoutField.setMargins(k_verticalMargin, k_horizontalMargin, k_verticalMargin, k_horizontalMargin);
  m_layoutField.setBackgroundColor(Palette::ExpressionInputBackground);
}

void ExpressionField::setEditing(bool isEditing, bool reinitDraftBuffer) {
  if (editionIsInTextField()) {
    if (reinitDraftBuffer) {
      m_textField.reinitDraftTextBuffer();
    }
    m_textField.setEditing(isEditing);
  } else {
    if (reinitDraftBuffer) {
      m_layoutField.clearLayout();
    }
    m_layoutField.setEditing(isEditing);
  }
}

bool ExpressionField::isEditing() const {
  return editionIsInTextField() ? m_textField.isEditing() : m_layoutField.isEditing();
}

const char * ExpressionField::text() {
  if (!editionIsInTextField()) {
    m_layoutField.layout().serializeParsedExpression(m_textField.draftTextBuffer(), k_textFieldBufferSize, m_layoutField.context());
  }
  return m_textField.draftTextBuffer();
}

void ExpressionField::setText(const char * text) {
  if (editionIsInTextField()) {
    m_textField.reinitDraftTextBuffer();
    m_textField.setText(text);
  } else {
    m_layoutField.clearLayout();
    m_layoutField.handleEventWithText(text, false, true);
  }
}

View * ExpressionField::subviewAtIndex(int index) {
  assert(index == 0);
  if (editionIsInTextField()) {
    return &m_textField;
  }
  return &m_layoutField;
}

void ExpressionField::layoutSubviews(bool force) {
  KDRect inputViewFrame(0, k_separatorThickness, bounds().width(), bounds().height() - k_separatorThickness);
  if (editionIsInTextField()) {
    m_textField.setFrame(inputViewFrame, force);
    m_layoutField.setFrame(KDRectZero, force);
    return;
  }
  m_layoutField.setFrame(inputViewFrame, force);
  m_textField.setFrame(KDRectZero, force);
}

void ExpressionField::drawRect(KDContext * ctx, KDRect rect) const {
  // Draw the separator
  ctx->fillRect(KDRect(0, 0, bounds().width(), k_separatorThickness), Palette::ExpressionInputBorder);
}

bool ExpressionField::handleEvent(Ion::Events::Event event) {
  return editionIsInTextField() ? m_textField.handleEvent(event) : m_layoutField.handleEvent(event);
}

void ExpressionField::didBecomeFirstResponder() {
  m_inputViewMemoizedHeight = inputViewHeight();
}

KDSize ExpressionField::minimalSizeForOptimalDisplay() const {
  return KDSize(0, inputViewHeight());
}

bool ExpressionField::editionIsInTextField() const {
  return Poincare::Preferences::sharedPreferences()->editionMode() == Poincare::Preferences::EditionMode::Edition1D;
}

bool ExpressionField::isEmpty() const {
  return editionIsInTextField() ? (m_textField.draftTextLength() == 0) : !m_layoutField.hasText();
}

bool ExpressionField::inputViewHeightDidChange() {
  KDCoordinate newHeight = inputViewHeight();
  bool didChange = m_inputViewMemoizedHeight != newHeight;
  m_inputViewMemoizedHeight = newHeight;
  return didChange;
}

bool ExpressionField::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) {
  if (editionIsInTextField()) {
    return m_textField.handleEventWithText(text, indentation, forceCursorRightOfText);
  } else {
    return m_layoutField.handleEventWithText(text, indentation, forceCursorRightOfText);
  }
}

KDCoordinate ExpressionField::inputViewHeight() const {
  return k_separatorThickness
    + (editionIsInTextField() ? k_minimalHeight :
        std::min(k_maximalHeight,
          std::max(k_minimalHeight, m_layoutField.minimalSizeForOptimalDisplay().height())));
}

size_t ExpressionField::moveCursorAndDumpContent(char * buffer, size_t bufferSize) {
  size_t size;
  size_t returnValue;
  char * currentContent;
  if (editionIsInTextField()) {
    size = strlen(m_textField.draftTextBuffer()) + 1;
    m_textField.setCursorLocation(m_textField.cursorLocation() + size - 1);
    currentContent = m_textField.draftTextBuffer();
    /* We take advantage of the fact that draftTextBuffer is null terminated to
     * use a size of 0 as a shorthand for "The buffer contains raw text instead
     * of layouts", since the size of a layout is at least 32 (the size of an
     * empty horizontal layout). This way, we can detect when the edition mode
     * has changed and invalidate the data.*/
    returnValue = 0;
  } else {
    /* moveCursorAndDumpContent will be called whenever Calculation exits,
     * even when an exception occurs. We thus need to check the validity of the
     * layout we are dumping (m_layoutField.contentView.expressionView.layout).
     * However, attempting to get a handle on a layout that has been erased
     * will crash the program. We need the check to be performed on the
     * original object in expressionView. */
    if (!m_layoutField.layoutHasNode()) {
      buffer[0] = 0;
      return 0;
    }
    m_layoutField.putCursorRightOfLayout();
    size = m_layoutField.layout().size();
    currentContent = reinterpret_cast<char *>(m_layoutField.layout().node());
    returnValue = size;
  }
  if (size > bufferSize - 1) {
    buffer[0] = 0;
    return 0;
  }
  memcpy(buffer, currentContent, size);
  return returnValue;
}

void ExpressionField::restoreContent(const char * buffer, size_t size) {
  if (editionIsInTextField()) {
    if (size != 0 || buffer[0] == 0) {
      /* A size other than 0 means the buffer contains Layout information
       * (instead of raw text) that we don't want to restore. This is most
       * likely because the edition mode has been changed between use. */
      return;
    }
    setText(buffer);
    return;
  }
  if (size == 0) {
    return;
  }
  m_layoutField.setLayout(Poincare::Layout::LayoutFromAddress(buffer, size));
}
