#include "variable_box_controller.h"
#include "shared/global_context.h"
#include "shared/poincare_helpers.h"
#include "shared/storage_function.h"
#include "shared/storage_cartesian_function.h"
#include "graph/storage_cartesian_function_store.h"
#include "constant.h"
#include <escher/metric.h>
#include <assert.h>
#include <poincare/matrix_layout.h>
#include <poincare/layout_helper.h>

using namespace Poincare;
using namespace Shared;
using namespace Ion;

VariableBoxController::VariableBoxController() :
  NestedMenuController(nullptr, I18n::Message::Variables),
  m_currentPage(Page::RootMenu),
  m_lockPageDelete(Page::RootMenu),
  m_firstMemoizedLayoutIndex(0)
{
}

void VariableBoxController::viewWillAppear() {
  assert(m_currentPage == Page::RootMenu);
  NestedMenuController::viewWillAppear();
}

void VariableBoxController::viewDidDisappear() {
  if (isDisplayingEmptyController()) {
    pop();
  }

  NestedMenuController::viewDidDisappear();

  /* NestedMenuController::viewDidDisappear might need cell heights, which would
   * use the VariableBoxController cell heights memoization. We thus reset the
   * VariableBoxController layouts only after calling the parent's
   * viewDidDisappear. */

  // Tidy the layouts displayed in the VariableBoxController to clean TreePool
  for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) {
    m_leafCells[i].setLayout(Layout());
    m_leafCells[i].setAccessoryLayout(Layout());
  }
  /* We reset the page when view disappears rather than when it appears because
   * subview layout is done before viewWillAppear. If the page at that point is
   * wrong, the memoized layouts are going be filled with wrong layouts. */
  setPage(Page::RootMenu);
}

bool VariableBoxController::handleEvent(Ion::Events::Event event) {
  /* We do not want to handle backspace event if:
   * - On the root menu page
   *   The deletion on the current page is locked
   * - The empty controller is displayed
   */
  if (event == Ion::Events::Backspace && m_currentPage != Page::RootMenu && m_lockPageDelete != m_currentPage && !isDisplayingEmptyController()) {
    int rowIndex = selectedRow();
    m_selectableTableView.deselectTable();
    destroyRecordAtRowIndex(rowIndex);
    int newSelectedRow = rowIndex >= numberOfRows() ? numberOfRows()-1 : rowIndex;
    selectCellAtLocation(selectedColumn(), newSelectedRow);
    m_selectableTableView.reloadData();
    displayEmptyController();
    return true;
  }
  return NestedMenuController::handleEvent(event);
}

int VariableBoxController::numberOfRows() {
  switch (m_currentPage) {
    case Page::RootMenu:
      return k_numberOfMenuRows;
    case Page::Expression:
      return Storage::sharedStorage()->numberOfRecordsWithExtension(GlobalContext::expExtension);
    case Page::Function:
      return Storage::sharedStorage()->numberOfRecordsWithExtension(GlobalContext::funcExtension);
    default:
      return 0;
  }
}

int VariableBoxController::reusableCellCount(int type) {
  assert(type < 2);
  if (type == 0) {
    return k_maxNumberOfDisplayedRows;
  }
  return k_numberOfMenuRows;
}

void VariableBoxController::willDisplayCellForIndex(HighlightCell * cell, int index) {
  if (m_currentPage == Page::RootMenu) {
    I18n::Message label = nodeLabelAtIndex(index);
    MessageTableCell * myCell = (MessageTableCell *)cell;
    myCell->setMessage(label);
    myCell->reloadCell();
    return;
  }
  ExpressionTableCellWithExpression * myCell = (ExpressionTableCellWithExpression *)cell;
  Storage::Record record = recordAtIndex(index);
  assert(Shared::StorageFunction::k_maxNameWithArgumentSize > SymbolAbstract::k_maxNameSize);
  char symbolName[Shared::StorageFunction::k_maxNameWithArgumentSize];
  size_t symbolLength = 0;
  if (m_currentPage == Page::Expression) {
    symbolLength = SymbolAbstract::TruncateExtension(symbolName, record.fullName(), SymbolAbstract::k_maxNameSize);
  } else {
    assert(m_currentPage == Page::Function);
    StorageCartesianFunction f(record);
    symbolLength = f.nameWithArgument(
        symbolName,
        Shared::StorageFunction::k_maxNameWithArgumentSize,
        Graph::StorageCartesianFunctionStore::Symbol());
  }
  Layout symbolLayout = LayoutHelper::String(symbolName, symbolLength);
  myCell->setLayout(symbolLayout);
  myCell->setAccessoryLayout(expressionLayoutForRecord(record, index));
  myCell->reloadCell();
}

KDCoordinate VariableBoxController::rowHeight(int index) {
  if (m_currentPage != Page::RootMenu) {
    Layout layoutR = expressionLayoutForRecord(recordAtIndex(index), index);
    if (!layoutR.isUninitialized()) {
      return max(layoutR.layoutSize().height()+k_leafMargin, Metric::ToolboxRowHeight);
    }
  }
  return NestedMenuController::rowHeight(index);
}

int VariableBoxController::typeAtLocation(int i, int j) {
  if (m_currentPage == Page::RootMenu) {
    return 1;
  }
  return 0;
}

ExpressionTableCellWithExpression * VariableBoxController::leafCellAtIndex(int index) {
  assert(index >= 0 && index < k_maxNumberOfDisplayedRows);
  return &m_leafCells[index];
}

MessageTableCellWithChevron * VariableBoxController::nodeCellAtIndex(int index) {
  assert(index >= 0 && index < k_numberOfMenuRows);
  return &m_nodeCells[index];
}

VariableBoxController::Page VariableBoxController::pageAtIndex(int index) {
  Page pages[2] = {Page::Expression, Page::Function};
  return pages[index];
}

void VariableBoxController::setPage(Page page) {
  m_currentPage = page;
  resetMemoization();
}

bool VariableBoxController::selectSubMenu(int selectedRow) {
  m_selectableTableView.deselectTable();
  setPage(pageAtIndex(selectedRow));
  bool selectSubMenu = NestedMenuController::selectSubMenu(selectedRow);
  if (displayEmptyController()) {
    return true;
  }
  return selectSubMenu;
}

bool VariableBoxController::returnToPreviousMenu() {
  if (isDisplayingEmptyController()) {
    pop();
  } else {
    m_selectableTableView.deselectTable();
  }
  setPage(Page::RootMenu);
  return NestedMenuController::returnToPreviousMenu();
}

bool VariableBoxController::selectLeaf(int selectedRow) {
  if (isDisplayingEmptyController()) {
    /* We do not want to handle OK/EXE events in that case. */
    return false;
  }

  // Deselect the table
  assert(selectedRow >= 0 && selectedRow < numberOfRows());
  m_selectableTableView.deselectTable();

  // Get the name text to insert
  Storage::Record record = recordAtIndex(selectedRow);
  assert(Shared::StorageFunction::k_maxNameWithArgumentSize > 0);
  assert(Shared::StorageFunction::k_maxNameWithArgumentSize > SymbolAbstract::k_maxNameSize);
  constexpr size_t nameToHandleMaxSize = Shared::StorageFunction::k_maxNameWithArgumentSize;
  char nameToHandle[nameToHandleMaxSize];
  size_t nameLength = SymbolAbstract::TruncateExtension(nameToHandle, record.fullName(), nameToHandleMaxSize);

  if (m_currentPage == Page::Function) {
    // Add parentheses to a function name
    assert(nameLength < nameToHandleMaxSize);
    nameToHandle[nameLength++] = '(';
    assert(nameLength < nameToHandleMaxSize);
    nameToHandle[nameLength++] = Ion::Charset::Empty;
    assert(nameLength < nameToHandleMaxSize);
    nameToHandle[nameLength++] = ')';
    assert(nameLength < nameToHandleMaxSize);
    nameToHandle[nameLength] = 0;
  }

  // Handle the text
  sender()->handleEventWithText(nameToHandle);
  app()->dismissModalViewController();
  return true;
}

I18n::Message VariableBoxController::nodeLabelAtIndex(int index) {
  assert(m_currentPage == Page::RootMenu);
  I18n::Message labels[2] = {I18n::Message::Expressions, I18n::Message::Functions};
  return labels[index];
}

Layout VariableBoxController::expressionLayoutForRecord(Storage::Record record, int index) {
  assert(m_currentPage != Page::RootMenu);
  assert(index >= 0);
  if (index >= m_firstMemoizedLayoutIndex+k_maxNumberOfDisplayedRows || index < m_firstMemoizedLayoutIndex) {
    // Change range of layout memoization
    int deltaIndex = index >= m_firstMemoizedLayoutIndex + k_maxNumberOfDisplayedRows ? index - k_maxNumberOfDisplayedRows + 1 - m_firstMemoizedLayoutIndex : index - m_firstMemoizedLayoutIndex;
    for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) {
      int j = deltaIndex + i;
      m_layouts[i] = (j >= m_firstMemoizedLayoutIndex && j < k_maxNumberOfDisplayedRows) ? m_layouts[j] : Layout();
    }
    m_firstMemoizedLayoutIndex += deltaIndex;
    assert(m_firstMemoizedLayoutIndex >= 0);
  }
  assert(index >= m_firstMemoizedLayoutIndex && index < m_firstMemoizedLayoutIndex + k_maxNumberOfDisplayedRows);
  if (m_layouts[index-m_firstMemoizedLayoutIndex].isUninitialized()) {
    m_layouts[index-m_firstMemoizedLayoutIndex] = GlobalContext::ExpressionFromRecord(record).createLayout(Poincare::Preferences::sharedPreferences()->displayMode(), Constant::ShortNumberOfSignificantDigits);
  }
  return m_layouts[index-m_firstMemoizedLayoutIndex];
}

const char * VariableBoxController::extension() const {
  assert(m_currentPage != Page::RootMenu);
  return m_currentPage == Page::Function ? GlobalContext::funcExtension : GlobalContext::expExtension;
}

Storage::Record VariableBoxController::recordAtIndex(int rowIndex) {
  assert(m_currentPage != Page::RootMenu);
  assert(!Storage::sharedStorage()->recordWithExtensionAtIndex(extension(), rowIndex).isNull());
  return Storage::sharedStorage()->recordWithExtensionAtIndex(extension(), rowIndex);
}

bool VariableBoxController::displayEmptyController() {
  assert(!isDisplayingEmptyController());
  // If the content is empty, we push above an empty controller.
  if (numberOfRows() == 0) {
    m_emptyViewController.setType((VariableBoxEmptyController::Type)m_currentPage);
    push(&m_emptyViewController);
    return true;
  }
  return false;
}

void VariableBoxController::resetMemoization() {
  for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) {
    m_layouts[i] = Layout();
  }
  m_firstMemoizedLayoutIndex = 0;
}

void VariableBoxController::destroyRecordAtRowIndex(int rowIndex) {
  // Destroy the record
  recordAtIndex(rowIndex).destroy();
  // Shift the memoization
  assert(rowIndex >= m_firstMemoizedLayoutIndex && rowIndex < m_firstMemoizedLayoutIndex + k_maxNumberOfDisplayedRows);
  for (int i = rowIndex - m_firstMemoizedLayoutIndex; i < k_maxNumberOfDisplayedRows - 1; i++) {
    m_layouts[i] = m_layouts[i+1];
  }
  m_layouts[k_maxNumberOfDisplayedRows - 1] = Layout();
}
