// -*- 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 ParenCell

  ParenCell is the Cell type that represents a math element that is kept
  between parenthesis.
*/

#include "ParenCell.h"
#include "CellImpl.h"
#include "VisiblyInvalidCell.h"

ParenCell::ParenCell(GroupCell *group, Configuration *config,
                     std::unique_ptr<Cell> &&inner)
  : Cell(group, config),
    m_open(std::make_unique<TextCell>(group, config, wxS("("))),
    m_innerCell(std::move(inner)),
    m_close(std::make_unique<TextCell>(group, config, wxS(")"))) {
  InitBitFields_ParenCell();
  // If there is a contents it doesn't need to start with a multiplication
  // dot.
  if(m_innerCell)
    m_innerCell->SetSuppressMultiplicationDot(true);
  m_open->SetStyle(TS_FUNCTION);
  m_close->SetStyle(TS_FUNCTION);
}

ParenCell::ParenCell(GroupCell *group, const ParenCell &cell)
  : ParenCell(group, cell.m_configuration,
              CopyList(group, cell.m_innerCell.get())) {
  CopyCommonData(cell);
}

DEFINE_CELL(ParenCell)

void ParenCell::SetInner(std::unique_ptr<Cell> inner, CellType type) {
  if (!inner)
    return;
  m_innerCell = std::move(inner);

  m_type = type;
  // Tell the first of our inner cells not to begin with a multiplication dot.
  m_innerCell->SetSuppressMultiplicationDot(true);
  ResetSize();
}

void ParenCell::Recalculate(AFontSize fontsize) const {
  bool changed = false;
  if(m_innerCell)
    changed |= m_innerCell->RecalculateList(fontsize);
  changed |= m_open->RecalculateList(fontsize);
  changed |= m_close->RecalculateList(fontsize);

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

    wxDC *dc = m_configuration->GetRecalcDC();
    int size = 0;
    if(m_innerCell)
      size = m_innerCell->GetHeightList();
    // If our font provides all the unicode chars we need we don't need
    // to bother which exotic method we need to use for drawing nice parenthesis.
    if (1.2 * m_open->GetHeight() >= size) {
      m_bigParenType = Configuration::ascii;
      m_signHeight = std::max(m_open->GetHeight(),m_close->GetHeight());
      m_signWidth = m_open->GetWidth();
    } else 
      {
        m_bigParenType = Configuration::handdrawn;
        m_signWidth = Scale_Px(6) + m_configuration->GetDefaultLineWidth();
        if (m_signWidth < size / 15)
          m_signWidth = size / 15;
        m_signHeight = size;
      }
    int innerCellWidth = 0;
    if(m_innerCell)
      innerCellWidth = m_innerCell->SumOfWidths();
    m_width = innerCellWidth + m_signWidth * 2;
    if (IsBrokenIntoLines())
      m_width = 0;

    int innerCellHeight = 0;
    if(m_innerCell)
      innerCellHeight = m_innerCell->GetHeightList();
    m_height = std::max(m_signHeight.GetOrElse(0), innerCellHeight) + Scale_Px(2);
    m_center = m_height / 2;

    dc->GetTextExtent(wxS("("), &m_charWidth1, &m_charHeight1);
    if (m_charHeight1 < 2)
      m_charHeight1 = 2;

    if (IsBrokenIntoLines()) {
      m_width = 0;
      m_height = 0;
      m_center = 0;
    } else {
      if (m_innerCell) {
        m_innerCell->SetCurrentPoint(
                                     wxPoint(m_currentPoint.x + m_signWidth, m_currentPoint.y));
        switch (m_bigParenType) {
        case Configuration::ascii:
          break;
        default: {
        }
        }

        // Center the argument of all big parenthesis vertically
        if (m_bigParenType != Configuration::ascii) {
          m_innerCell->SetCurrentPoint(
                                       wxPoint(m_currentPoint.x + m_signWidth,
                                               m_currentPoint.y + (m_innerCell->GetCenterList() -
                                                                   m_innerCell->GetHeightList() / 2)));
          m_height =
            std::max(m_signHeight.GetOrElse(0), m_innerCell->GetHeightList()) + Scale_Px(4);
          m_center = m_height / 2;
        } else {
          m_innerCell->SetCurrentPoint(
                                       wxPoint(m_currentPoint.x + m_signWidth, m_currentPoint.y));
          m_center = std::max(m_open->GetCenterList(), m_innerCell->GetCenterList());
          m_height = m_center + std::max(m_open->GetMaxDrop(), m_innerCell->GetMaxDrop());
        }
      }
    }
  }
}

/**
 * @brief Pass 2 (Arrange): Positions opening bracket, inner content, and closing bracket.
 */
void ParenCell::SetCurrentPoint(wxPoint point) const {
  Cell::SetCurrentPoint(point);
  
  // Linearized cells (broken into lines) behave as zero-size containers.
  // Their children are positioned by the flattened GroupCell loop.
  if (IsBrokenIntoLines())
    return;

  if (m_innerCell) {
    wxPoint innerCellPos = point;
    switch (m_bigParenType) {
    case Configuration::ascii:
      // Simple font-based parenthesis
      innerCellPos.x += m_open->GetWidth();
      m_open->SetCurrentPointList(point);
      m_innerCell->SetCurrentPointList(innerCellPos);
      {
        int innerCellWidth = 0;
        if (m_innerCell)
          innerCellWidth = m_innerCell->SumOfWidths();
        m_close->SetCurrentPointList(
            wxPoint(point.x + m_open->GetWidth() + innerCellWidth, point.y));
      }
      break;
    default: {
      // Hand-drawn or Unicode-scaled parenthesis
      // We center the argument of all big parenthesis vertically
      innerCellPos.y +=
          (m_innerCell->GetCenterList() - m_innerCell->GetHeightList() / 2);
      innerCellPos.x = point.x + m_signWidth;
      m_innerCell->SetCurrentPointList(innerCellPos);
    } break;
    }
  }
}

void ParenCell::Draw(wxDC *dc, wxDC *antialiassingDC) {
  Cell::Draw(dc, antialiassingDC);
  if (DrawThisCell()) {
    if (!m_innerCell)
      return;

    wxPoint point = m_currentPoint;
    switch (m_bigParenType) {
    case Configuration::ascii: {
      m_open->DrawList(dc, antialiassingDC);
      m_innerCell->DrawList(dc, antialiassingDC);
      m_close->DrawList(dc, antialiassingDC);
      break;
    }
    default: {
      m_innerCell->DrawList(dc, antialiassingDC);
      SetPen(antialiassingDC, 1.0);
      SetBrush(antialiassingDC);

      int signWidth = m_signWidth - Scale_Px(2);

      // Left bracket
      const wxPoint pointsL[10] = {
          {point.x + Scale_Px(1) + signWidth, point.y - m_center + Scale_Px(4)},
          {point.x + Scale_Px(1) + 3 * signWidth / 4,
           point.y - m_center + 3 * signWidth / 4 + Scale_Px(4)},
          {point.x + Scale_Px(1), point.y},
          {point.x + Scale_Px(1) + 3 * signWidth / 4,
           point.y + m_center - 3 * signWidth / 4 - Scale_Px(4)},
          {point.x + Scale_Px(1) + signWidth, point.y + m_center - Scale_Px(4)},
          // Appending the last point twice should allow for an abrupt 180° turn
          {point.x + Scale_Px(1) + signWidth, point.y + m_center - Scale_Px(4)},
          {point.x + Scale_Px(1) + 3 * signWidth / 4,
           point.y + m_center - 3 * signWidth / 4 - Scale_Px(4)},
          // The middle point of the 2nd run of the parenthesis is at a
          // different place making the parenthesis wider here
          {point.x + Scale_Px(2), point.y},
          {point.x + Scale_Px(1) + 3 * signWidth / 4,
           point.y - m_center + 3 * signWidth / 4 + Scale_Px(4)},
          {point.x + Scale_Px(1) + signWidth,
           point.y - m_center + Scale_Px(4)}};
      antialiassingDC->DrawSpline(10, pointsL);

      // Right bracket
      const wxPoint pointsR[10] = {
          {point.x + m_width - Scale_Px(1) - signWidth,
           point.y - m_center + Scale_Px(4)},
          {point.x + m_width - Scale_Px(1) - signWidth / 2,
           point.y - m_center + signWidth / 2 + Scale_Px(4)},
          {point.x + m_width - Scale_Px(1), point.y},
          {point.x + m_width - Scale_Px(1) - signWidth / 2,
           point.y + m_center - signWidth / 2 - Scale_Px(4)},
          {point.x + m_width - Scale_Px(1) - signWidth,
           point.y + m_center - Scale_Px(4)},
          {point.x + m_width - Scale_Px(1) - signWidth,
           point.y + m_center - Scale_Px(4)},
          {point.x + m_width - Scale_Px(1) - signWidth / 2,
           point.y + m_center - signWidth / 2 - Scale_Px(4)},
          {point.x + m_width - Scale_Px(2), point.y},
          {point.x + m_width - Scale_Px(1) - signWidth / 2,
           point.y - m_center + signWidth / 2 + Scale_Px(4)},
          {point.x + m_width - Scale_Px(1) - signWidth,
           point.y - m_center + Scale_Px(4)}};
      antialiassingDC->DrawSpline(10, pointsR);
    } break;
    }
  }
}

wxString ParenCell::ToString() const {
  wxString s;
  if (!m_innerCell)
    return wxS("()");

  if (!IsBrokenIntoLines()) {
    if (m_print)
      s = wxS("(") + m_innerCell->ListToString() + wxS(")");
    else
      s = m_innerCell->ListToString();
  }
  return s;
}

wxString ParenCell::ToMatlab() const {
  if (!m_innerCell)
    return "()";
  wxString s;
  if (!IsBrokenIntoLines()) {
    if (m_print)
      s = wxS("(") + m_innerCell->ListToMatlab() + wxS(")");
    else
      s = m_innerCell->ListToMatlab();
  }
  return s;
}

wxString ParenCell::ToTeX() const {
  if (!m_innerCell)
    return "()";
  wxString s;
  if (!IsBrokenIntoLines()) {
    wxString innerCell = m_innerCell->ListToTeX();

    // Let's see if the cell contains anything potentially higher than a normal
    // character.
    bool needsLeftRight = false;
    for (size_t i = 0; i < innerCell.Length(); i++)
      if (!wxIsalnum(innerCell.at(i))) {
        needsLeftRight = true;
        break;
      }

    if (m_print) {
      if (needsLeftRight)
        s = wxS("\\left( ") + m_innerCell->ListToTeX() + wxS("\\right) ");
      else
        s = wxS("(") + m_innerCell->ListToTeX() + wxS(")");
    } else
      s = m_innerCell->ListToTeX();
  }
  return s;
}

wxString ParenCell::ToOMML() const {
  if(m_innerCell)
    return wxS("<m:d><m:dPr m:begChr=\"") + XMLescape(m_open->ToString()) +
      wxS("\" m:endChr=\"") + XMLescape(m_close->ToString()) +
      wxS("\" m:grow=\"1\"></m:dPr><m:e>") + m_innerCell->ListToOMML() +
      wxS("</m:e></m:d>");
  else
    return wxS("<m:d><m:dPr m:begChr=\"") + XMLescape(m_open->ToString()) +
      wxS("\" m:endChr=\"") + XMLescape(m_close->ToString()) +
      wxS("\" m:grow=\"1\"></m:dPr><m:e>") +
      wxS("</m:e></m:d>");
}

wxString ParenCell::ToMathML() const {
  if (!m_print)
    return m_innerCell->ListToMathML();

  wxString open = m_open->ToString();
  wxString close = m_close->ToString();
  wxString innerCellContents;
  if(m_innerCell)
    innerCellContents = m_innerCell->ListToMathML();

  return (wxS("<mrow><mo>") + XMLescape(open) + wxS("</mo>") +
          innerCellContents + wxS("<mo>") + XMLescape(close) +
          wxS("</mo></mrow>\n"));
}

wxString ParenCell::ToXML() const {
  wxString s;
  if (m_innerCell)
    s = m_innerCell->ListToXML();
  return ((m_print) ? wxS("<r><p") + GetXMLFlags() + wxS(">") + s + wxS("</p></r>") : s);
}

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

  Cell::BreakUpAndMark();
  if(m_innerCell)
    {
      m_open->SetNextToDraw(m_innerCell);
      m_innerCell->last()->SetNextToDraw(m_close);
    }
  else
    m_open->SetNextToDraw(m_close);
  m_close->SetNextToDraw(m_nextToDraw);
  m_nextToDraw = m_open;
  return true;
}

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