#include "editor_controller.h"
#include "menu_controller.h"
#include "script_parameter_controller.h"
#include "app.h"
#include <escher/metric.h>
#include <ion.h>

using namespace Shared;

namespace Code {

EditorController::EditorController(MenuController * menuController, App * pythonDelegate) :
  ViewController(nullptr),
  m_editorView(this, pythonDelegate),
  m_script(Ion::Storage::Record()),
  m_menuController(menuController)
{
  m_editorView.setTextAreaDelegates(this, this);
}

void EditorController::setScript(Script script) {
  m_script = script;
  Script::Data scriptData = m_script.value();
  size_t availableScriptSize = scriptData.size + Ion::Storage::sharedStorage()->availableSize();
  assert(sizeof(m_areaBuffer) >= availableScriptSize);
  // We cannot use strlcpy as the first char reprensenting the importation status can be 0.
  memcpy(m_areaBuffer, (const char *)scriptData.buffer, scriptData.size);
  m_editorView.setText(m_areaBuffer+1, availableScriptSize-1); // 1 char is taken by the importation status flag
}

// TODO: this should be done in textAreaDidFinishEditing maybe??
bool EditorController::handleEvent(Ion::Events::Event event) {
  if (event == Ion::Events::OK || event == Ion::Events::Back || event == Ion::Events::Home) {
    saveScript();
    stackController()->pop();
    return event != Ion::Events::Home;
  }
  return false;
}

void EditorController::didBecomeFirstResponder() {
  Container::activeApp()->setFirstResponder(&m_editorView);
}

void EditorController::viewWillAppear() {
  m_editorView.loadSyntaxHighlighter();
  m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text()));
}

void EditorController::viewDidDisappear() {
  m_menuController->scriptContentEditionDidFinish();
}

bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) {
  if (event == Ion::Events::Var) {
    /* We save the script before displaying the Variable box to add new
     * functions or variables. */
    saveScript();
    return false;
  }
  if (App::app()->textInputDidReceiveEvent(textArea, event)) {
    return true;
  }
  if (event == Ion::Events::EXE) {
    textArea->handleEventWithText("\n", true, false);
    return true;
  }

  if (event == Ion::Events::Backspace) {
    /* If the cursor is on the left of the text of a line, backspace one
     * indentation space at a time. */
    const char * text = textArea->text();
    const char * cursorLocation = textArea->cursorLocation();
    const char * firstNonSpace = UTF8Helper::NotCodePointSearch(text, ' ', true, cursorLocation);
    assert(firstNonSpace >= text);
    bool cursorIsPrecededOnTheLineBySpacesOnly = false;
    size_t numberOfSpaces = cursorLocation - firstNonSpace;
    if (UTF8Helper::CodePointIs(firstNonSpace, '\n')) {
      cursorIsPrecededOnTheLineBySpacesOnly = true;
      numberOfSpaces -= UTF8Decoder::CharSizeOfCodePoint('\n');
    } else if (firstNonSpace == text) {
      cursorIsPrecededOnTheLineBySpacesOnly = true;
    }
    numberOfSpaces = numberOfSpaces / UTF8Decoder::CharSizeOfCodePoint(' ');
    if (cursorIsPrecededOnTheLineBySpacesOnly && numberOfSpaces >= TextArea::k_indentationSpaces) {
      for (int i = 0; i < TextArea::k_indentationSpaces; i++) {
        textArea->removePreviousGlyph();
      }
      return true;
    }
  } else if (event == Ion::Events::Space) {
    /* If the cursor is on the left of the text of a line, a space triggers an
     * indentation. */
    const char * text = textArea->text();
    const char * firstNonSpace = UTF8Helper::NotCodePointSearch(text, ' ', true, textArea->cursorLocation());
    assert(firstNonSpace >= text);
    if (UTF8Helper::CodePointIs(firstNonSpace, '\n')) {
      assert(UTF8Decoder::CharSizeOfCodePoint(' ') == 1);
      char indentationBuffer[TextArea::k_indentationSpaces+1];
      for (int i = 0; i < TextArea::k_indentationSpaces; i++) {
        indentationBuffer[i] = ' ';
      }
      indentationBuffer[TextArea::k_indentationSpaces] = 0;
      textArea->handleEventWithText(indentationBuffer);
      return true;
    }
  }
  return false;
}

VariableBoxController * EditorController::variableBoxForInputEventHandler(InputEventHandler * textInput) {
  VariableBoxController * varBox = App::app()->variableBoxController();
  varBox->loadFunctionsAndVariables();
  return varBox;
}

StackViewController * EditorController::stackController() {
  return static_cast<StackViewController *>(parentResponder());
}

void EditorController::saveScript() {
  size_t sizeOfValue = strlen(m_areaBuffer+1)+1+1; // size of scriptContent + size of importation status
  Script::ErrorStatus err = m_script.setValue({.buffer=m_areaBuffer, .size=sizeOfValue});
  assert(err != Script::ErrorStatus::NotEnoughSpaceAvailable && err != Script::ErrorStatus::RecordDoesNotExist); // This should not happen as we set the text area according to the available space in the Kallax
  (void) err;
}

}
