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

  FracCell is the Cell type that represents fractions.
*/

#include "FracCell.h"
#include "CellImpl.h"

#define FRAC_DEC 1

FracCell::FracCell(GroupCell *parent, Configuration **config, std::unique_ptr<Cell> &&num, std::unique_ptr<Cell> &&denom) :
    Cell(parent, config),
    m_numParenthesis(std::make_unique<ParenCell>(m_group, m_configuration, std::move(num))),
    m_denomParenthesis(std::make_unique<ParenCell>(m_group, m_configuration, std::move(denom)))
{
  InitBitFields();
  SetStyle(TS_VARIABLE);
  SetupBreakUps();
}

FracCell::FracCell(const FracCell &cell) :
    FracCell(cell.m_group, cell.m_configuration, CopyList(cell.Num()), CopyList(cell.Denom()))
{
  CopyCommonData(cell);
  m_fracStyle = cell.m_fracStyle;
  SetupBreakUps();
  if(cell.m_inExponent)
    SetExponentFlag();
}

DEFINE_CELL(FracCell)

void FracCell::MakeDivideCell()
{
  if (m_divideOwner) return;
  m_divideOwner = std::make_unique<TextCell>(m_group, m_configuration, "/");
  m_divideOwner->SetStyle(TS_VARIABLE);
  m_divide = m_divideOwner.get();
}

void FracCell::Recalculate(AFontSize fontsize)
{
  if(m_inExponent || IsBrokenIntoLines())
  {
    m_displayedNum->RecalculateList(fontsize);
    m_displayedDenom->RecalculateList(fontsize);
    m_divide->RecalculateList(fontsize);
  }
  else
  {
    m_displayedNum->RecalculateList({ MC_MIN_SIZE, fontsize - FRAC_DEC });
    m_displayedDenom->RecalculateList({ MC_MIN_SIZE, fontsize - FRAC_DEC });
  }
  
  if(IsBrokenIntoLines())
  {
    m_height = 0;
    m_center = 0;
    m_width = 0;
  }
  else
  {
    if (m_inExponent)
    {
      m_protrusion = m_horizontalGapLeft = m_horizontalGapRight = 0;
      m_width = Num()->GetWidth() + Denom()->GetWidth() + m_divide->GetWidth();
      m_height = wxMax(Num()->GetHeightList(), Denom()->GetHeightList()) + Scale_Px(6.5);
      m_center = wxMax(Num()->GetCenterList(), Denom()->GetCenterList()) + Scale_Px(3);
    }
    else
    {
      m_protrusion = Scale_Px((*m_configuration)->GetMathFontSize() / 2);
      
      // We want half a space's widh of blank space to separate us from the
      // next minus.
      
      if (GetPrevious() && GetPrevious()->ToString().EndsWith(wxT("-")))
        m_horizontalGapLeft = m_protrusion;
      else
        m_horizontalGapLeft = 0;
      
      if (GetNext() && GetNext()->ToString().StartsWith(wxT("-")))
        m_horizontalGapRight = m_protrusion;
      else
        m_horizontalGapRight = 0;
      
      m_width = wxMax(m_displayedNum->GetFullWidth(), m_displayedDenom->GetFullWidth()) +
        2 * m_protrusion + m_horizontalGapLeft + m_horizontalGapRight;
      m_height = Num()->GetHeightList() + Denom()->GetHeightList() +
        Scale_Px(6.5);
      m_center = Num()->GetHeightList() + Scale_Px(3);
    }
  }

  Cell::Recalculate(fontsize);
}

void FracCell::Draw(wxPoint point)
{
  Cell::Draw(point);
  if (DrawThisCell(point))
  {
    Configuration *configuration = (*m_configuration);
    
    wxDC *dc = configuration->GetDC();
    wxPoint num, denom;

    if(IsBrokenIntoLines())
      return;
    
    if (m_inExponent)
    {
      num = point;
      wxPoint divide(point);
      divide.x += m_displayedNum->GetFullWidth();
      denom = divide;
      denom.x += m_divide->GetFullWidth();
      
      m_displayedNum->DrawList(num);
      m_divide->Draw(divide);
      m_displayedDenom->DrawList(denom);
    }
    else
    {
      num.x = point.x + m_horizontalGapLeft +
              (m_width - m_horizontalGapLeft - m_horizontalGapRight - m_displayedNum->GetFullWidth()) / 2;
      num.y = point.y - m_displayedNum->GetHeightList() + m_displayedNum->GetCenterList();
      m_displayedNum->DrawList(num);

      denom.x = point.x + m_horizontalGapLeft +
                (m_width - m_horizontalGapLeft - m_horizontalGapRight - m_displayedDenom->GetFullWidth()) / 2;
      denom.y = point.y + m_displayedDenom->GetCenterList() + Scale_Px(4);
      m_displayedDenom->DrawList(denom);
      SetPen(1.2);
      if (m_fracStyle != FC_CHOOSE)
        dc->DrawLine(point.x + m_horizontalGapLeft + (*m_configuration)->GetDefaultLineWidth() / 2,
                     point.y + Scale_Px(2),
                    point.x + m_width - m_horizontalGapRight - (*m_configuration)->GetDefaultLineWidth() / 2,
                    point.y + Scale_Px(2)
        );
    }
  }
}

wxString FracCell::ToString() const
{
  wxString s;
  if (!IsBrokenIntoLines())
  {
    if (m_fracStyle == FC_NORMAL)
    {
      if (Num()->IsCompound())
        s += wxT("(") + Num()->ListToString() + wxT(")/");
      else
        s += Num()->ListToString() + wxT("/");
      if (Denom()->IsCompound())
        s += wxT("(") + Denom()->ListToString() + wxT(")");
      else
        s += Denom()->ListToString();
    }
    else if (m_fracStyle == FC_CHOOSE)
    {
      s = wxT("binomial(") + Num()->ListToString() + wxT(",") +
          Denom()->ListToString() + wxT(")");
    }
    else
    {
      Cell *tmp = Denom();
      while (tmp != NULL)
      {
        tmp = tmp->GetNext();   // Skip the d
        if (tmp == NULL)
          break;
        tmp = tmp->GetNext();   // Skip the *
        if (tmp == NULL)
          break;
        s += tmp->GetDiffPart();
        tmp = tmp->GetNext();   // Skip the *
        if (tmp == NULL)
          break;
        tmp = tmp->GetNext();
      }
    }
  }
  return s;
}

wxString FracCell::ToMatlab() const
{
  wxString s;
  if (!IsBrokenIntoLines())
  {
	if (m_fracStyle == FC_NORMAL)
	{
      if (Num()->IsCompound())
        s += wxT("(") + Num()->ListToMatlab() + wxT(")/");
	  else
        s += Num()->ListToMatlab() + wxT("/");
      if (Denom()->IsCompound())
        s += wxT("(") + Denom()->ListToMatlab() + wxT(")");
	  else
        s += Denom()->ListToMatlab();
	}
	else if (m_fracStyle == FC_CHOOSE)
	{
      s = wxT("binomial(") + Num()->ListToMatlab() + wxT(",") +
          Denom()->ListToMatlab() + wxT(")");
	}
	else
    {
      for (Cell *tmp = Denom(); tmp; tmp = tmp->GetNext())
      {
        tmp = tmp->GetNext();   // Skip the d
        if (!tmp)
          break;
        tmp = tmp->GetNext();   // Skip the *
        if (!tmp)
          break;
        s += tmp->GetDiffPart();
        tmp = tmp->GetNext();   // Skip the *
        if (!tmp)
          break;
      }
    }
  }
  return s;
}

wxString FracCell::ToTeX() const
{
  wxString s;
  if (!IsBrokenIntoLines())
  {
    if (m_fracStyle == FC_CHOOSE)
    {
      s = wxT("\\begin{pmatrix}") + Num()->ListToTeX() + wxT("\\\\\n") +
          Denom()->ListToTeX() + wxT("\\end{pmatrix}");
    }
    else
    {
      s = wxT("\\frac{") + Num()->ListToTeX() + wxT("}{") +
          Denom()->ListToTeX() + wxT("}");
    }
  }
  return s;
}

wxString FracCell::ToMathML() const
{
  return wxT("<mfrac>") +
         Num()->ListToMathML() +
         Denom()->ListToMathML() + wxT("</mfrac>\n");
}


wxString FracCell::ToOMML() const
{
  return wxT("<m:f><m:num>") +
         Num()->ListToOMML() + wxT("</m:num><m:den>") +
         Denom()->ListToOMML() + wxT("</m:den></m:f>\n");
}

wxString FracCell::ToXML() const
{
  wxString s = (m_fracStyle == FC_NORMAL || m_fracStyle == FC_DIFF) ?
               _T("f") : _T("f line = \"no\"");
  wxString diffStyle;
  if (m_fracStyle == FC_DIFF)
    diffStyle = wxT(" diffstyle=\"yes\"");
  if (HasHardLineBreak())
    diffStyle += wxT(" breakline=\"true\"");

  return _T("<") + s + diffStyle + _T("><r>") +
         Num()->ListToXML() + _T("</r><r>") +
         Denom()->ListToXML() + _T("</r></f>");
}

void FracCell::SetExponentFlag()
{
  if (Num()->IsShortNum() && Denom()->IsShortNum())
  {
    m_inExponent = true;
    MakeDivideCell();
  }
}

void FracCell::SetupBreakUps()
{
  m_displayedNum = m_numParenthesis.get();
  m_displayedDenom = m_denomParenthesis.get();
  if (m_fracStyle == FC_CHOOSE)
  {
    if (Num() && !Num()->IsCompound())
      m_displayedNum = Num();
    if (Denom() && !Denom()->IsCompound())
      m_displayedDenom = Denom();
  }
  else
  {
    m_displayedNum = Num();
    m_displayedDenom = Denom();
  }
}

bool FracCell::BreakUp()
{
  if (m_fracStyle == FC_DIFF || IsBrokenIntoLines())
    return false;

  MakeDivideCell();
  Cell::BreakUpAndMark();
  if(Num() && Num()->GetNext())
    m_displayedNum = m_numParenthesis.get();
  if(Denom() && Denom()->GetNext())
    m_displayedDenom = m_denomParenthesis.get();
  // Note: Yes, we don't want m_displayedNum->last() here.
  m_displayedNum->SetNextToDraw(m_divide);
  m_divide->SetNextToDraw(m_displayedDenom);
  m_displayedDenom->SetNextToDraw(m_nextToDraw);
  m_nextToDraw = m_displayedNum;
  ResetCellListSizes();
  m_height = 0;
  m_center = 0;
  return true;
}

void FracCell::SetNextToDraw(Cell *next)
{
  if (IsBrokenIntoLines())
    m_displayedDenom->SetNextToDraw(next);
  else
    m_nextToDraw = next;
}
