// -*- mode: c++; c-file-style: "linux"; c-basic-offset: 2; indent-tabs-mode:
// nil -*-
//
//  Copyright (C) 2009      Ziga Lenarcic <zigalenarcic@users.sourceforge.net>
//            (C) 2012      Doug Ilijev <doug.ilijev@gmail.com>
//            (C) 2015-2018 Gunter Königsmann <wxMaxima@physikbuch.de>
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
//  SPDX-License-Identifier: GPL-2.0+

/*! \file
  This file defines the class EvaluationQueue

  This queue contains all code cells that still have to be passed to maxima.
*/

#include "EvaluationQueue.h"
#include "MaximaTokenizer.h"

bool EvaluationQueue::Empty() const {
  return (m_queue.empty()) && (m_commands.empty());
}

EvaluationQueue::EvaluationQueue() {
  m_size = 0;
  m_workingGroupChanged = false;
}

void EvaluationQueue::Clear() {
  m_queue.clear();
  m_size = 0;
  m_commands.clear();
  m_workingGroupChanged = false;
}

bool EvaluationQueue::IsInQueue(GroupCell *gr) const {
  return std::find(m_queue.begin(), m_queue.end(), gr) != m_queue.end();
}

void EvaluationQueue::Remove(GroupCell *gr) {
  bool removeFirst = IsLastInQueue(gr);
  auto pos = std::find(m_queue.begin(), m_queue.end(), gr);
  if (pos != m_queue.end())
    m_queue.erase(pos);
  m_size = m_queue.size();
  if (removeFirst) {
    m_commands.clear();
    if (!m_queue.empty())
      AddTokens(gr);
  }
}

void EvaluationQueue::AddToQueue(GroupCell *gr) {
  if (gr == NULL)
    return;

  if (gr->GetGroupType() != GC_TYPE_CODE ||
      gr->GetEditable() == NULL) // don't add cells which can't be evaluated
    return;

  if (m_queue.empty()) {
    AddTokens(gr);
    m_workingGroupChanged = true;
  }
  m_size++;
  m_queue.push_back(CellPtr<GroupCell>(gr));
}

/**
 * Add the tree of hidden cells to the EQ by recursively adding cells'
 * hidden branches to the EQ.
 */
void EvaluationQueue::AddHiddenTreeToQueue(const GroupCell *gr) {
  if (!gr)
    return; // caller should check, but just in case

  for (auto &cell : OnList(gr->GetHiddenTree())) {
    AddToQueue(&cell);
    AddHiddenTreeToQueue(&cell);
  }
}

void EvaluationQueue::RemoveFirst() {
  if (!m_commands.empty()) {
    m_workingGroupChanged = false;
    m_commands.erase(m_commands.begin());
  } else {
    do {
      if (m_queue.empty())
        return;

      m_queue.erase(m_queue.begin());
      m_size--;
      AddTokens(GetCell());
    } while (m_commands.empty() && (!m_queue.empty()));
    m_workingGroupChanged = true;
  }
}

void EvaluationQueue::AddTokens(const GroupCell *cell) {
  if (cell == NULL)
    return;
  wxString token;
  int index = 0;
  Configuration *config = cell->GetEditable()->GetConfiguration();
  auto tokens = MaximaTokenizer(cell->GetEditable()->ToString(true), config).PopTokens();
  for (auto const &tok : tokens) {
    const TextStyle itemStyle = tok.GetTextStyle();
    wxString itemText = tok.GetText();
    itemText.Replace(wxS("\u00a0"), " ");
    index += itemText.Length();
    if (itemStyle != TS_CODE_COMMENT)
      token += itemText;

    if (itemStyle == TS_CODE_LISP) {
      token.Trim(true);
      token.Trim(false);
      if (!token.IsEmpty())
        m_commands.emplace_back(token, index);
      token.Clear();
      continue;
    }

    if (itemStyle == TS_CODE_ENDOFLINE) {
      token.Trim(true);
      token.Trim(false);
      wxString trimmedToken = token;
      trimmedToken.Replace(wxS(";"), wxEmptyString);
      trimmedToken.Replace(wxS("$"), wxEmptyString);
      trimmedToken.Trim(true).Trim(false);
      if (!trimmedToken.IsEmpty())
        m_commands.emplace_back(token, index);
      token.Clear();
      continue;
    }
  }
  token.Trim(true);
  token.Trim(false);
  wxString trimmedToken = token;
  trimmedToken.Replace(wxS(";"), wxEmptyString);
  trimmedToken.Replace(wxS("$"), wxEmptyString);
  trimmedToken.Trim(true).Trim(false);
  if (!trimmedToken.IsEmpty())
    m_commands.emplace_back(token, index);
}

GroupCell *EvaluationQueue::GetCell() {
  if (m_queue.empty())
    return NULL;
  else
    return m_queue.front();
}

wxString EvaluationQueue::GetCommand() {
  if (m_commands.empty())
    return {};

  const auto &retval = m_commands.front().GetString();

  m_userLabel.Clear();
  wxString userLabel;

  int colonPos = retval.find(wxS(":"));
  if (colonPos != wxNOT_FOUND && !retval.StartsWith(wxS(":lisp"))) {
    userLabel = retval.Left(colonPos);
    userLabel.Trim(true);
    userLabel.Trim(false);
    if (!userLabel.empty() &&
        (wxIsalpha(userLabel[0]) || (userLabel[0] == wxS('\\')) ||
         (userLabel[0] > 127) || (userLabel[0] == wxS('_')))) {
      for (std::size_t i = 0; i < userLabel.Length(); i++) {
        if (userLabel[i] == wxS('\\'))
          i++;
        else {
          if ((!wxIsalnum(userLabel[i])) && (userLabel[i] != '_') &&
              (userLabel[i] < 128) && (userLabel[i] != '[') &&
              (userLabel[i] != ']')) {
            userLabel.Clear();
            break;
          }
        }
      }
      m_userLabel = userLabel;
    }
  }
  return retval;
}
