// -*- mode: c++; c-file-style: "linux"; c-basic-offset: 2; indent-tabs-mode:
// nil -*-
//
//  Copyright (C) 2007-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 SubSupCell

  SubSubCell is the Cell type that represents a math element with subscript and
  superscript.
*/

#include "SubSupCell.h"
#include "CellImpl.h"
#include "TextCell.h"
#include <wx/config.h>

#define SUBSUP_DEC 3

SubSupCell::SubSupCell(GroupCell *group, Configuration *config,
                       std::unique_ptr<Cell> &&base)
  : Cell(group, config), m_baseCell(std::move(base)) {
  InitBitFields_SubSubCell();
}

SubSupCell::SubSupCell(GroupCell *group, const SubSupCell &cell)
  : SubSupCell(group, cell.m_configuration,
               CopyList(group, cell.m_baseCell.get())) {
  CopyCommonData(cell);
  m_altCopyText = cell.m_altCopyText;
  SetIndex(CopyList(group, cell.m_postSubCell.get()));
  SetExponent(CopyList(group, cell.m_postSupCell.get()));
  SetPreSub(CopyList(group, cell.m_preSubCell.get()));
  SetPreSup(CopyList(group, cell.m_preSupCell.get()));
}

DEFINE_CELL(SubSupCell)

static void RemoveCell(std::vector<Cell *> &cells,
                       std::unique_ptr<Cell> const &cell) {
  cells.erase(std::remove(cells.begin(), cells.end(), cell.get()), cells.end());
}

void SubSupCell::SetPreSup(std::unique_ptr<Cell> &&index) {
  if (!index)
    return;
  RemoveCell(m_scriptCells, m_preSupCell);
  m_preSupCell = std::move(index);
  m_scriptCells.emplace_back(m_preSupCell.get());
}

void SubSupCell::SetPreSub(std::unique_ptr<Cell> &&index) {
  if (!index)
    return;
  RemoveCell(m_scriptCells, m_preSubCell);
  m_preSubCell = std::move(index);
  m_scriptCells.emplace_back(m_preSubCell.get());
}

void SubSupCell::SetPostSup(std::unique_ptr<Cell> &&index) {
  if (!index)
    return;
  RemoveCell(m_scriptCells, m_postSupCell);
  m_postSupCell = std::move(index);
  m_scriptCells.emplace_back(m_postSupCell.get());
}

void SubSupCell::SetPostSub(std::unique_ptr<Cell> &&index) {
  if (!index)
    return;
  RemoveCell(m_scriptCells, m_postSubCell);
  m_postSubCell = std::move(index);
  m_scriptCells.emplace_back(m_postSubCell.get());
}

void SubSupCell::SetIndex(std::unique_ptr<Cell> &&index) {
  if (!index)
    return;
  RemoveCell(m_scriptCells, m_postSubCell);
  m_postSubCell = std::move(index);
}

void SubSupCell::SetExponent(std::unique_ptr<Cell> &&expt) {
  if (!expt)
    return;
  RemoveCell(m_scriptCells, m_postSupCell);
  m_postSupCell = std::move(expt);
}

void SubSupCell::Recalculate(AFontSize const fontsize) const {
  if (NeedsRecalculation(fontsize)) {
    Cell::Recalculate(fontsize);
    AFontSize const smallerFontSize{MC_MIN_SIZE, fontsize - SUBSUP_DEC};

    m_baseCell->RecalculateList(fontsize);

    wxCoord preWidth = 0;
    wxCoord postWidth = 0;
    wxCoord subHeight = 0;
    wxCoord supHeight = 0;
    if (m_postSubCell) {
      m_postSubCell->RecalculateList(smallerFontSize);
      postWidth = m_postSubCell->SumOfWidths();
      subHeight = m_postSubCell->GetHeightList();
    }
    if (m_postSupCell) {
      m_postSupCell->RecalculateList(smallerFontSize);
      postWidth = std::max(postWidth, m_postSupCell->SumOfWidths());
      supHeight = m_postSupCell->GetHeightList();
    }
    if (m_preSubCell) {
      m_preSubCell->RecalculateList(smallerFontSize);
      preWidth = m_preSubCell->SumOfWidths();
      subHeight = std::max(subHeight, m_preSubCell->GetHeightList());
    }
    if (m_preSupCell) {
      m_preSupCell->RecalculateList(smallerFontSize);
      preWidth = std::max(preWidth, m_preSupCell->SumOfWidths());
      supHeight = std::max(supHeight, m_preSupCell->GetHeightList());
    }

    m_width = preWidth + m_baseCell->SumOfWidths() + postWidth;

    wxCoord const rise =
        static_cast<wxCoord>(.8 * Scale_Px(fontsize)) - MC_EXP_INDENT;

    m_center =
        std::max(m_baseCell->GetCenterList(), rise - m_baseCell->GetMaxDrop());
    if (supHeight > 0)
      m_center = std::max(m_center.GetOrElse(0),
                          m_baseCell->GetCenterList() + supHeight - rise);

    m_height = std::max(m_baseCell->GetMaxDrop() + subHeight - rise,
                        m_baseCell->GetMaxDrop()) +
               m_center;
  }
}

/**
 * @brief Pass 2 (Arrange): Positions base and all four possible scripts.
 */
void SubSupCell::SetCurrentPoint(wxPoint point) const {
  Cell::SetCurrentPoint(point);

  wxCoord preWidth = 0;

  // Calculate the width reserved for pre-scripts (presub/presup)
  if (m_preSubCell)
    preWidth = m_preSubCell->SumOfWidths();
  if (m_preSupCell)
    preWidth = std::max(preWidth, m_preSupCell->SumOfWidths());

  // Position pre-scripts
  if (m_preSubCell) {
    wxPoint presub = point;
    presub.x += preWidth - m_preSubCell->SumOfWidths();
    presub.y += m_baseCell->GetMaxDrop() + m_preSubCell->GetCenterList() -
                static_cast<wxCoord>(.8 * m_fontSize_Scaled.Get()) +
                MC_EXP_INDENT;
    m_preSubCell->SetCurrentPointList(presub);
  }

  if (m_preSupCell) {
    wxPoint presup = point;
    presup.x += preWidth - m_preSupCell->SumOfWidths();
    presup.y -= m_baseCell->GetCenterList() + m_preSupCell->GetHeightList() -
                m_preSupCell->GetCenterList() -
                static_cast<wxCoord>(.8 * m_fontSize_Scaled.Get()) +
                MC_EXP_INDENT;
    m_preSupCell->SetCurrentPointList(presup);
  }

  // Position the base cell
  point.x += preWidth;
  m_baseCell->SetCurrentPointList(point);

  // Position post-scripts
  wxPoint in = point;
  in.x += m_baseCell->SumOfWidths() - Scale_Px(2);
  if (m_postSubCell) {
    in.y = point.y + m_baseCell->GetMaxDrop() + m_postSubCell->GetCenterList() -
           .8 * m_fontSize_Scaled.Get() + MC_EXP_INDENT;
    m_postSubCell->SetCurrentPointList(in);
  }
  if (m_postSupCell) {
    in.y = point.y - m_baseCell->GetCenterList() -
           m_postSupCell->GetHeightList() + m_postSupCell->GetCenterList() +
           .8 * m_fontSize_Scaled.Get() + MC_EXP_INDENT;
    m_postSupCell->SetCurrentPointList(in);
  }
}

void SubSupCell::Draw(wxDC *dc, wxDC *antialiassingDC) {
  Cell::Draw(dc, antialiassingDC);
  if (DrawThisCell()) {
    if (m_preSubCell)
      m_preSubCell->DrawList(dc, antialiassingDC);

    if (m_preSupCell)
      m_preSupCell->DrawList(dc, antialiassingDC);

    m_baseCell->DrawList(dc, antialiassingDC);

    if (m_postSubCell)
      m_postSubCell->DrawList(dc, antialiassingDC);
    if (m_postSupCell)
      m_postSupCell->DrawList(dc, antialiassingDC);
  }
}

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

  wxString s;
  if (m_baseCell->IsCompound())
    s += "(" + m_baseCell->ListToString() + ")";
  else
    s += m_baseCell->ListToString();

  if (m_preSubCell)
    s += "[" + m_preSubCell->ListToString() + "]";
  if (m_preSupCell)
    s += "^(" + m_preSupCell->ListToString() + ")";
  if (m_postSubCell)
    s += "[" + m_postSubCell->ListToString() + "]";
  if (m_postSupCell) {
    s += "^";
    if (m_postSupCell->IsCompound())
      s += "(";
    s += m_postSupCell->ListToString();
    if (m_postSupCell->IsCompound())
      s += ")";
  }
  return s;
}

wxString SubSupCell::ToMatlab() const {
  wxString s;
  if (m_baseCell->IsCompound())
    s += "(" + m_baseCell->ListToMatlab() + ")";
  else
    s += m_baseCell->ListToMatlab();

  if (m_preSubCell)
    s += "[" + m_preSubCell->ListToMatlab() + "]";
  if (m_preSupCell)
    s += "^(" + m_preSupCell->ListToMatlab() + ")";
  if (m_postSubCell)
    s += "[" + m_postSubCell->ListToMatlab() + "]";
  if (m_postSupCell) {
    s += "^";
    if (m_postSupCell->IsCompound())
      s += "(";
    s += m_postSupCell->ListToMatlab();
    if (m_postSupCell->IsCompound())
      s += ")";
  }
  return s;
}

wxString SubSupCell::ToTeX() const {
  wxString s;

  if (m_preSupCell || m_preSubCell) {
    s = "{}";
    if (m_preSupCell)
      s += "^{" + m_preSupCell->ListToTeX() + "}";
    if (m_preSubCell)
      s += "_{" + m_preSubCell->ListToTeX() + "}";
  }
  s += "{" + m_baseCell->ListToTeX() + "}";
  if (m_postSupCell)
    s += "^{" + m_postSupCell->ListToTeX() + "}";
  if (m_postSubCell)
    s += "_{" + m_postSubCell->ListToTeX() + "}";

  return s;
}

wxString SubSupCell::ToMathML() const {
  wxString retval;
  if (m_scriptCells.empty()) {
    retval = "<msubsup>" + m_baseCell->ListToMathML();
    if (m_postSubCell)
      retval += m_postSubCell->ListToMathML();
    else
      retval += "<none/>";
    if (m_postSupCell)
      retval += m_postSupCell->ListToMathML();
    else
      retval += "<none/>";
    retval += "</msubsup>\n";
  } else {
    retval = "<mmultiscripts>" + m_baseCell->ListToMathML();
    if (m_postSupCell || m_postSubCell) {
      if (m_postSubCell)
        retval += "<mrow>" + m_postSubCell->ListToMathML() + "</mrow>";
      else
        retval += "<none/>";
      if (m_postSupCell)
        retval += "<mrow>" + m_postSupCell->ListToMathML() + "</mrow>";
      else
        retval += "<none/>";
    }
    if (m_preSupCell || m_preSubCell) {
      retval += "<mprescripts/>";
      if (m_preSubCell)
        retval += "<mrow>" + m_preSubCell->ListToMathML() + "</mrow>";
      else
        retval += "<none/>";
      if (m_preSupCell)
        retval += "<mrow>" + m_preSupCell->ListToMathML() + "</mrow>";
      else
        retval += "<none/>";
    }
    retval += "</mmultiscripts>\n";
  }
  return retval;
}
wxString SubSupCell::ToOMML() const {
  wxString retval;
  if (m_preSupCell || m_preSubCell) {
    retval += "<m:sSubSup><m:e><m:r></m:r></m:e><m:sub>";
    if (m_preSubCell)
      retval += m_preSubCell->ListToOMML();
    else
      retval += "<m:r></m:r>";
    retval += "</m:sub><m:sup>";
    if (m_preSupCell)
      retval += m_preSupCell->ListToOMML();
    else
      retval += "<m:r></m:r>";
    retval += "</m:sup></m:sSubSup>\n";
  }
  retval += "<m:sSubSup><m:e>" + m_baseCell->ListToOMML() + "</m:e><m:sub>";
  if (m_postSubCell)
    retval += m_postSubCell->ListToOMML();
  else
    retval += "<m:r></m:r>";
  retval += "</m:sub><m:sup>";
  if (m_postSupCell)
    retval += m_postSupCell->ListToOMML();
  else
    retval += "<m:r></m:r>";
  retval += "</m:sup></m:sSubSup>\n";
  return retval;
}

wxString SubSupCell::ToXML() const {
  wxString flags = GetXMLFlags();

  wxString retval;
  if (m_scriptCells.empty()) {
    retval = "<ie" + flags + "><r>" + m_baseCell->ListToXML() + "</r><r>";
    if (m_postSubCell)
      retval += m_postSubCell->ListToXML();
    retval += "</r><r>";
    if (m_postSupCell)
      retval += m_postSupCell->ListToXML();
    retval += "</r></ie>";
  } else {
    retval = "<ie" + flags + "><r>" + m_baseCell->ListToXML() + "</r>";
    if (m_preSupCell)
      retval += "<r pos=\"presup\">" + m_preSupCell->ListToXML() + "</r>";
    if (m_preSubCell)
      retval += "<r pos=\"presub\">" + m_preSubCell->ListToXML() + "</r>";
    if (m_postSupCell)
      retval += "<r pos=\"postsup\">" + m_postSupCell->ListToXML() + "</r>";
    if (m_postSubCell)
      retval += "<r pos=\"postsub\">" + m_postSubCell->ListToXML() + "</r>";
    retval += "</ie>";
  }
  return retval;
}

wxString SubSupCell::GetDiffPart() const {
  wxString s(wxS(","));
  if (m_baseCell->IsCompound())
    s += "(" + m_baseCell->ListToString() + ")";
  else
    s += m_baseCell->ListToString();

  if (m_preSubCell)
    s += "[" + m_preSubCell->ListToString() + "]";
  if (m_preSupCell)
    s += "^(" + m_preSupCell->ListToString() + ")";
  if (m_postSubCell)
    s += "[" + m_postSubCell->ListToString() + "]";
  if (m_postSupCell)
    s += "^(" + m_postSupCell->ListToString() + ")";

  if (m_scriptCells.empty()) {
    s += ",";
    s += m_postSupCell->ListToString();
  } else {
    s += ",1";
  }
  return s;
}
