/*
 * This file is part of OpenModelica.
 *
 * Copyright (c) 1998-2026, Open Source Modelica Consortium (OSMC),
 * c/o Linköpings universitet, Department of Computer and Information Science,
 * SE-58183 Linköping, Sweden.
 *
 * All rights reserved.
 *
 * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF AGPL VERSION 3 LICENSE OR
 * THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.8.
 * ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES
 * RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GNU AGPL
 * VERSION 3, ACCORDING TO RECIPIENTS CHOICE.
 *
 * The OpenModelica software and the OSMC (Open Source Modelica Consortium)
 * Public License (OSMC-PL) are obtained from OSMC, either from the above
 * address, from the URLs:
 * http://www.openmodelica.org or
 * https://github.com/OpenModelica/ or
 * http://www.ida.liu.se/projects/OpenModelica,
 * and in the OpenModelica distribution.
 *
 * GNU AGPL version 3 is obtained from:
 * https://www.gnu.org/licenses/licenses.html#GPL
 *
 * This program is distributed WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH
 * IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL.
 *
 * See the full OSMC Public License conditions for more details.
 *
 */

/*!
 * \file cellcommands.h
 * \author Ingemar Axelsson and Anders Fernström
 *
 * \brief Describes different cell commands
 */

// QT Headers
#include <QObject>
#include <QtGui/QTextCursor>
#include <QtGui/QTextDocumentFragment>

//STD Headers
#include <exception>
#include <stdexcept>
#include <typeinfo>

//IAEX Headers
#include "cellcommands.h"
#include "graphcell.h"
#include "latexcell.h"
#include "inputcell.h"
#include "textcell.h"
#include "cellgroup.h"


typedef std::vector<Rule *> rules_t;

namespace IAEX
{
   /*!
    * \class AddCellCommand
  * \author Ingemar Axelsson and Anders Fernström
    *
    * \brief Command for adding a new cell to the cellstructure.
    */
  void AddCellCommand::execute()
  {
    try
    {
      CellCursor *cursor = document()->getCursor();

      Factory *fac = document()->cellFactory();

      // 2005-10-28 AF, changed style from QString to CellStyle
      CellStyle style;

      if(cursor->currentCell()->hasChilds())
        style = *cursor->currentCell()->child()->style();
      else
        style = *cursor->currentCell()->style();


      //This does not work.
      if(cursor->currentCell()->isClosed())
      {
        if(cursor->currentCell()->hasChilds())
        {
          cursor->currentCell()->child()->setReadOnly(true);
          cursor->currentCell()->child()->setFocus(false);
        }
      }
      else
      {
        cursor->currentCell()->setReadOnly(true);
        cursor->currentCell()->setFocus(false);
      }

      // 2005-11-21 AF, Added check if the current cell is a
      // inputcell, set style to 'text' instead.
      // 2006-02-03 AF, added check if the current cell is a
      // groupcell
      if( style.name() == "input" || style.name() == "Input" || style.name() == "ModelicaInput" ||
        style.name() == "cellgroup" )
        cursor->addBefore(fac->createCell( "Text" ));
      else
        cursor->addBefore(fac->createCell(style.name()));

      if(cursor->currentCell()->isClosed())
      {
        if(cursor->currentCell()->hasChilds())
        {
          cursor->currentCell()->child()->setReadOnly(false);
          cursor->currentCell()->child()->setFocus(true);
        }
      }
      else
      {
        cursor->currentCell()->setReadOnly(false);
        cursor->currentCell()->setFocus(true);
      }

      //2006-01-18 AF, set docuement changed
      document()->setChanged( true );
    }
    catch(const std::exception &e)
    {
      // 2006-01-30 AF, add exception
      std::string str = std::string("AddCellCommand(), Exception: \n") + e.what();
      throw std::runtime_error( str.c_str() );
    }
  }

   /*!
    * \class CreateNewCellCommand
  * \author Ingemar Axelsson
    *
    * \brief Command for creating a new cell.
    */
   void CreateNewCellCommand::execute()
   {
     try
     {
       CellCursor *cursor = document()->getCursor();

       Factory *fac = document()->cellFactory();

       //This does not work.
       if(cursor->currentCell()->isClosed())
       {
         if(cursor->currentCell()->hasChilds())
         {
           cursor->currentCell()->child()->setReadOnly(true);
           cursor->currentCell()->child()->setFocus(false);
         }
       }
       else
       {
         cursor->currentCell()->setReadOnly(true);
         cursor->currentCell()->setFocus(false);
       }

       cursor->addBefore(fac->createCell(style_));

       if(cursor->currentCell()->isClosed())
       {
         if(cursor->currentCell()->hasChilds())
         {
           cursor->currentCell()->child()->setReadOnly(false);
           cursor->currentCell()->child()->setFocus(true);
         }
       }
       else
       {
         cursor->currentCell()->setReadOnly(false);
         cursor->currentCell()->setFocus(true);
       }

       //2006-01-18 AF, set docuement changed
       document()->setChanged( true );
     }
     catch(const std::exception &e)
     {
       // 2006-01-30 AF, add exception
       std::string str = std::string("CreateNewCommand(), Exception: \n") + e.what();
       throw std::runtime_error( str.c_str() );
     }
   }


   /*!
    * \class CopySelectedCellsCommand
  * \author Ingemar Axelsson
  *
    * \brief copy a cell. Adds a copy of the cell on the pasteboard.
    *
  *
    * \todo Implement some kind of internal state or copy constructors
    * for all cells. This to make the copy process more general.(Ingemar Axelsson)
    *
    * \todo Implement release from cellgroup. It should be possible to
    * remove pointers to the cell structure without deleting the
    * object. That needs reparenting of all subcells.(Ingemar Axelsson)
    */
   void CopySelectedCellsCommand::execute()
   {
      //CellCursor *c = document()->getCursor();
      std::vector<Cell *> cells = document()->getSelection();

      if(cells.empty())
      {
      return;
    //Empty pasteboard.
    //application()->clearPasteboard();
    //application()->addToPasteboard(c->currentCell());
      }
      else
      {
        document()->clearSelection(); //Notice
        application()->clearPasteboard();

        std::vector<Cell *>::iterator i = cells.begin();
        for(;i != cells.end();++i)
        {
          application()->addToPasteboard((*i));
        }
      }
   }

   /*! What happens if a Groupcell is removed?
    */
   void DeleteCurrentCellCommand::execute()
   {
     try
     {
       CellCursor *c = document()->getCursor();
       std::vector<Cell *> cells = document()->getSelection();

       if(cells.empty())
       {
         return;
       }
       else
       {
         document()->clearSelection(); //Notice
         application()->clearPasteboard();

         std::vector<Cell *>::iterator i = cells.begin();
         for(;i != cells.end();++i)
         {
           c->moveAfter((*i));

           //1. Copy cell to pasteboard.
           application()->addToPasteboard(c->currentCell());

           //2. Delete Cell.
           c->removeCurrentCell();
         }
       }

       //2006-01-18 AF, set docuement changed
       document()->setChanged( true );
     }
     catch(const std::exception &e)
     {
       // 2006-01-30 AF, add message box
       std::string str = std::string("DeleteCurrentCellsCommand(), Exception: \n") + e.what();
       throw std::runtime_error( str.c_str() );
     }
   }

   //Det borde vara möjligt att titta på stylen för att avgöra om en
   //cell är en grupp cell. Då borde det gå att kopiera in underceller
   //också.
   //
   // Kontrollera på stylen hur cellerna ska kopieras. Speciellt ska
   // gruppceller specialbehandlas så att deras subceller också
   // kopieras. Just nu funkar det att kopiera enstaka celler. Men
   // inte gruppceller.
  void PasteCellsCommand::execute()
  {
    try
    {
      std::vector<Cell *> cells = application()->pasteboard();

      // Insert new cells before this position.
      if(!cells.empty())
      {
        //Reverse iterator!!!!!
        //vector<Cell *>::reverse_iterator i = cells.rbegin();
        //for(;i != cells.rend();++i)
        // AF, Not reverse
        std::vector<Cell *>::iterator i = cells.begin();
        for(;i != cells.end();++i)
        {
          pasteCell( (*i) );
        }
      }
      else
      {
        QMessageBox::warning(nullptr, QObject::tr("Warning"), QObject::tr("No cells in clipboard. To paste a cell, select the cells and perform copy."));
      }

      //2006-01-18 AF, set docuement changed
      document()->setChanged( true );
    }
    catch(const std::exception &e)
    {
      // 2006-01-30 AF, add exception
      std::string str = std::string("PasteCellsCommand(), Exception: \n") + e.what();
      throw std::runtime_error( str.c_str() );
    }
  }

  // 2006-01-16 AF, move this code to a seperated function
  // 2006-09-04 AF, redid entire function, so groupcells are created, have there
  // children added and THEN add to the documnet
  void PasteCellsCommand::pasteCell( Cell *cell, CellGroup *groupcell )
  {
    // get cursor, factory and cell style
    CellCursor *cursor = document()->getCursor();
    Factory *factory = document()->cellFactory();
    CellStyle style = *cell->style();

    // set focus and readonly stuff (from old implementation, IA)
    if(cursor->currentCell()->isClosed())
    {
      if(cursor->currentCell()->hasChilds())
      {
        cursor->currentCell()->child()->setReadOnly(true);
        cursor->currentCell()->child()->setFocus(false);
      }
    }
    else
    {
      cursor->currentCell()->setReadOnly(true);
      cursor->currentCell()->setFocus(false);
    }

    // create the new cell, if there exists a groupcell add the new cell to
    // that groupcell.
    Cell* newCell = factory->createCell( style.name() );

//    if( groupcell )
//      groupcell->addChild( newCell );


    // set content of cell
    // *************************************************************************

    // COPY - EVERY CELL TYPE
    // ----------------------
    newCell->setCellTag( cell->cellTag() );

    // rules
    for (const auto &rule: cell->rules()) {
      newCell->addRule(rule);
    }

    // COPY - SPECIFIC FOR CELL TYPE
    // -----------------------------
    if( typeid(CellGroup) == typeid( *newCell ))
    {
      CellGroup *newCellGroup = dynamic_cast<CellGroup *>( newCell );
      newCellGroup->setClosed( cell->isClosed() );

      if( cell->hasChilds() )
      {
        Cell* child = cell->child();
        while( child )
        {
          pasteCell( child, newCellGroup );
          child = child->next();
        }
      }
    }
    else if( typeid(InputCell) == typeid( *newCell ))
    {
      InputCell *newInputCell = dynamic_cast<InputCell *>( newCell );
      InputCell *oldInputCell = dynamic_cast<InputCell *>( cell );

      newInputCell->setStyle( style );
      newInputCell->setText( oldInputCell->text() );

      if( oldInputCell->isEvaluated() )
      {
        newInputCell->setEvaluated( true );
        newInputCell->setTextOutput( oldInputCell->textOutput() );
      }
      else
        newInputCell->setEvaluated( false );

      newInputCell->setClosed( oldInputCell->isClosed() );
    }
    else if( typeid(GraphCell) == typeid( *newCell ))
    {
      GraphCell *newGraphCell = dynamic_cast<GraphCell *>( newCell );
      GraphCell *oldGraphCell = dynamic_cast<GraphCell *>( cell );

      newGraphCell->setStyle( style );
      newGraphCell->setText( oldGraphCell->text() );

      if( oldGraphCell->isEvaluated() )
      {
        newGraphCell->setEvaluated( true );
        newGraphCell->setTextOutput( oldGraphCell->textOutput() );
      }
      else
        newGraphCell->setEvaluated( false );

      newGraphCell->setClosed( oldGraphCell->isClosed() );
    }
    else if( typeid(LatexCell) == typeid( *newCell ))
    {
      LatexCell *newLatexCell = dynamic_cast<LatexCell *>( newCell );
      LatexCell *oldLatexCell = dynamic_cast<LatexCell *>( cell );

      newLatexCell->setStyle( style );
      newLatexCell->setTextHtml(oldLatexCell->textHtml());
      if( oldLatexCell->isEvaluated() )
      {
        newLatexCell->setEvaluated(true);
        //newLatexCell->setTextOutput( oldLatexCell->textOutput() );
        newLatexCell->setTextOutputHtml(oldLatexCell->textOutputHtml());

      }
      else
      {
        newLatexCell->setEvaluated(false);
      }
      newLatexCell->setClosed( oldLatexCell->isClosed() );
    }
    else if( typeid(TextCell) == typeid( *newCell ))
    {
      newCell->setStyle( style );
      newCell->setTextHtml( cell->textHtml() );
    }
    else
    {
      // Error
      throw std::runtime_error("pasteCell(): Unknown celltype.");
    }
    // *************************************************************************


    // Add cell to document
    if( !groupcell )
      cursor->addBefore( newCell );
    else //if there exists a groupcell add the new cell to that groupcell.
      groupcell->addChild( newCell );

    // set focus and readonly stuff (from old implementation, IA)
    if(cursor->currentCell()->isClosed())
    {
      if(cursor->currentCell()->hasChilds())
      {
        cursor->currentCell()->child()->setReadOnly(false);
        cursor->currentCell()->child()->setFocus(true);
      }
    }
    else
    {
      cursor->currentCell()->setReadOnly(false);
      cursor->currentCell()->setFocus(true);
    }
  }

   /*!
    * \class DeleteSelectedCellsCommand
  * \author Ingemar Axelsson
    *
    * Deletes all selected cells. If no cell is selected the cell that
    * is before the cursor is deleted.
    *
    * What happens when all cells in a cellgroup is empty? Should the
    * cellgroup be deleted? Or what to do with it?
    *
    * Notice that the selection must be cleared.
    *
    *
    * \bug When all cells within a cellgroup is deleted. The cellgroup
    * should also be deleted then. What if all but one cell is deleted?
    */
   void DeleteSelectedCellsCommand::execute()
   {
     try
     {
       std::vector<Cell *> cells = document()->getSelection();
       if(cells.empty())
       {
         application()->clearPasteboard(); // HACK: clear pasteboard as this cell might be referenced in it
         document()->getCursor()->deleteCurrentCell();
       }
       else
       {
         document()->clearSelection(); //Notice
         application()->clearPasteboard(); // HACK: clear pasteboard as this cell might be referenced in it

         std::vector<Cell *>::iterator i = cells.begin();
         for(;i != cells.end();++i)
         {
           (document()->getCursor())->moveAfter((*i));
           (document()->getCursor())->deleteCurrentCell();
         }
       }

       //2006-01-18 AF, set docuement changed
       document()->setChanged( true );
     }
     catch(const std::exception &e)
     {
       // 2006-01-30 AF, add exception
       std::string str = std::string("DeleteSelectedCellsCommand(), Exception: \n") + e.what();
       throw std::runtime_error( str.c_str() );
     }
   }



     /*!
   * \class ChangeStyleOnSelectedCellsCommand
   * \author Ingemar Axelsson and Anders Fernström
   * \date 2005-10-28 (update)
   *
   * \brief Changes style on selected cells.
   *
   * This does not work on selected Cellgroups. This because I have
     * not defined what will happen if I change style on a
     * cellgroup. Probably all its children will get the same style.
   *
   * 2005-10-28, updated style_ from a QString object to a CellStyle
   * object. Needed to do some major change of the class. /AF
   *
   */
  void ChangeStyleOnSelectedCellsCommand::execute()
  {
    try
    {
      std::vector<Cell *> cells = document()->getSelection();

      if(cells.empty())
      {
        document()->getCursor()->currentCell()->setStyle(style_);
      }
      else
      {
        std::vector<Cell *>::iterator i = cells.begin();

        for(;i != cells.end() ;++i)
        {
          //This makes an segfault. Do not now why?
          (*i)->setStyle(style_);
        }

      }

      //2006-01-18 AF, set docuement changed
      document()->setChanged( true );
    }
    catch(const std::exception &e)
    {
      // 2006-01-30 AF, add exception
      std::string str = std::string("ChangeStyleOnSelectedCellsCommand(), Exception: \n") + e.what();
      throw std::runtime_error( str.c_str() );
    }
  }


   void ChangeStyleOnCurrentCellCommand::execute()
   {
     try
     {
       document()->getCursor()->currentCell()->setStyle(style_);

       //2006-01-18 AF, set docuement changed
       document()->setChanged( true );
     }
     catch(const std::exception &e)
     {
       // 2006-01-30 AF, add exception
       std::string str = std::string("ChangeStyleOnCurrentCellCommand(), Exception: \n") + e.what();
       throw std::runtime_error( str.c_str() );
     }
   }


   void MakeGroupCellCommand::execute()
   {
     try
     {
       Factory *fac = document()->cellFactory();
       CellCursor *cursor = document()->getCursor();

       Cell *prev = cursor->currentCell();
       cursor->currentCell()->parentCell()->removeChild(prev);

       Cell *group = fac->createCell("cellgroup", cursor->parentCell());

       group->addChild(prev);
       cursor->addBefore(group);
       cursor->moveToLastChild(group);

       //2006-01-18 AF, set docuement changed
       document()->setChanged( true );
     }
     catch(const std::exception &e)
     {
       // 2006-01-30 AF, add exception
       std::string str = std::string("MakeGroupCellCommand(), Exception: \n") + e.what();
       throw std::runtime_error( str.c_str() );

     }
   }

   /*!
    * \class UngroupCellCommand
    * \author Anders Fernström
    * \date 2006-04-26
    *
    * \brief Ungroup the cell
    */
  void UngroupCellCommand::execute()
  {
    try
    {
      std::vector<Cell *> cells = document()->getSelection();

      if( !cells.empty() )
      {
        // clear selection before changing cell strucure
        document()->clearSelection();

        for (auto cell: cells)
        {
          //check if groupcell
          if( dynamic_cast<CellGroup*>(cell) )
          {
            if( !cell->hasChilds() )
              throw std::runtime_error( "No children" );

            // get child
            Cell* child = cell->child();
            Cell* deletedCellsParent = cell->parentCell();
            Cell* deletedCellsPrevious = cell->previous();
            Cell* deletedCellsNext = cell->next();

            // if previous is 0 = first in cell
            child->setPrevious( deletedCellsPrevious );
            if( !child->hasPrevious() )
              deletedCellsParent->setChild( child );
            else
              deletedCellsPrevious->setNext( child );

            // add all children
            while( child != 0 )
            {
              //child->setParent( (*c_iter)->parent() );
              child->setParentCell( deletedCellsParent );

              if( child->hasNext() )
                                child = child->next();
              else
              {
                child->setNext( deletedCellsNext );
                if( !child->hasNext() )
                {
                  // update last value on all cells
                  Cell* current = child;
                  while( current != 0 )
                  {
                    current->setLast( child );
                    current = current->previous();
                  }
                }
                else
                  deletedCellsNext->setPrevious( child );

                break;
              }
            }

            cell->setChild( 0 );
            cell->hide();

            // must update groupcells parents layout
            deletedCellsParent->removeCellWidgets();
            deletedCellsParent->addCellWidgets();

            // delete groupcell
            //(document()->getCursor())->moveAfter( (*c_iter) );
            //(document()->getCursor())->deleteCurrentCell();
            delete cell;

            // update document
            document()->setChanged( true );
          }
        }
      }
    }
    catch( const std::exception &e )
    {
      std::string str = std::string("UngroupCellCommand(), Exception: ") + e.what();
      throw std::runtime_error( str.c_str() );
    }
  }

 /*!
    * \class SplitCellCommand
    * \author Anders Fernström
    * \date 2006-04-26
    *
    * \brief Split the cell
    */
  void SplitCellCommand::execute()
  {
    try
    {
      if( document()->getCursor()->currentCell() )
      {
        auto cell = document()->getCursor()->currentCell();
        if( dynamic_cast<TextCell*>(cell) || dynamic_cast<InputCell*>(cell) )
        {
          // extraxt text
          QTextCursor cursor = cell->textCursor();
          if( !cursor.isNull() )
          {
            cursor.movePosition( QTextCursor::End, QTextCursor::KeepAnchor );
            QTextDocumentFragment text = cursor.selection();
            cursor.removeSelectedText();

            // add new cell
            if( dynamic_cast<TextCell*>(cell) )
            {
              AddCellCommand addcellCommand;
              addcellCommand.setApplication( application() );
              addcellCommand.setDocument( document() );
              addcellCommand.execute();
            }
            else
            {
              // inputcell
              CreateNewCellCommand newcellCommand( "Input" );
              newcellCommand.setApplication( application() );
              newcellCommand.setDocument( document() );
              newcellCommand.execute();
            }

            // add text to new cell
            auto currentCell = document()->getCursor()->currentCell();
            currentCell->textCursor().insertFragment(text);
            currentCell->moveCursor(QTextCursor::Start);

            // update document
            document()->setChanged( true );
          }
        }
      }
    }
    catch( const std::exception &e )
    {
      std::string str = std::string("SplitCellCommand(), Exception: ") + e.what();
      throw std::runtime_error( str.c_str() );
    }
  }

};
