// -*- mode: c++; c-file-style: "linux"; c-basic-offset: 2; indent-tabs-mode:
// nil -*-
//
//  Copyright (C) 2004-2015 Andrej Vodopivec <andrej.vodopivec@gmail.com>
//            (C) 2014-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 SumCell

  SumCell is the Cell type that represents maxima's <code>sum()</code>,
  <code>lsum</code> and <code>product()</code>
  commands.
*/

#include <algorithm>
#include "SumCell.h"
#include "CellImpl.h"
#include "TextCell.h"
#include "sumSign_svg.h"
#include "CellList.h"
#include "EditorCell.h"
#include "ParenCell.h"

SumCell::SumCell(GroupCell *group, Configuration *config, 
                 std::unique_ptr<Cell> &&under, std::unique_ptr<Cell> &&over,
                 std::unique_ptr<Cell> &&base)
  : Cell(group, config),
    m_paren(std::make_unique<ParenCell>(group, config, std::move(base))),
    m_var(under->Copy(group)), m_start(MakeStart(under.get())),
    m_over(std::move(over)), m_under(std::move(under)) {
  InitBitFields_SumCell();
  if (!m_over)
    m_over = std::make_unique<TextCell>(group, config);
  wxASSERT(Base());
  MakeBreakUpCells();
}

SumCell::SumCell(GroupCell *group, const SumCell &cell)
  : SumCell(group, cell.m_configuration,
            CopyList(group, cell.m_under.get()),
            CopyList(group, cell.m_over.get()),
            CopyList(group, cell.Base())) {
  CopyCommonData(cell);
  m_altCopyText = cell.m_altCopyText;
}

DEFINE_CELL(SumCell)

void SumCell::MakeBreakUpCells() {
  if (m_open)
    return;

  m_comma1 = std::make_unique<TextCell>(m_group, m_configuration, wxS(","));
  m_comma2 = std::make_unique<TextCell>(m_group, m_configuration, wxS(","));
  m_comma3 = std::make_unique<TextCell>(m_group, m_configuration, wxS(","));
  m_open = std::make_unique<TextCell>(m_group, m_configuration, GetMaximaCommandName());
  m_close = std::make_unique<TextCell>(m_group, m_configuration, wxS(")"));
}

ParenCell *SumCell::Paren() const {
  return static_cast<ParenCell *>(m_paren.get());
}

Cell *SumCell::Base() const { return Paren() ? Paren()->GetInner() : nullptr; }

Cell *SumCell::DisplayedBase() const {
  return m_displayParen ? static_cast<Cell *>(m_paren.get()) : Base();
}

std::unique_ptr<Cell> SumCell::MakeStart(Cell *under) const {
  // m_under consists of a list of cells:
  //  The variable name, that can be more than one cell if there is a subscript.
  //  1 cell containing the text "in" or "=" (TODO: That's heuristics. Is there
  //  a better, but
  //                                          backwards-compatible way for
  //                                          this?)
  //  And the rest contains the lower limit.

  auto const &list = OnList(under);
  auto it = std::find_if(list.begin(), list.end(), [](const Cell &start) {
    auto const &value = start.GetValue();
    return (value == wxS("in")) || (value == wxS("="));
  });

  if (it != list.end() && std::next(it) != list.end())
    return std::next(it)->CopyList(GetGroup());

  return std::make_unique<TextCell>(m_group, m_configuration);
}

const wxSize SumCell::GetSymbolSize() const
{
  wxSize signSize;
  // A sane height for the sum sign
  signSize.y = Scale_Px(40.0);
  // The width of the sum sign is defined by its height and aspect ratio
  signSize.x = 13 * signSize.y / 15;
  return signSize;
}

const wxString SumCell::GetMaximaCommandName() const {
  wxString s = wxS("sum(");
  if (m_over->ListToString() == wxEmptyString)
    s = wxS("lsum(");
  
  return s;
}

const wxString SumCell::GetSvgSymbolData() const
{
  return(m_svgSumSign);
}

//! What maxima command name corresponds to this cell?
const wxString SumCell::GetMatlabCommandName() const
{
  return wxS("sum(");
}

const wxString SumCell::GetLaTeXCommandName() const
{
  return wxS("\\sum");
}

const wxString SumCell::GetUnicodeSymbol() const
{
    return wxS("\u2211");
}

const wxString SumCell::GetXMLType() const
{
  wxString type(wxS("sum"));
  if (m_over->ListToString() == wxEmptyString)
      type = wxS("lsum");
  return type;
}

void SumCell::Recalculate(AFontSize fontsize) const {
  bool changed = false;
  changed |= m_paren->RecalculateList(fontsize);
  changed |= m_start->RecalculateList(fontsize);
  changed |= m_var->RecalculateList(fontsize);
  changed |= m_comma1->RecalculateList(fontsize);
  changed |= m_comma2->RecalculateList(fontsize);
  changed |= m_comma3->RecalculateList(fontsize);
  changed |= m_open->RecalculateList(fontsize);
  changed |= m_close->RecalculateList(fontsize);

  if (IsBrokenIntoLines()) {
    changed |= m_over->RecalculateList(fontsize);
    changed |= m_under->RecalculateList(fontsize);
  } else {
    changed |= m_over->RecalculateList({MC_MIN_SIZE, fontsize - SUM_DEC});
    changed |= m_under->RecalculateList({MC_MIN_SIZE, fontsize - SUM_DEC});
  }

  if (changed || NeedsRecalculation(fontsize)) {
    Cell::Recalculate(fontsize);

    if (IsBrokenIntoLines()) {
      m_width = 0;
      m_center = 0;
      m_height = 0;
    } else {
      m_signSize = GetSymbolSize();
      m_width = std::max(std::max(m_signSize.x, m_over->SumOfWidths()),
                         m_under->SumOfWidths()) + DisplayedBase()->SumOfWidths();

      m_center = std::max(m_signSize.y / 2 + Scale_Px(2)
                          + m_over->GetHeightList(),
                          DisplayedBase()->GetCenterList());
      m_height = m_center +
        std::max(m_under->GetHeightList() +  Scale_Px(2) + m_signSize.y / 2,
                 DisplayedBase()->GetMaxDrop());
    }
  }
}

void SumCell::SetCurrentPoint(wxPoint point) const {
  Cell::SetCurrentPoint(point);
  if (IsBrokenIntoLines())
    return;

  wxPoint under(point), over(point), base(point);
  wxCoord signCenter_horizontal = std::max(std::max(m_signSize.x, m_over->SumOfWidths()),
                                           m_under->SumOfWidths()) / 2;

  under.x += signCenter_horizontal - m_under->SumOfWidths() / 2;
  under.y += + m_signSize.y / 2 + Scale_Px(2) + m_under->GetCenterList();
  m_under->SetCurrentPointList(under);

  over.x += signCenter_horizontal - m_over->SumOfWidths() / 2;
  over.y -= m_signSize.y / 2 + m_over->GetMaxDrop() + Scale_Px(2);
  m_over->SetCurrentPointList(over);

  base.x += std::max(std::max(m_signSize.x, m_over->SumOfWidths()),
                       m_under->SumOfWidths());
  DisplayedBase()->SetCurrentPointList(base);
}

void SumCell::Draw(wxDC *dc, wxDC *antialiassingDC) {
  Cell::Draw(dc, antialiassingDC);

  if (DrawThisCell()) {
    wxPoint point = m_currentPoint;
    wxPoint sign(point);

    wxCoord signCenter_horizontal = std::max(std::max(m_signSize.x, m_over->SumOfWidths()),
                                             m_under->SumOfWidths()) / 2;

    m_under->DrawList(dc, antialiassingDC);
    m_over->DrawList(dc, antialiassingDC);

    sign.x += signCenter_horizontal - m_signSize.x / 2;
    sign.y -= .5 * m_signSize.y;
    antialiassingDC->DrawBitmap(BitmapFromSVG(GetSvgSymbolData(),
                                              wxSize(m_signSize.x, m_signSize.y)),
                                sign.x, sign.y, true);    
    DisplayedBase()->DrawList(dc, antialiassingDC);
  }
}

wxString SumCell::ToString() const {
  if (m_altCopyText != wxEmptyString)
    return m_altCopyText;

  wxString s = GetMaximaCommandName();

  s += Base()->ListToString();

  const Cell *tmp  = m_under.get();
  wxString var = tmp->ToString();
  wxString from;
  tmp = tmp->GetNext();
  if (tmp != NULL) {
    tmp = tmp->GetNext();
    if (tmp != NULL)
      from = tmp->ListToString();
  }
  wxString to = m_over->ListToString();
  s += wxS(",") + var + wxS(",") + from;
  if (to != wxEmptyString)
    s += wxS(",") + to + wxS(")");
  else
    s = wxS("l") + s + wxS(")");
  return s;
}

wxString SumCell::ToMatlab() const {
  wxString s = GetMatlabCommandName();
  s += Base()->ListToMatlab();

  const Cell *tmp = m_under.get();
  wxString var = tmp->ToMatlab();
  wxString from;
  tmp = tmp->GetNext();
  if (tmp != NULL) {
    tmp = tmp->GetNext();
    if (tmp != NULL)
      from = tmp->ListToMatlab();
  }
  wxString to = m_over->ListToMatlab();
  s += wxS(",") + var + wxS(",") + from;
  if (to != wxEmptyString)
    s += wxS(",") + to + wxS(")");
  else
    s = wxS("l") + s + wxS(")");
  return s;
}

wxString SumCell::ToTeX() const {
  wxString s = GetLaTeXCommandName();
  s += wxS("_{") + m_under->ListToTeX() + wxS("}");
  wxString to = m_over->ListToTeX();
  if (to.Length())
    s += wxS("^{") + to + wxS("}");

  s += wxS("{\\left. ");
  s += Base()->ListToTeX();
  s += wxS("\\right.}");
  return s;
}

wxString SumCell::ToOMML() const {
  wxString base = Base()->ListToOMML();
  wxString from = m_under ? m_under->ListToOMML() : wxString{};
  wxString to = m_over ? m_over->ListToOMML() : wxString{};

  wxString retval;

  retval = wxS("<m:nary><m:naryPr><m:chr>");
  retval += GetUnicodeSymbol();

  retval += wxS("</m:chr></m:naryPr>");
  if (from != wxEmptyString)
    retval += wxS("<m:sub>") + from + wxS("</m:sub>");
  if (to != wxEmptyString)
    retval += wxS("<m:sup>") + to + wxS("</m:sup>");
  retval += wxS("<m:e>") + base + wxS("</m:e></m:nary>");

  return retval;
}
  
wxString SumCell::ToXML() const {
  wxString type = GetXMLType();
  wxString flags = GetXMLFlags();

  return wxS("<sm type=\"") + type + "\"" + flags + wxS("><r>") +
    m_under->ListToXML() + _T("</r><r>") + m_over->ListToXML() +
    _T("</r><r>") + Base()->ListToXML() + _T("</r></sm>");
}

wxString SumCell::ToMathML() const {
  wxString base = Base()->ListToMathML();

  wxString from;
  if (m_under)
    from = m_under->ListToMathML();

  wxString to;
  if (m_over)
    to = m_over->ListToMathML();

  wxString retval;

  if (from.IsEmpty() && to.IsEmpty())
    retval = wxS("<mo>" + GetUnicodeSymbol() + "</mo>") + base;
  if (from.IsEmpty() && !to.IsEmpty())
    retval = wxS("<mover><mo>" + GetUnicodeSymbol() + "</mo>") + to + wxS("</mover>") + base;
  if (!from.IsEmpty() && to.IsEmpty())
    retval =
      wxS("<munder><mo>" + GetUnicodeSymbol() + "</mo>") + from + wxS("</munder>") + base;
  if (!from.IsEmpty() && !to.IsEmpty())
    retval = wxS("<munderover><mo>" + GetUnicodeSymbol() + "</mo>") + from + to +
      wxS("</munderover>") + base;
  return (wxS("<mrow>") + retval + wxS("</mrow>"));
}

void SumCell::Unbreak() const {
  m_displayParen = true;
  Cell::Unbreak();
}

bool SumCell::BreakUp() const {
  if (IsBrokenIntoLines())
    return false;

  Cell::BreakUpAndMark();
  m_displayParen = false;

  m_close->SetNextToDraw(m_nextToDraw);
  m_nextToDraw = m_open;
  m_open->last()->SetNextToDraw(DisplayedBase());
  DisplayedBase()->last()->SetNextToDraw(m_comma1);
  m_comma1->last()->SetNextToDraw(m_var);
  m_var->last()->SetNextToDraw(m_comma2);
  m_comma2->last()->SetNextToDraw(m_start);
  // The first cell of m_var should normally be a "d"
  if (m_over->ToString().IsEmpty())
    m_start->last()->SetNextToDraw(m_close);
  else {
    m_start->last()->SetNextToDraw(m_comma3);
    m_comma3->last()->SetNextToDraw(m_over);
    m_over->last()->SetNextToDraw(m_close);
  }
  return true;
}

void SumCell::SetNextToDraw(Cell *next) const {
  if (IsBrokenIntoLines())
    m_close->SetNextToDraw(next);
  else
    m_nextToDraw = next;
}

const wxString SumCell::m_svgSumSign(reinterpret_cast<const char*>(SUMSIGN));
