#include "menu_controller.h"
#include "app.h"
#include <apps/i18n.h>
#include "../apps_container.h"
#include <assert.h>
#include <escher/metric.h>
#include <ion/events.h>

namespace Code {

MenuController::MenuController(Responder * parentResponder, App * pythonDelegate, ScriptStore * scriptStore, ButtonRowController * footer) :
  ViewController(parentResponder),
  ButtonRowDelegate(nullptr, footer),
  m_scriptStore(scriptStore),
  m_addNewScriptCell(),
  m_consoleButton(this, I18n::Message::Console, Invocation([](void * context, void * sender) {
        MenuController * menu = (MenuController *)context;
        menu->consoleController()->setAutoImport(true);
        menu->stackViewController()->push(menu->consoleController());
        return true;
        }, this), KDFont::LargeFont),
  m_selectableTableView(this, this, this, this),
  m_scriptParameterController(nullptr, I18n::Message::ScriptOptions, this),
  m_editorController(this, pythonDelegate),
  m_reloadConsoleWhenBecomingFirstResponder(false),
  m_shouldDisplayAddScriptRow(true)
{
  m_selectableTableView.setMargins(0);
  m_selectableTableView.setDecoratorType(ScrollView::Decorator::Type::None);
  m_addNewScriptCell.setMessage(I18n::Message::AddScript);
  for (int i = 0; i < k_maxNumberOfDisplayableScriptCells; i++) {
    m_scriptCells[i].setParentResponder(&m_selectableTableView);
    m_scriptCells[i].textField()->setDelegates(nullptr, this);
  }
}

ConsoleController * MenuController::consoleController() {
  return static_cast<App *>(app())->consoleController();
}

StackViewController * MenuController::stackViewController() {
  return static_cast<StackViewController *>(parentResponder()->parentResponder());
}

void MenuController::willExitResponderChain(Responder * nextFirstResponder) {
  int selectedRow = m_selectableTableView.selectedRow();
  int selectedColumn = m_selectableTableView.selectedColumn();
  if (selectedRow >= 0 && selectedRow < m_scriptStore->numberOfScripts() && selectedColumn == 0) {
    TextField * tf = static_cast<ScriptNameCell *>(m_selectableTableView.selectedCell())->textField();
    if (tf->isEditing()) {
      tf->setEditing(false, false);
      privateTextFieldDidAbortEditing(tf, false);
    }
  }
}

void MenuController::didBecomeFirstResponder() {
  if (m_reloadConsoleWhenBecomingFirstResponder) {
    reloadConsole();
  }
  if (footer()->selectedButton() == 0) {
    assert(m_selectableTableView.selectedRow() < 0);
    app()->setFirstResponder(&m_consoleButton);
    return;
  }
  if (m_selectableTableView.selectedRow() < 0) {
    m_selectableTableView.selectCellAtLocation(0,0);
  }
  assert(m_selectableTableView.selectedRow() < m_scriptStore->numberOfScripts() + 1);
  app()->setFirstResponder(&m_selectableTableView);
#if EPSILON_GETOPT
  if (consoleController()->locked()) {
    consoleController()->setAutoImport(true);
    stackViewController()->push(consoleController());
    return;
  }
#endif
}

void MenuController::viewWillAppear() {
  updateAddScriptRowDisplay();
}

bool MenuController::handleEvent(Ion::Events::Event event) {
  if (event == Ion::Events::Down) {
    m_selectableTableView.deselectTable();
    footer()->setSelectedButton(0);
    return true;
  }
  if (event == Ion::Events::Up) {
    if (footer()->selectedButton() == 0) {
      footer()->setSelectedButton(-1);
      m_selectableTableView.selectCellAtLocation(0, numberOfRows()-1);
      app()->setFirstResponder(&m_selectableTableView);
      return true;
    }
  }
  if (event == Ion::Events::OK || event == Ion::Events::EXE) {
    int selectedRow = m_selectableTableView.selectedRow();
    int selectedColumn = m_selectableTableView.selectedColumn();
    if (selectedRow >= 0 && selectedRow < m_scriptStore->numberOfScripts()) {
      if (selectedColumn == 1) {
        configureScript();
        return true;
      }
      assert(selectedColumn == 0);
      editScriptAtIndex(selectedRow);
      return true;
    } else if (m_shouldDisplayAddScriptRow
        && selectedColumn == 0
        && selectedRow == m_scriptStore->numberOfScripts())
    {
      addScript();
      return true;
    }
  }
  return false;
}

void MenuController::renameSelectedScript() {
  assert(m_selectableTableView.selectedRow() >= 0);
  assert(m_selectableTableView.selectedRow() < m_scriptStore->numberOfScripts());
  static_cast<AppsContainer *>(const_cast<Container *>(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::AlphaLock);
  m_selectableTableView.selectCellAtLocation(0, (m_selectableTableView.selectedRow()));
  ScriptNameCell * myCell = static_cast<ScriptNameCell *>(m_selectableTableView.selectedCell());
  app()->setFirstResponder(myCell);
  myCell->setHighlighted(false);
  myCell->textField()->setEditing(true, false);
  myCell->textField()->setCursorLocation(strlen(myCell->textField()->text()));
}

void MenuController::deleteScript(Script script) {
  assert(!script.isNull());
  script.destroy();
  updateAddScriptRowDisplay();
}

void MenuController::reloadConsole() {
  consoleController()->unloadPythonEnvironment();
  m_reloadConsoleWhenBecomingFirstResponder = false;
}

void MenuController::openConsoleWithScript(Script script) {
  reloadConsole();
  consoleController()->setAutoImport(false);
  stackViewController()->push(consoleController());
  consoleController()->autoImportScript(script, true);
  m_reloadConsoleWhenBecomingFirstResponder = true;
}

void MenuController::scriptContentEditionDidFinish() {
  reloadConsole();
}

int MenuController::numberOfRows() {
  return m_scriptStore->numberOfScripts() + m_shouldDisplayAddScriptRow;
}

void MenuController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) {
  if (i == 0 && j < m_scriptStore->numberOfScripts()) {
    willDisplayScriptTitleCellForIndex(cell, j);
  }
  static_cast<EvenOddCell *>(cell)->setEven(j%2 == 0);
  cell->setHighlighted(i == selectedColumn() && j == selectedRow());
}

KDCoordinate MenuController::columnWidth(int i) {
  switch (i) {
    case 0:
      return m_selectableTableView.bounds().width()-k_parametersColumnWidth;
    case 1:
      return k_parametersColumnWidth;
    default:
      assert(false);
      return 0;
  }
}

KDCoordinate MenuController::cumulatedWidthFromIndex(int i) {
  switch (i) {
    case 0:
      return 0;
    case 1:
      return m_selectableTableView.bounds().width()-k_parametersColumnWidth;
    case 2:
      return m_selectableTableView.bounds().width();
    default:
      assert(false);
      return 0;
  }
}

KDCoordinate MenuController::cumulatedHeightFromIndex(int j) {
  return Metric::StoreRowHeight * j;
}

int MenuController::indexFromCumulatedWidth(KDCoordinate offsetX) {
  if (offsetX <= m_selectableTableView.bounds().width()-k_parametersColumnWidth) {
    return 0;
  }
  if (offsetX <= m_selectableTableView.bounds().width()) {
    return 1;
  }
  else {
    return 2;
  }
  assert(false);
  return 0;
}

int MenuController::indexFromCumulatedHeight(KDCoordinate offsetY) {
  if (Metric::StoreRowHeight == 0) {
    return 0;
  }
  return (offsetY - 1) / Metric::StoreRowHeight;
}

HighlightCell * MenuController::reusableCell(int index, int type) {
  assert(index >= 0);
  if (type == ScriptCellType) {
    assert(index >=0 && index < k_maxNumberOfDisplayableScriptCells);
    return &m_scriptCells[index];
  }
  if (type == ScriptParameterCellType) {
    assert(index >=0 && index < k_maxNumberOfDisplayableScriptCells);
    return &m_scriptParameterCells[index];
  }
  if (type == AddScriptCellType) {
    assert(index == 0);
    return &m_addNewScriptCell;
  }
  if(type == EmptyCellType) {
    return &m_emptyCell;
  }
  assert(false);
  return nullptr;
}

int MenuController::reusableCellCount(int type) {
  if (type == AddScriptCellType) {
    return 1;
  }
  if (type == ScriptCellType || type == ScriptParameterCellType) {
    return k_maxNumberOfDisplayableScriptCells;
  }
  if (type == EmptyCellType) {
    return 1;
  }
  assert(false);
  return 0;
}

int MenuController::typeAtLocation(int i, int j) {
  assert(i >= 0 && i < numberOfColumns());
  assert(j >= 0 && j < numberOfRows());
  if (i == 0) {
    if (j == numberOfRows()-1 && m_shouldDisplayAddScriptRow) {
      return AddScriptCellType;
    }
    return ScriptCellType;
  }
  assert(i == 1);
  if (j == numberOfRows()-1 && m_shouldDisplayAddScriptRow) {
    return EmptyCellType;
  }
  return ScriptParameterCellType;
}

void MenuController::willDisplayScriptTitleCellForIndex(HighlightCell * cell, int index) {
  assert(index >= 0 && index < m_scriptStore->numberOfScripts());
  (static_cast<ScriptNameCell *>(cell))->textField()->setText(m_scriptStore->scriptAtIndex(index).fullName());
}

void MenuController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) {
  if (selectedRow() == numberOfRows() - 1 && selectedColumn() == 1 && m_shouldDisplayAddScriptRow) {
    t->selectCellAtLocation(0, numberOfRows()-1);
  }
}

bool MenuController::textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) {
  return event == Ion::Events::OK || event == Ion::Events::EXE
    || event == Ion::Events::Down || event == Ion::Events::Up;
}

bool MenuController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) {
  if (event == Ion::Events::Right && textField->isEditing() && textField->cursorLocation() == textField->draftTextLength()) {
    return true;
  }
  if (event == Ion::Events::Clear && textField->isEditing()) {
    constexpr size_t k_bufferSize = 4;
    char buffer[k_bufferSize] = {'.', 0, 0, 0};
    assert(k_bufferSize >= 1 + strlen(ScriptStore::k_scriptExtension) + 1);
    strlcpy(&buffer[1], ScriptStore::k_scriptExtension, strlen(ScriptStore::k_scriptExtension) + 1);
    textField->setText(buffer);
    textField->setCursorLocation(0);
    return true;
  }
  return false;
}

bool MenuController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) {
  const char * newName;
  static constexpr int bufferSize = Script::k_defaultScriptNameMaxSize + 1 + ScriptStore::k_scriptExtensionLength; //"script99" + "." + "py"

  char numberedDefaultName[bufferSize];
  if (strlen(text) > 1 + strlen(ScriptStore::k_scriptExtension)) {
    newName = text;
  } else {
    // The user entered an empty name. Use a numbered default script name.
    bool foundDefaultName = Script::DefaultName(numberedDefaultName, Script::k_defaultScriptNameMaxSize);
    int defaultNameLength = strlen(numberedDefaultName);
    numberedDefaultName[defaultNameLength++] = '.';
    strlcpy(&numberedDefaultName[defaultNameLength], ScriptStore::k_scriptExtension, bufferSize - defaultNameLength);
    /* If there are already scripts named script1.py, script2.py,... until
     * Script::k_maxNumberOfDefaultScriptNames, we want to write the last tried
     * default name and let the user modify it. */
    if (!foundDefaultName) {
      textField->setText(numberedDefaultName);
      textField->setCursorLocation(defaultNameLength);
    }
    newName = const_cast<const char *>(numberedDefaultName);
  }
  Script::ErrorStatus error = Script::nameCompliant(newName) ? m_scriptStore->scriptAtIndex(m_selectableTableView.selectedRow()).setName(newName) : Script::ErrorStatus::NonCompliantName;
  if (error == Script::ErrorStatus::None) {
    updateAddScriptRowDisplay();
    textField->setText(newName);
    int currentRow = m_selectableTableView.selectedRow();
    if (event == Ion::Events::Down && currentRow < numberOfRows() - 1) {
      m_selectableTableView.selectCellAtLocation(m_selectableTableView.selectedColumn(), currentRow + 1);
    } else if (event == Ion::Events::Up && currentRow > 0) {
      m_selectableTableView.selectCellAtLocation(m_selectableTableView.selectedColumn(), currentRow - 1);
    }
    m_selectableTableView.selectedCell()->setHighlighted(true);
    reloadConsole();
    app()->setFirstResponder(&m_selectableTableView);
    static_cast<AppsContainer *>(const_cast<Container *>(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default);
    return true;
  } else if (error == Script::ErrorStatus::NameTaken) {
    app()->displayWarning(I18n::Message::NameTaken);
  } else if (error == Script::ErrorStatus::NonCompliantName) {
    app()->displayWarning(I18n::Message::AllowedCharactersaz09, I18n::Message::NameCannotStartWithNumber);
  } else {
    assert(error == Script::ErrorStatus::NotEnoughSpaceAvailable);
    app()->displayWarning(I18n::Message::NameTooLong);
  }
  return false;
}

bool MenuController::textFieldDidHandleEvent(TextField * textField, bool returnValue, bool textSizeDidChange) {
  int scriptExtensionLength = 1 + strlen(ScriptStore::k_scriptExtension);
  if (textField->isEditing() && textField->cursorLocation() > textField->draftTextLength() - scriptExtensionLength) {
    textField->setCursorLocation(textField->draftTextLength() - scriptExtensionLength);
  }
  return returnValue;
}

void MenuController::addScript() {
  Script::ErrorStatus error = m_scriptStore->addNewScript();
  if (error == Script::ErrorStatus::None) {
    updateAddScriptRowDisplay();
    renameSelectedScript();
    return;
  }
  assert(false); // Adding a new script is called when !m_scriptStore.isFull() which guarantees that the available space in the storage is big enough
}

void MenuController::configureScript() {
  assert(m_selectableTableView.selectedRow() >= 0);
  assert(m_selectableTableView.selectedRow() < m_scriptStore->numberOfScripts());
  m_scriptParameterController.setScript(m_scriptStore->scriptAtIndex(m_selectableTableView.selectedRow()));
  stackViewController()->push(&m_scriptParameterController);
}

void MenuController::editScriptAtIndex(int scriptIndex) {
  assert(scriptIndex >=0 && scriptIndex < m_scriptStore->numberOfScripts());
  Script script = m_scriptStore->scriptAtIndex(scriptIndex);
  m_editorController.setScript(script);
  stackViewController()->push(&m_editorController);
}

void MenuController::updateAddScriptRowDisplay() {
  m_shouldDisplayAddScriptRow = !m_scriptStore->isFull();
  m_selectableTableView.reloadData();
}

bool MenuController::privateTextFieldDidAbortEditing(TextField * textField, bool menuControllerStaysInResponderChain) {
  /* If menuControllerStaysInResponderChain is false, we do not want to use
   * methods that might call setFirstResponder, because we might be in the
   * middle of another setFirstResponder call. */
  Script script = m_scriptStore->scriptAtIndex(m_selectableTableView.selectedRow());
  const char * scriptName = script.fullName();
  if (strlen(scriptName) <= 1 + strlen(ScriptStore::k_scriptExtension)) {
    // The previous text was an empty name. Use a numbered default script name.
    char numberedDefaultName[Script::k_defaultScriptNameMaxSize];
    bool foundDefaultName = Script::DefaultName(numberedDefaultName, Script::k_defaultScriptNameMaxSize);
    if (!foundDefaultName) {
      // If we did not find a default name, delete the script
      deleteScript(script);
      return true;
    }
    Script::ErrorStatus error = script.setBaseNameWithExtension(numberedDefaultName, ScriptStore::k_scriptExtension);
    scriptName = m_scriptStore->scriptAtIndex(m_selectableTableView.selectedRow()).fullName();
    /* Because we use the numbered default name, the name should not be
     * already taken. Plus, the script could be added only if the storage has
     * enough available space to add a script named 'script99.py' */
    (void) error; // Silence the "variable unused" warning if assertions are not enabled
    assert(error == Script::ErrorStatus::None);
    if (menuControllerStaysInResponderChain) {
      updateAddScriptRowDisplay();
    }
  }
  textField->setText(scriptName);
  if (menuControllerStaysInResponderChain) {
    m_selectableTableView.selectCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow());
    app()->setFirstResponder(&m_selectableTableView);
  }
  static_cast<AppsContainer *>(const_cast<Container *>(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default);
  return true;
}

}
