text:
/********************************************************************************
 * Copyright 2019 The Robotics Group, The Maersk Mc-Kinney Moller Institute,
 * Faculty of Engineering, University of Southern Denmark
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ********************************************************************************/

#include "WorkcellEditorWindow.hpp"

#include "InputFormDialog.hpp"
#include "WCCodeEditor.hpp"
#include "WCECompleter.hpp"
#include "WorkcellHighlighter.hpp"
#include "ui_WorkcellEditorWindow.h"

#include <rw/core/Log.hpp>
#include <rw/core/StringUtil.hpp>
#include <rw/loaders/WorkCellLoader.hpp>
#include <rwlibs/swig/ScriptTypes.hpp>
#include <rws/RobWorkStudio.hpp>

#include <QColor>
#include <QDebug>
#include <QFileDialog>
#include <QPushButton>
#include <QStandardItem>
#include <QStringListModel>
#include <QTabWidget>
#include <QTextStream>
#include <QVector2D>
#include <QVector3D>
#include <boost/filesystem.hpp>
#include <fstream>

// using namespace xercesc;
using namespace rw::core;
using namespace rw::loaders;
using namespace rw::math;
using namespace rws;
using namespace rwlibs;

WorkcellEditorWindow::WorkcellEditorWindow (rw::core::Log::Ptr output, rws::RobWorkStudio* rwstudio,
                                            QWidget* parent) :
    QMainWindow (parent),
    _output (output), _rws (rwstudio), ignoreNextWorkcellOpen (false)
{
    _ui = new Ui::WorkcellEditorWindow ();
    _ui->setupUi (this);
    _tabPane = new QTabWidget ();
    _tabPane->setTabsClosable (true);
    connect (
        _tabPane, SIGNAL (tabCloseRequested (int)), this, SLOT (on_actionClose_triggered (int)));

    this->setWindowTitle ("Workcell Editor");
    this->setCentralWidget (_tabPane);
    makeEditor ();

    openWorkCell (_rws->getWorkcell ()->getFilePath ().c_str ());
    rw::core::Log::infoLog () << "Opening: workcell "
                              << _rws->getWorkcell ()->getFilePath ().c_str () << "\n";
}

WorkcellEditorWindow::~WorkcellEditorWindow ()
{}

void WorkcellEditorWindow::ShowContextMenu (const QPoint& pos)
{
    /*QMenu *menu = getCurrentTab()->_editor->createStandardContextMenu();
    menu->setTitle("Edit");
    // for most widgets
    QPoint globalPos = this->mapToGlobal(pos);
    // for QAbstractScrollArea and derived classes you would use:
    // QPoint globalPos = myWidget->viewport()->mapToGlobal(pos);

    QMenu myMenu;
    QMenu deviceMenu("Devices");
    std::vector<rw::models::Device::Ptr> devs = _rws->getWorkcell()->getDevices();
    for (rw::models::Device::Ptr dev : devs) {
        QMenu *devMenu = new QMenu(dev->getName().c_str());
        connect(devMenu, SIGNAL(triggered(QAction * )), this, SLOT(setCheckAction(QAction * )));
        QAction *action = devMenu->addAction("Get Q");
        devMenu->addAction(action);
        devMenu->addAction("Get Pos Limits");
        devMenu->addAction("Get Vel Limits");
        devMenu->addAction("Get Acc Limits");

        deviceMenu.addMenu(devMenu);
    }

    QMenu objectMenu("Objects");
    std::vector<rw::models::Object::Ptr> objects = _rws->getWorkcell()->getObjects();
    for (rw::models::Object::Ptr dev : objects) {
        QMenu *devMenu = new QMenu(dev->getName().c_str());
        connect(devMenu, SIGNAL(triggered(QAction * )), this, SLOT(setCheckAction(QAction * )));

        QAction *action = devMenu->addAction("Get Transform");
        devMenu->addAction(action);

        action = devMenu->addAction("Get Transform W");
        devMenu->addAction(action);

        //devMenu->addAction( "Get Pos Limits" );
        //devMenu->addAction( "Get Vel Limits" );
        //devMenu->addAction( "Get Acc Limits" );

        objectMenu.addMenu(devMenu);
    }

    QMenu *rwsMenu = new QMenu("RWStudio");
    connect(rwsMenu, SIGNAL(triggered(QAction * )), this, SLOT(setCheckAction(QAction * )));
    QAction *action;
    action = rwsMenu->addAction("View Transform");
    rwsMenu->addAction(action);
    action = rwsMenu->addAction("View Transform (RPY)");
    rwsMenu->addAction(action);
    action = rwsMenu->addAction("View Transform (Quat)");
    rwsMenu->addAction(action);

    myMenu.addMenu(&deviceMenu);
    myMenu.addMenu(&objectMenu);
    myMenu.addMenu(rwsMenu);
    myMenu.addMenu(menu);
    // ...

    QAction *selectedItem = myMenu.exec(globalPos);
    if (selectedItem) {
        // something was chosen, do stuff
    } else {
        // nothing was chosen
    }*/
}

void WorkcellEditorWindow::setCheckAction (QAction* action)
{
    /*QObject *obj = sender();
    QMenu *menu = dynamic_cast<QMenu *>(obj);
    std::string devname = menu->title().toStdString();
    std::string actionStr = action->text().toStdString();
    rw::models::Device::Ptr dev = _rws->getWorkcell()->findDevice(devname);
    if (actionStr == "Get Q") {
        rw::math::Q q = dev->getQ(_rws->getState());
        std::stringstream sstr;
        sstr << "rw.Q(" << q.size() << ",{";
        for (size_t i = 0; i < q.size() - 1; i++)
            sstr << q[i] << ",";
        sstr << q[q.size() - 1] << "})";
        getCurrentTab()->_editor->insertCompletion(sstr.str().c_str());
    } else if (actionStr == "View Transform (RPY)") {
        rw::math::Transform3D<> t3d =
            _rws->getView()->getSceneViewer()->getTransform(); RPY<> rpy(t3d.R());
        std::stringstream sstr;
        sstr << "rw.Transform3D( rw.Vector3D(" << t3d.P()[0] << "," <<
            t3d.P()[1] << "," << t3d.P()[2] << "),"
             << "rw.RPY(" << rpy[0] << "," << rpy[1] << "," << rpy[2] << "))";
        getCurrentTab()->_editor->insertCompletion(sstr.str().c_str());
    }*/
}

WorkcellEditorWindow::EditorTab::Ptr WorkcellEditorWindow::makeEditor ()
{
    QFont font;
    font.setFamily ("Courier");
    font.setFixedPitch (true);
    font.setPointSize (12);

    EditorTab::Ptr etab = ownedPtr (new EditorTab ());
    etab->_editor       = new WCCodeEditor (this);
    etab->_editor->setFont (font);

    QPalette p            = etab->_editor->palette ();
    QColor* editorBgColor = new QColor (238, 238, 238);
    p.setColor (QPalette::Active, QPalette::Base, editorBgColor->rgb ());
    p.setColor (QPalette::Inactive, QPalette::Base, editorBgColor->rgb ());
    etab->_editor->setPalette (p);

    const int tabStop = 2;    // 2 characters
    QFontMetrics metrics (font);
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
    etab->_editor->setTabStopDistance (tabStop * metrics.horizontalAdvance (' '));
#else
    etab->_editor->setTabStopDistance (tabStop * metrics.width (' '));
#endif
#else
    etab->_editor->setTabStopWidth (tabStop * metrics.width (' '));
#endif

    etab->_completer = new WCECompleter (etab->_editor);
    // etab->_completer->setSeparator (QLatin1String ("."));
    etab->_completer->setModel (modelFromFile (":/word_list.txt", etab->_completer));
    etab->_completer->setCompletionMode (QCompleter::PopupCompletion);
    etab->_completer->setModelSorting (QCompleter::CaseInsensitivelySortedModel);
    etab->_completer->setCaseSensitivity (Qt::CaseInsensitive);
    etab->_completer->setWrapAround (false);

    etab->_editor->setCompleter (etab->_completer);

    etab->_highlighter = new WorkcellHighlighter (etab->_editor->document ());
    // connect(etab->_editor, SIGNAL(modificationChanged(bool)), this,
    // SLOT(textChanged()));
    // etab->_editor->setContextMenuPolicy(Qt::CustomContextMenu);
    // connect(etab->_editor, SIGNAL(customContextMenuRequested(const QPoint&)),
    //		this, SLOT(ShowContextMenu(const QPoint&)));

    // Add workcell tag to new workcells by default with empty name attribute
    QString default_text = "<WorkCell name=\"\">\n\n</WorkCell>";
    etab->_editor->setPlainText (default_text);
    _tabPane->addTab (etab->_editor, "New");
    _editors[etab->_editor] = etab;
    return etab;
}

void WorkcellEditorWindow::runFinished ()
{
    _tabPane->setEnabled (true);
}

void WorkcellEditorWindow::textChanged ()
{
    //_modified = _editor->document()->isModified();
}

void WorkcellEditorWindow::on_actionClose_triggered (bool)
{
    // todo: if last tab then don't remove it.
    if (_tabPane->count () == 1)
        return;
    EditorTab::Ptr tab = getCurrentTab ();
    if (tab->_editor->document ()->isModified ())
        save ();
    _tabPane->removeTab (_tabPane->indexOf (tab->_editor));
    _editors[tab->_editor] = NULL;
}

void WorkcellEditorWindow::on_actionClose_triggered (int index)
{
    // todo: if last tab then don't remove it.
    if (_tabPane->count () == 1)
        return;
    EditorTab::Ptr tab = _editors[_tabPane->widget (index)];
    if (tab->_editor->document ()->isModified ())
        save ();
    _tabPane->removeTab (index);
    _editors[tab->_editor] = NULL;
}

void WorkcellEditorWindow::on_actionNew_triggered (bool)
{
    // create a new tab
    EditorTab::Ptr tab = makeEditor ();
    _tabPane->setCurrentIndex (_tabPane->indexOf (tab->_editor));
    QString default_text = "<WorkCell name=\"\">\n\n</WorkCell>";
    tab->_editor->setPlainText (default_text);
    tab->_editor->document ()->setModified (false);
}

void WorkcellEditorWindow::on_actionOpen_triggered (bool)
{
    // on_actionNew_triggered(true);
    QString path = _pmap.get< std::string > ("PreviousOpenDirectory", ".").c_str ();

    QString fileName = QFileDialog::getOpenFileName (this,
                                                     tr ("Open File"),
                                                     path,
                                                     "Supported (*.wc.xml *.xml)"
                                                     "\nWorkcell Files (*.wc.xml)"
                                                     "\nXML Files (*.xml)"
                                                     "\nAll (*.*)");

    openWorkCell (fileName);
}

void WorkcellEditorWindow::on_actionSave_triggered (bool)
{
    save ();
}

void WorkcellEditorWindow::on_actionSave_As_triggered (bool)
{
    saveAs ();
}

void WorkcellEditorWindow::on_actionAdd_Frame_triggered (bool)
{
    // Load Workcell

    // Define form inputs
    InputFormDialog::FormData data;
    data["Name"]          = "";
    QStringList refFrames = getRefFrameList ();
    if (refFrames.isEmpty ()) {
        std::cout << "wc is null" << std::endl;
        data["Ref. frame"] = "";
    }
    else {
        data["Ref. frame"] = refFrames;
    }

    data["Type"] = QStringList () << "Fixed"
                                  << "Movable";
    data["Show Frame Axis"] = false;
    data["Position"]        = QVector3D (0.0, 0.0, 0.0);
    data["RPY"]             = QVector3D (0.0, 0.0, 0.0);

    /*data["Bool"] = true;
    data["Color"] = QColor("red");
    data["Int"] = 1;
    data["String"] = "Test";
    data["ComboBox"] = QStringList() << "One" << "Two";
    data["Vector2"] = QVector2D(10.0, 5.0);*/

    // Define form options
    InputFormDialog::FormOptions options;
    options.listDisplaysAsRadios = false;
    options.listReturnsIndex     = false;

    // Ask user for input and retrieve data
    if (InputFormDialog::getInput ("Add Frame", data, options)) {
        // qDebug() << data.at<QString>("Name");
        // qDebug() << data.at<QString>("Ref. frame");
        // qDebug() << data.at<QString>("Type");
        // qDebug() << data.at<QVector3D>("Position");
        // qDebug() << data.at<QVector3D>("RPY");
        QString frame_type = "";
        if (data.at< QString > ("Type") == "Movable")
            frame_type = "type=\"Movable\"";

        QString show_frame_axis = "";
        if (data.at< bool > ("Show Frame Axis") == true) {
            show_frame_axis = "\t<Property name=\"ShowFrameAxis\">true</Property>\n";
        }

        QString frame_str;
        QTextStream (&frame_str) << "<Frame name=\"" << data.at< QString > ("Name") << "\" "
                                 << "refframe=\"" << data.at< QString > ("Ref. frame") << "\" "
                                 << frame_type << ">"
                                 << "\n\t"
                                 << "<Pos>" << data.at< QVector3D > ("Position").x () << " "
                                 << data.at< QVector3D > ("Position").y () << " "
                                 << data.at< QVector3D > ("Position").z () << "</Pos>\n\t"
                                 << "<RPY>" << data.at< QVector3D > ("RPY").x () << " "
                                 << data.at< QVector3D > ("RPY").y () << " "
                                 << data.at< QVector3D > ("RPY").z () << "</RPY>"
                                 << "\n"
                                 << show_frame_axis << "</Frame>";

        // Insert frame at cursor position
        getCurrentTab ()->_editor->insertXMLTextUnderCursor (frame_str);
    }
}

void WorkcellEditorWindow::on_actionAdd_Drawable_triggered (bool)
{
    std::string defaultName = _pmap.get< std::string > ("WorkcellFile", "");
    std::size_t found       = defaultName.find_last_of ("/\\");
    std::string cad_path    = defaultName.substr (0, found);

    // Define form options
    InputFormDialog::FormOptions options;
    options.listDisplaysAsRadios = false;
    options.listReturnsIndex     = false;

    // Define form inputs
    InputFormDialog::FormData data;
    data["Name"]          = "";
    QStringList refFrames = getRefFrameList ();
    if (refFrames.isEmpty ()) {
        std::cout << "wc is null" << std::endl;
        data["Ref. frame"] = "";
    }
    else {
        data["Ref. frame"] = refFrames;
    }
    data["CollisionModel"]  = true;
    data["Mesh Resolution"] = 20;
    data["Position"]        = QVector3D (0.0, 0.0, 0.0);
    data["RPY"]             = QVector3D (0.0, 0.0, 0.0);
    data["CAD file"]        = "cad_file," + QString::fromStdString (cad_path);
    data["Geometry Type"]   = QStringList () << "Box"
                                           << "Cone"
                                           << "Cylinder"
                                           << "Sphere";
    data["Color (r,g,b)"]             = QVector3D (0.6, 0.6, 0.6);
    data["Box (x, y, z)"]             = QVector3D (0.0, 0.0, 0.0);
    data["Cone (radiusBot, height)"]  = QVector2D (0.0, 0.0);
    data["Cylinder (radius, height)"] = QVector2D (0.0, 0.0);
    data["Sphere (r)"]                = 0.0;

    // Ask user for input and retrieve data

    if (InputFormDialog::getInput ("Add Drawable", data, options)) {
        QString col_model = "Enabled";
        if (data.at< bool > ("CollisionModel")) {
            col_model = "Enabled";
        }
        else {
            col_model = "Disabled";
        }
        QString ref_frame_str = "";
        if (!data.at< QString > ("Ref. frame").isEmpty ()) {
            ref_frame_str = " refframe=\"" + data.at< QString > ("Ref. frame") + "\"";
        }
        else {
            ref_frame_str = "";
        }

        // If the drawable is defined inside a frame, we indent with an
        // additional tab
        QString indentation = "";
        if (ref_frame_str.isEmpty ())
            indentation = "    ";
        else
            indentation = "  ";

        QString model_str = "";

        if (data.at< QString > ("CAD file").isEmpty ()) {
            // Geometry I presume?
            if (data.at< QString > ("Geometry Type") == "Box") {
                QTextStream (&model_str)
                    << indentation << "  <Box x=\"" << data.at< QVector3D > ("Box (x, y, z)").x ()
                    << "\" y=\"" << data.at< QVector3D > ("Box (x, y, z)").y () << "\" z=\""
                    << data.at< QVector3D > ("Box (x, y, z)").z () << "\"/>";
            }
            else if (data.at< QString > ("Geometry Type") == "Cone") {
                QTextStream (&model_str)
                    << indentation << "  <Cone radius=\""
                    << data.at< QVector2D > ("Cone (radiusBot, height)").x () << "\" z=\""
                    << data.at< QVector2D > ("Cone (radiusBot, height)").y () << "\" level=\""
                    << data.at< int > ("Mesh Resolution") << "\"/>";
            }
            else if (data.at< QString > ("Geometry Type") == "Cylinder") {
                QTextStream (&model_str)
                    << indentation << "  <Cylinder radius=\""
                    << data.at< QVector2D > ("Cylinder (radius, height)").x () << "\" z=\""
                    << data.at< QVector2D > ("Cylinder (radius, height)").y () << "\" level=\""
                    << data.at< int > ("Mesh Resolution") << "\"/>";
            }
            else if (data.at< QString > ("Geometry Type") == "Sphere") {
                QTextStream (&model_str)
                    << indentation << "  <Sphere radius=\"" << data.at< double > ("Sphere (r)")
                    << "\" level=\"" << data.at< int > ("Mesh Resolution") << "\"/>";
            }
        }
        else {
            QTextStream (&model_str) << indentation << "  <Polytope file=\""
                                     << data.at< QString > ("CAD file") << "\"/>";
        }

        QString drawable_str;
        QTextStream (&drawable_str)
            << "<Drawable name=\"" << data.at< QString > ("Name") << "\"" << ref_frame_str
            << " colmodel=\"" << col_model << "\">"
            << "\n"
            <<
            // POS
            indentation << "  <Pos>" << data.at< QVector3D > ("Position").x () << " "
            << data.at< QVector3D > ("Position").y () << " "
            << data.at< QVector3D > ("Position").z ()
            << "</Pos>\n"
            // RPY
            << indentation << "  <RPY>" << data.at< QVector3D > ("RPY").x () << " "
            << data.at< QVector3D > ("RPY").y () << " " << data.at< QVector3D > ("RPY").z ()
            << "</RPY>\n"
            // RGB
            << indentation << "  <RGB>" << data.at< QVector3D > ("Color (r,g,b)").x () << " "
            << data.at< QVector3D > ("Color (r,g,b)").y () << " "
            << data.at< QVector3D > ("Color (r,g,b)").z ()
            << " </RGB>\n"
            // MODEL
            << model_str << "\n"
            << indentation << "</Drawable>";

        // Insert frame at cursor position
        getCurrentTab ()->_editor->insertXMLTextUnderCursor (drawable_str);
    }
}

void WorkcellEditorWindow::on_actionAdd_CollisionModel_triggered (bool)
{
    std::string defaultName = _pmap.get< std::string > ("WorkcellFile", "");
    std::size_t found       = defaultName.find_last_of ("/\\");
    std::string cad_path    = defaultName.substr (0, found);

    // Define form options
    InputFormDialog::FormOptions options;
    options.listDisplaysAsRadios = false;
    options.listReturnsIndex     = false;

    // Define form inputs
    InputFormDialog::FormData data;
    data["Name"]          = "";
    QStringList refFrames = getRefFrameList ();
    if (refFrames.isEmpty ()) {
        std::cout << "wc is null" << std::endl;
        data["Ref. frame"] = "";
    }
    else {
        data["Ref. frame"] = refFrames;
    }
    data["Mesh Resolution"] = 20;
    data["Position"]        = QVector3D (0.0, 0.0, 0.0);
    data["RPY"]             = QVector3D (0.0, 0.0, 0.0);
    data["CAD file"]        = "cad_file," + QString::fromStdString (cad_path);
    data["Geometry Type"]   = QStringList () << "Box"
                                           << "Cone"
                                           << "Cylinder"
                                           << "Sphere";
    data["Box (x, y, z)"]             = QVector3D (0.0, 0.0, 0.0);
    data["Cone (radiusBot, height)"]  = QVector2D (0.0, 0.0);
    data["Cylinder (radius, height)"] = QVector2D (0.0, 0.0);
    data["Sphere (r)"]                = 0.0;

    // Ask user for input and retrieve data
    if (InputFormDialog::getInput ("Add Collision Model", data, options)) {
        QString ref_frame_str = "";
        if (!data.at< QString > ("Ref. frame").isEmpty ())
            ref_frame_str = " refframe=\"" + data.at< QString > ("Ref. frame") + "\"";
        else
            ref_frame_str = "";

        // If the drawable is defined inside a frame, we indent with an
        // additional tab
        QString indentation = "";
        if (ref_frame_str.isEmpty ())
            indentation = "    ";
        else
            indentation = "  ";

        QString model_str = "";
        if (data.at< QString > ("CAD file").isEmpty ()) {
            // Geometry I presume?
            if (data.at< QString > ("Geometry Type") == "Box") {
                QTextStream (&model_str)
                    << "<Box x=\"" << data.at< QVector3D > ("Box (x, y, z)").x () << "\" y=\""
                    << data.at< QVector3D > ("Box (x, y, z)").y () << "\" z=\""
                    << data.at< QVector3D > ("Box (x, y, z)").z () << "\"/>";
            }
            else if (data.at< QString > ("Geometry Type") == "Cone") {
                QTextStream (&model_str)
                    << "<Cone radius=\"" << data.at< QVector2D > ("Cone (radiusBot, height)").x ()
                    << "\" z=\"" << data.at< QVector2D > ("Cone (radiusBot, height)").y ()
                    << "\" level=\"" << data.at< int > ("Mesh Resolution") << "\"/>";
            }
            else if (data.at< QString > ("Geometry Type") == "Cylinder") {
                QTextStream (&model_str)
                    << "<Cylinder radius=\""
                    << data.at< QVector2D > ("Cylinder (radius, height)").x () << "\" z=\""
                    << data.at< QVector2D > ("Cylinder (radius, height)").y () << "\" level=\""
                    << data.at< int > ("Mesh Resolution") << "\"/>";
            }
            else if (data.at< QString > ("Geometry Type") == "Sphere") {
                QTextStream (&model_str)
                    << "<Sphere radius=\"" << data.at< double > ("Sphere (r)") << "\" level=\""
                    << data.at< int > ("Mesh Resolution") << "\"/>";
            }
        }
        else {
            QTextStream (&model_str)
                << "<Polytope file=\"" << data.at< QString > ("CAD file") << "\"/>";
        }

        QString col_model_str;
        QTextStream (&col_model_str)
            << "<CollisionModel name=\"" << data.at< QString > ("Name") << "\"" << ref_frame_str
            << ">"
            << "\n"
            // POS
            << indentation << "  <Pos>" << data.at< QVector3D > ("Position").x () << " "
            << data.at< QVector3D > ("Position").y () << " "
            << data.at< QVector3D > ("Position").z ()
            << "</Pos>\n"
            // RPY
            << indentation << "  <RPY>" << data.at< QVector3D > ("RPY").x () << " "
            << data.at< QVector3D > ("RPY").y () << " " << data.at< QVector3D > ("RPY").z ()
            << "</RPY>"
            << "\n"
            // Model
            << indentation << "  " << model_str << "\n"
            << indentation << "</CollisionModel>";

        // Insert frame at cursor position
        getCurrentTab ()->_editor->insertXMLTextUnderCursor (col_model_str);
    }
}

bool WorkcellEditorWindow::save ()
{
    // save the current viable
    std::string filename = getCurrentTab ()->_filename;
    if (filename == "") {
        return saveAs ();
    }
    else {
        return save (filename);
    }
}

bool WorkcellEditorWindow::saveAs ()
{
    std::string defaultName = _pmap.get< std::string > ("WorkcellFile", "");
    QString filename        = QFileDialog::getSaveFileName (
        this, "Save as", defaultName.c_str (), "Supported (*.wc.xml *.xml)");

    if (filename.isEmpty ())
        return false;

    return save (filename.toStdString ());
}

bool WorkcellEditorWindow::save (const std::string& filename)
{
    bool isTmpFile = false;
    if (filename.find (".temp.wc.xml") < filename.size ()) {
        isTmpFile = true;
    }
    QFile file;
    file.setFileName (filename.c_str ());
    if (file.open (QFile::WriteOnly | QFile::Text)) {
        {
            QTextStream out (&file);
            out << getCurrentTab ()->_editor->toPlainText ();
            if (!isTmpFile) {
                _pmap.set< std::string > ("WorkcellFile", filename);
                getCurrentTab ()->_editor->document ()->setModified (false);
                getCurrentTab ()->_filename = filename;

                _tabPane->setTabText (
                    _tabPane->indexOf (getCurrentTab ()->_editor),
                    boost::filesystem::path (filename).filename ().string ().c_str ());
            }
        }
        file.close ();

        // Verify workcell xml using schemas
        /*try {
            XMLPlatformUtils::Initialize();  // Initialize Xerces infrastructure
        } catch( XMLException& e ){
            RW_THROW("Xerces initialization
        Error"<<XMLStr(e.getMessage()).str());
        }

        XercesDOMParser parser;
        xercesc::DOMDocument* doc = XercesDocumentReader::readDocument(parser,
        filename,
         "/home/prier/RobWork/RobWork/xml-schemas/rwxml_workcell.xsd");
        */

        rw::core::Log ().setLevel (rw::core::Log::Debug);
        rw::models::WorkCell::Ptr wc = nullptr;

        if (!isTmpFile) {
            std::string wcFilename = _pmap.get< std::string > ("WorkcellFile", "");
            wc                     = rw::loaders::WorkCellLoader::Factory::load (wcFilename);
        }
        else {
            wc = rw::loaders::WorkCellLoader::Factory::load (filename);
            file.remove ();
        }
        rw::core::Log ().setLevel (rw::core::Log::Info);

        if (_rws != nullptr) {
            ignoreNextWorkcellOpen = true;
            _rws->setWorkcell (wc);
        }

        return true;
    }
    else {
        file.close ();
        return false;
    }
}

bool WorkcellEditorWindow::openWorkCell (const QString& fileName)
{
    bool success = false;
    if (!fileName.isEmpty () && !ignoreNextWorkcellOpen) {
        _pmap.set< std::string > ("PreviousOpenDirectory",
                                  StringUtil::getDirectoryName (fileName.toStdString ()));
        _pmap.set< std::string > ("WorkcellFile", fileName.toStdString ());
        QFile file;
        file.setFileName (fileName);
        EditorTab::Ptr tab = makeEditor ();
        tab->_filename     = file.fileName ().toStdString ();
        _tabPane->setTabText (
            _tabPane->indexOf (tab->_editor),
            boost::filesystem::path (fileName.toStdString ()).filename ().string ().c_str ());

        if (file.open (QFile::ReadWrite | QFile::Text)) {
            tab->_editor->setPlainText (file.readAll ());
            success = true;
        }
        file.close ();
        tab->_editor->document ()->setModified (false);
        _tabPane->setCurrentIndex (_tabPane->indexOf (tab->_editor));
    }
    else if (ignoreNextWorkcellOpen) {
        ignoreNextWorkcellOpen = false;
    }

    return success;
}

QStringList WorkcellEditorWindow::getRefFrameList ()
{
    EditorTab::Ptr tab           = getCurrentTab ();
    rw::models::WorkCell::Ptr wc = NULL;
    QStringList refFrames;
    refFrames << "";
    if (tab->_filename.size () > 5 /*minimum length*/) {
        std::string file = tab->_filename + ".temp1234.xml";

        std::ofstream out (file);
        out << tab->_editor->toPlainText ().toStdString ();
        out.close ();

        wc = WorkCellLoader::Factory::load (file);
        remove (file.c_str ());

        if (!wc.isNull ()) {
            std::vector< rw::kinematics::Frame* > frameList = wc->getFrames ();
            for (size_t i = 0; i < frameList.size (); i++) {
                refFrames << frameList[i]->getName ().c_str ();
            }
        }
    }
    return refFrames;
}

WorkcellEditorWindow::EditorTab::Ptr WorkcellEditorWindow::getCurrentTab ()
{
    return _editors[_tabPane->currentWidget ()];
}

void WorkcellEditorWindow::on_actionRun_triggered (bool)
{
    // Save as temproary file
    save (getCurrentTab ()->_filename + ".temp.wc.xml");
}

void WorkcellEditorWindow::on_actionReload_triggered (bool)
{
    std::string fileName = getCurrentTab ()->_filename;
    if (fileName != "") {
        QFile file;
        file.setFileName (fileName.c_str ());
        if (file.open (QFile::ReadWrite | QFile::Text)) {
            getCurrentTab ()->_editor->setPlainText (file.readAll ());
        }
        file.close ();
    }
}

QAbstractItemModel* WorkcellEditorWindow::modelFromFile (const QString& fileName,
                                                         WCECompleter* completer)
{
    QFile file (fileName);
    if (!file.open (QFile::ReadOnly))
        return new QStringListModel (completer);

#ifndef QT_NO_CURSOR
    QApplication::setOverrideCursor (QCursor (Qt::WaitCursor));
#endif
    QStringList words;

    QStandardItemModel* model = new QStandardItemModel (completer);
    QVector< QStandardItem* > parents (10);
    parents[0] = model->invisibleRootItem ();

    while (!file.atEnd ()) {
        QString line        = file.readLine ();
        QString trimmedLine = line.trimmed ();
        if (line.isEmpty () || trimmedLine.isEmpty ())
            continue;

        const QRegularExpression re ("^\\s+");
        const QRegularExpressionMatch match = re.match(line);
        int level = 0;
        if (!match.hasMatch()) {
            level = 0;
        }
        else {
            if (line.startsWith ("\t")) {
                level = match.capturedLength(0);
            }
            else {
                level = match.capturedLength(0) / 4;
            }
        }

        if (level + 1 >= parents.size ())
            parents.resize (parents.size () * 2);

        QStandardItem* item = new QStandardItem;
        item->setText (trimmedLine);
        parents[level]->appendRow (item);
        parents[level + 1] = item;
    }

#ifndef QT_NO_CURSOR
    QApplication::restoreOverrideCursor ();
#endif

    return model;
}


text:
// Distributed under the MIT License.
// See LICENSE.txt for details.

#pragma once

#include <boost/preprocessor/arithmetic/sub.hpp>
#include <boost/preprocessor/list/for_each.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/tuple/enum.hpp>
#include <boost/preprocessor/tuple/to_list.hpp>

#include "DataStructures/Tensor/TypeAliases.hpp"
#include "Parallel/CharmPupable.hpp"
#include "Utilities/TMPL.hpp"
#include "Utilities/TypeTraits.hpp"

/// \cond
class DataVector;
namespace EquationsOfState {
template <bool IsRelativistic>
class DarkEnergyFluid;
template <bool IsRelativistic>
class IdealFluid;
template <bool IsRelativistic>
class PolytropicFluid;
class Spectral;
}  // namespace EquationsOfState
/// \endcond

/// Contains all equations of state, including base class
namespace EquationsOfState {

namespace detail {
template <bool IsRelativistic, size_t ThermodynamicDim>
struct DerivedClasses {};

template <>
struct DerivedClasses<true, 1> {
  using type = tmpl::list<Spectral, PolytropicFluid<true>>;
};

template <>
struct DerivedClasses<false, 1> {
  using type = tmpl::list<PolytropicFluid<false>>;
};

template <>
struct DerivedClasses<true, 2> {
  using type = tmpl::list<DarkEnergyFluid<true>, IdealFluid<true>>;
};

template <>
struct DerivedClasses<false, 2> {
  using type = tmpl::list<IdealFluid<false>>;
};

}  // namespace detail

/*!
 * \ingroup EquationsOfStateGroup
 * \brief Base class for equations of state depending on whether or not the
 * system is relativistic, and the number of independent thermodynamic variables
 * (`ThermodynamicDim`) needed to determine the pressure.
 *
 * The template parameter `IsRelativistic` is `true` for relativistic equations
 * of state and `false` for non-relativistic equations of state.
 */
template <bool IsRelativistic, size_t ThermodynamicDim>
class EquationOfState;

/*!
 * \ingroup EquationsOfStateGroup
 * \brief Base class for equations of state which need one thermodynamic
 * variable in order to determine the pressure.
 *
 * The template parameter `IsRelativistic` is `true` for relativistic equations
 * of state and `false` for non-relativistic equations of state.
 */
template <bool IsRelativistic>
class EquationOfState<IsRelativistic, 1>
    : public PUP::able {
 public:
  static constexpr bool is_relativistic = IsRelativistic;
  static constexpr size_t thermodynamic_dim = 1;
  using creatable_classes =
      typename detail::DerivedClasses<IsRelativistic, 1>::type;

  EquationOfState() = default;
  EquationOfState(const EquationOfState&) = default;
  EquationOfState& operator=(const EquationOfState&) = default;
  EquationOfState(EquationOfState&&) = default;
  EquationOfState& operator=(EquationOfState&&) = default;
  ~EquationOfState() override = default;

  WRAPPED_PUPable_abstract(EquationOfState);  // NOLINT

  /// @{
  /*!
   * Computes the pressure \f$p\f$ from the rest mass density \f$\rho\f$.
   */
  virtual Scalar<double> pressure_from_density(
      const Scalar<double>& /*rest_mass_density*/) const = 0;
  virtual Scalar<DataVector> pressure_from_density(
      const Scalar<DataVector>& /*rest_mass_density*/) const = 0;
  /// @}

  /// @{
  /*!
   * Computes the rest mass density \f$\rho\f$ from the specific enthalpy
   * \f$h\f$.
   */
  virtual Scalar<double> rest_mass_density_from_enthalpy(
      const Scalar<double>& /*specific_enthalpy*/) const = 0;
  virtual Scalar<DataVector> rest_mass_density_from_enthalpy(
      const Scalar<DataVector>& /*specific_enthalpy*/) const = 0;
  /// @}

  /// @{
  /*!
   * Computes the specific enthalpy \f$h\f$ from the rest mass density
   * \f$\rho\f$.
   */
  virtual Scalar<double> specific_enthalpy_from_density(
      const Scalar<double>& /*rest_mass_density*/) const = 0;
  virtual Scalar<DataVector> specific_enthalpy_from_density(
      const Scalar<DataVector>& /*rest_mass_density*/) const = 0;
  /// @}

  /// @{
  /*!
   * Computes the specific internal energy \f$\epsilon\f$ from the rest mass
   * density \f$\rho\f$.
   */
  virtual Scalar<double> specific_internal_energy_from_density(
      const Scalar<double>& /*rest_mass_density*/) const = 0;
  virtual Scalar<DataVector> specific_internal_energy_from_density(
      const Scalar<DataVector>& /*rest_mass_density*/) const = 0;
  /// @}

  /// @{
  /*!
   * Computes \f$\chi=\partial p / \partial \rho\f$ from \f$\rho\f$, where
   * \f$p\f$ is the pressure and \f$\rho\f$ is the rest mass density.
   */
  virtual Scalar<double> chi_from_density(
      const Scalar<double>& /*rest_mass_density*/) const = 0;
  virtual Scalar<DataVector> chi_from_density(
      const Scalar<DataVector>& /*rest_mass_density*/) const = 0;
  /// @}

  /// @{
  /*!
   * Computes \f$\kappa p/\rho^2=(p/\rho^2)\partial p / \partial \epsilon\f$
   * from \f$\rho\f$, where \f$p\f$ is the pressure, \f$\rho\f$ is the rest mass
   * density, and \f$\epsilon\f$ is the specific internal energy.
   *
   * The reason for not returning just
   * \f$\kappa=\partial p / \partial \epsilon\f$ is to avoid division by zero
   * for small values of \f$\rho\f$ when assembling the speed of sound with
   * some equations of state.
   */
  virtual Scalar<double> kappa_times_p_over_rho_squared_from_density(
      const Scalar<double>& /*rest_mass_density*/) const = 0;
  virtual Scalar<DataVector> kappa_times_p_over_rho_squared_from_density(
      const Scalar<DataVector>& /*rest_mass_density*/) const = 0;

  /// The lower bound of the rest mass density that is valid for this EOS
  virtual double rest_mass_density_lower_bound() const = 0;

  /// The upper bound of the rest mass density that is valid for this EOS
  virtual double rest_mass_density_upper_bound() const = 0;

  /// The lower bound of the specific internal energy that is valid for this EOS
  /// at the given rest mass density \f$\rho\f$
  virtual double specific_internal_energy_lower_bound(
      const double rest_mass_density) const = 0;

  /// The upper bound of the specific internal energy that is valid for this EOS
  /// at the given rest mass density \f$\rho\f$
  virtual double specific_internal_energy_upper_bound(
      const double rest_mass_density) const = 0;

  /// The lower bound of the specific enthalpy that is valid for this EOS
  virtual double specific_enthalpy_lower_bound() const = 0;
};

/*!
 * \ingroup EquationsOfStateGroup
 * \brief Base class for equations of state which need two independent
 * thermodynamic variables in order to determine the pressure.
 *
 * The template parameter `IsRelativistic` is `true` for relativistic equations
 * of state and `false` for non-relativistic equations of state.
 */
template <bool IsRelativistic>
class EquationOfState<IsRelativistic, 2>
    : public PUP::able {
 public:
  static constexpr bool is_relativistic = IsRelativistic;
  static constexpr size_t thermodynamic_dim = 2;
  using creatable_classes =
      typename detail::DerivedClasses<IsRelativistic, 2>::type;

  EquationOfState() = default;
  EquationOfState(const EquationOfState&) = default;
  EquationOfState& operator=(const EquationOfState&) = default;
  EquationOfState(EquationOfState&&) = default;
  EquationOfState& operator=(EquationOfState&&) = default;
  ~EquationOfState() override = default;

  WRAPPED_PUPable_abstract(EquationOfState);  // NOLINT

  /// @{
  /*!
   * Computes the pressure \f$p\f$ from the rest mass density \f$\rho\f$ and the
   * specific internal energy \f$\epsilon\f$.
   */
  virtual Scalar<double> pressure_from_density_and_energy(
      const Scalar<double>& /*rest_mass_density*/,
      const Scalar<double>& /*specific_internal_energy*/) const = 0;
  virtual Scalar<DataVector> pressure_from_density_and_energy(
      const Scalar<DataVector>& /*rest_mass_density*/,
      const Scalar<DataVector>& /*specific_internal_energy*/) const = 0;
  /// @}

  /// @{
  /*!
   * Computes the pressure \f$p\f$ from the rest mass density \f$\rho\f$ and the
   * specific enthalpy \f$h\f$.
   */
  virtual Scalar<double> pressure_from_density_and_enthalpy(
      const Scalar<double>& /*rest_mass_density*/,
      const Scalar<double>& /*specific_enthalpy*/) const = 0;
  virtual Scalar<DataVector> pressure_from_density_and_enthalpy(
      const Scalar<DataVector>& /*rest_mass_density*/,
      const Scalar<DataVector>& /*specific_enthalpy*/) const = 0;
  /// @}

  /// @{
  /*!
   * Computes the specific enthalpy \f$h\f$ from the rest mass density
   * \f$\rho\f$ and the specific internal energy \f$\epsilon\f$.
   */
  virtual Scalar<double> specific_enthalpy_from_density_and_energy(
      const Scalar<double>& /*rest_mass_density*/,
      const Scalar<double>& /*specific_internal_energy*/) const = 0;
  virtual Scalar<DataVector> specific_enthalpy_from_density_and_energy(
      const Scalar<DataVector>& /*rest_mass_density*/,
      const Scalar<DataVector>& /*specific_internal_energy*/) const = 0;
  /// @}

  /// @{
  /*!
   * Computes the specific internal energy \f$\epsilon\f$ from the rest mass
   * density \f$\rho\f$ and the pressure \f$p\f$.
   */
  virtual Scalar<double> specific_internal_energy_from_density_and_pressure(
      const Scalar<double>& /*rest_mass_density*/,
      const Scalar<double>& /*pressure*/) const = 0;
  virtual Scalar<DataVector> specific_internal_energy_from_density_and_pressure(
      const Scalar<DataVector>& /*rest_mass_density*/,
      const Scalar<DataVector>& /*pressure*/) const = 0;
  /// @}

  /// @{
  /*!
   * Computes \f$\chi=\partial p / \partial \rho\f$ from the \f$\rho\f$ and
   * \f$\epsilon\f$, where \f$p\f$ is the pressure, \f$\rho\f$ is the rest mass
   * density, and \f$\epsilon\f$ is the specific internal energy.
   */
  virtual Scalar<double> chi_from_density_and_energy(
      const Scalar<double>& /*rest_mass_density*/,
      const Scalar<double>& /*specific_internal_energy*/) const = 0;
  virtual Scalar<DataVector> chi_from_density_and_energy(
      const Scalar<DataVector>& /*rest_mass_density*/,
      const Scalar<DataVector>& /*specific_internal_energy*/) const = 0;
  /// @}

  /// @{
  /*!
   * Computes \f$\kappa p/\rho^2=(p/\rho^2)\partial p / \partial \epsilon\f$
   * from \f$\rho\f$ and \f$\epsilon\f$, where \f$p\f$ is the pressure,
   * \f$\rho\f$ is the rest mass density, and \f$\epsilon\f$ is the specific
   * internal energy.
   *
   * The reason for not returning just
   * \f$\kappa=\partial p / \partial \epsilon\f$ is to avoid division by zero
   * for small values of \f$\rho\f$ when assembling the speed of sound with
   * some equations of state.
   */
  virtual Scalar<double> kappa_times_p_over_rho_squared_from_density_and_energy(
      const Scalar<double>& /*rest_mass_density*/,
      const Scalar<double>& /*specific_internal_energy*/) const = 0;
  virtual Scalar<DataVector>
  kappa_times_p_over_rho_squared_from_density_and_energy(
      const Scalar<DataVector>& /*rest_mass_density*/,
      const Scalar<DataVector>& /*specific_internal_energy*/) const = 0;
  /// @}

  /// The lower bound of the rest mass density that is valid for this EOS
  virtual double rest_mass_density_lower_bound() const = 0;

  /// The upper bound of the rest mass density that is valid for this EOS
  virtual double rest_mass_density_upper_bound() const = 0;

  /// The lower bound of the specific internal energy that is valid for this EOS
  /// at the given rest mass density \f$\rho\f$
  virtual double specific_internal_energy_lower_bound(
      const double rest_mass_density) const = 0;

  /// The upper bound of the specific internal energy that is valid for this EOS
  /// at the given rest mass density \f$\rho\f$
  virtual double specific_internal_energy_upper_bound(
      const double rest_mass_density) const = 0;

  /// The lower bound of the specific enthalpy that is valid for this EOS
  virtual double specific_enthalpy_lower_bound() const = 0;
};
}  // namespace EquationsOfState

/// \cond
#define EQUATION_OF_STATE_FUNCTIONS_1D                                    \
  (pressure_from_density, rest_mass_density_from_enthalpy,                \
   specific_enthalpy_from_density, specific_internal_energy_from_density, \
   chi_from_density, kappa_times_p_over_rho_squared_from_density)

#define EQUATION_OF_STATE_FUNCTIONS_2D                                   \
  (pressure_from_density_and_energy, pressure_from_density_and_enthalpy, \
   specific_enthalpy_from_density_and_energy,                            \
   specific_internal_energy_from_density_and_pressure,                   \
   chi_from_density_and_energy,                                          \
   kappa_times_p_over_rho_squared_from_density_and_energy)

#define EQUATION_OF_STATE_ARGUMENTS_EXPAND(z, n, type) \
  BOOST_PP_COMMA_IF(n) const Scalar<type>&

#define EQUATION_OF_STATE_FORWARD_DECLARE_MEMBERS_HELPER(r, DIM,        \
                                                         FUNCTION_NAME) \
  Scalar<double> FUNCTION_NAME(BOOST_PP_REPEAT(                         \
      DIM, EQUATION_OF_STATE_ARGUMENTS_EXPAND, double)) const override; \
  Scalar<DataVector> FUNCTION_NAME(BOOST_PP_REPEAT(                     \
      DIM, EQUATION_OF_STATE_ARGUMENTS_EXPAND, DataVector)) const override;

/// \endcond

/*!
 * \ingroup EquationsOfStateGroup
 * \brief Macro used to generate forward declarations of member functions in
 * derived classes
 */
#define EQUATION_OF_STATE_FORWARD_DECLARE_MEMBERS(DERIVED, DIM)               \
  BOOST_PP_LIST_FOR_EACH(                                                     \
      EQUATION_OF_STATE_FORWARD_DECLARE_MEMBERS_HELPER, DIM,                  \
      BOOST_PP_TUPLE_TO_LIST(BOOST_PP_TUPLE_ELEM(                             \
          BOOST_PP_SUB(DIM, 1),                                               \
          (EQUATION_OF_STATE_FUNCTIONS_1D, EQUATION_OF_STATE_FUNCTIONS_2D)))) \
                                                                              \
  /* clang-tidy: do not use non-const references */                           \
  void pup(PUP::er& p) override; /* NOLINT */                                 \
                                                                              \
  explicit DERIVED(CkMigrateMessage* /*unused*/);

/// \cond
#define EQUATION_OF_STATE_FORWARD_ARGUMENTS(z, n, unused) \
  BOOST_PP_COMMA_IF(n) arg##n

#define EQUATION_OF_STATE_ARGUMENTS_EXPAND_NAMED(z, n, type) \
  BOOST_PP_COMMA_IF(n) const Scalar<type>& arg##n

#define EQUATION_OF_STATE_MEMBER_DEFINITIONS_HELPER(                        \
    TEMPLATE, DERIVED, DATA_TYPE, DIM, FUNCTION_NAME)                       \
  TEMPLATE                                                                  \
  Scalar<DATA_TYPE> DERIVED::FUNCTION_NAME(BOOST_PP_REPEAT(                 \
      DIM, EQUATION_OF_STATE_ARGUMENTS_EXPAND_NAMED, DATA_TYPE)) const {    \
    return FUNCTION_NAME##_impl(                                            \
        BOOST_PP_REPEAT(DIM, EQUATION_OF_STATE_FORWARD_ARGUMENTS, UNUSED)); \
  }

#define EQUATION_OF_STATE_MEMBER_DEFINITIONS_HELPER_2(r, ARGS, FUNCTION_NAME) \
  EQUATION_OF_STATE_MEMBER_DEFINITIONS_HELPER(                                \
      BOOST_PP_TUPLE_ELEM(0, ARGS), BOOST_PP_TUPLE_ELEM(1, ARGS),             \
      BOOST_PP_TUPLE_ELEM(2, ARGS), BOOST_PP_TUPLE_ELEM(3, ARGS),             \
      FUNCTION_NAME)
/// \endcond

#define EQUATION_OF_STATE_MEMBER_DEFINITIONS(TEMPLATE, DERIVED, DATA_TYPE, \
                                             DIM)                          \
  BOOST_PP_LIST_FOR_EACH(                                                  \
      EQUATION_OF_STATE_MEMBER_DEFINITIONS_HELPER_2,                       \
      (TEMPLATE, DERIVED, DATA_TYPE, DIM),                                 \
      BOOST_PP_TUPLE_TO_LIST(BOOST_PP_TUPLE_ELEM(                          \
          BOOST_PP_SUB(DIM, 1),                                            \
          (EQUATION_OF_STATE_FUNCTIONS_1D, EQUATION_OF_STATE_FUNCTIONS_2D))))

/// \cond
#define EQUATION_OF_STATE_FORWARD_DECLARE_MEMBER_IMPLS_HELPER(r, DIM,        \
                                                              FUNCTION_NAME) \
  template <class DataType>                                                  \
  Scalar<DataType> FUNCTION_NAME##_impl(BOOST_PP_REPEAT(                     \
      DIM, EQUATION_OF_STATE_ARGUMENTS_EXPAND, DataType)) const;
/// \endcond

#define EQUATION_OF_STATE_FORWARD_DECLARE_MEMBER_IMPLS(DIM)       \
  BOOST_PP_LIST_FOR_EACH(                                         \
      EQUATION_OF_STATE_FORWARD_DECLARE_MEMBER_IMPLS_HELPER, DIM, \
      BOOST_PP_TUPLE_TO_LIST(BOOST_PP_TUPLE_ELEM(                 \
          BOOST_PP_SUB(DIM, 1),                                   \
          (EQUATION_OF_STATE_FUNCTIONS_1D, EQUATION_OF_STATE_FUNCTIONS_2D))))

ext: hpp

max_stars_repo_name: Shabibti/spectre

max_issues_repo_name: Shabibti/spectre

max_forks_repo_name: Shabibti/spectre


text:
/**
 * Copyright (C) 2015 Dato, Inc.
 * All rights reserved.
 *
 * This software may be modified and distributed under the terms
 * of the BSD license. See the LICENSE file for details.
 */
#ifndef GRAPHLAB_UNITY_SFRAME_SARRAY_HPP
#define GRAPHLAB_UNITY_SFRAME_SARRAY_HPP
#include <set>
#include <iterator>
#include <type_traits>
#include <logger/logger.hpp>
#include <logger/assertions.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <sframe/output_iterator.hpp>
#include <serialization/iarchive.hpp>
#include <serialization/oarchive.hpp>
#include <parallel/pthread_tools.hpp>
#include <parallel/lambda_omp.hpp>
#include <sframe/swriter_base.hpp>
#include <sframe/sarray_file_format_v2.hpp>
#include <sframe/sarray_index_file.hpp>
#include <sframe/algorithm.hpp>
#include <sframe/sframe_config.hpp>
#include <flexible_type/flexible_type.hpp>
#include <fileio/fixed_size_cache_manager.hpp>
#include <fileio/file_ownership_handle.hpp>
#include <fileio/file_handle_pool.hpp>
#include <exceptions/error_types.hpp>
#include <sframe/sframe_constants.hpp>
#include <sframe/sarray_saving.hpp>

namespace graphlab {

template <typename T>
class sarray_reader;

namespace swriter_impl {
  // We define the iterator type here so as not to polute the outer namesapce
  //
template <typename T>
using output_iterator = graphlab::sframe_function_output_iterator<
              T,
              std::function<void(const T&)>,
              std::function<void(T&&)>,
              std::function<void(const sframe_rows&)> >;
} // namespace swriter_impl

/**
 * The SArray represents an immutable, on disk, sequence of objects T.
 *
 * The SArray is an immutable sequence of objects of type T, and is internally
 * represented as a collection of files. The sequence is cut up into a
 * collection of segments (not necessarily of equal length), where each segment
 * covers a disjoint subset of the sequence. Each segment can then be read
 * in parallel. SArray is referenced on disk by a single ".sidx" file, which
 * then has a list of file names, one file for each segment.
 *
 * The SArray is \b write-once, \b read-many. The SArray can be opened for
 * writing \b once, after which it is read-only.
 *
 * To open an existing sarray on disk for reading:
 * \code
 * sarray<int> array;
 * array.open_for_read("test.sidx");
 * \endcode
 * Note that the type of the array on disk is NOT checked.
 * (though, we probably should)
 *
 * To open an sarray for writing:
 * \code
 * sarray<int> array;
 * array.open_for_write(); // create an sarray backed with temporary files
 * //temporary files will be deleted when array goes out of scope
 * \endcode
 *
 * \code
 * sarray<int> array;
 * array.open_for_write("test.sidx"); // create an sarray backed by real files
 * \endcode
 *
 * When the array is opened for writing, it can written into using
 * \ref get_output_iterator() , to get an output iterator into each segment.
 * \code
 * // Gets the output iterator for the 3rd segment
 * auto iter = get_output_iterator(3);
 * // writes the value "5" into the segment
 * (*iter) = 5; ++iter;
 *
 * // when done,
 * close(); // closes the write.
 * \endcode
 *  The get_output_iterator() function can be called concurrently, but
 *  each individual output iterator is not concurrent.
 *  After close() is called, the sarray becomes a read-only array, and is
 *  equivalent to having called array.open_for_read(...)
 *
 * To read from the sarray, \ref get_reader() is used.
 * \code
 * auto reader = array.get_reader();
 * \endcode
 * Each reader provides read access to the SArray. Multiple readers can be
 * obtained, as each has its own distinct file handles which are closed as
 * the reader goes out of scope.
 * See the documentation for \ref sarray_reader for details.
 *
 * The sarray<flexible_type> has additional capabilities.
 *  - It has the functions set_type() and get_type() to set the run-time type
 *    of the stored values.
 *  - Writes to the sarray<flexible_type> type check against the type setted.
 *    The writes \b must match either the type set by set_type() or be
 *    UNDEFINED.
 *
 * \note The only guaranteed concurrent safe function is get_output_iterator.
 * All other mutating functions are not guaranteed to be safe.
 */
template <typename T>
class sarray : public swriter_base<swriter_impl::output_iterator<T> > {
 public:
  /// The reader type
  typedef sarray_reader<T> reader_type;

  /// The iterator type which \ref get_output_iterator returns
  typedef swriter_impl::output_iterator<T> iterator;

  /// The type contained in the sarray
  typedef T value_type;

  /**
   * default constructor; does nothing; use \ref open_for_read or
   * \ref open_for_write after construction to read/create an sarray.
   */
  sarray() = default;

  /// Move constructor
  sarray(sarray&& other):sarray() {
    (*this) = std::move(other);
  }

  /// Copy constructor
  sarray(const sarray& other) {
    (*this) = other;
  }

   /// Assignment operator
  sarray& operator=(const sarray& other) {
    if (other.inited && other.writing) {
      throw("Cannot copy an array which is writing");
    }
    if (writing) {
      throw("Cannot copy over an array which is writing");
    }
    index_info = other.index_info;
    index_file = other.index_file;
    files_managed = other.files_managed;
    inited = other.inited;
    writing = other.writing;
    return (*this);
  }

  /** Move assignment.
   * Moves other into this. Other will be cleared as if it is a newly
   * constructed sarray object.
   */
  sarray& operator=(sarray&& other) {
    index_info = std::move(other.index_info);
    index_file = std::move(other.index_file);
    writer = std::move(other.writer);
    files_managed = std::move(other.files_managed);
    inited = other.inited;
    writing = other.writing;

    other.index_info = index_file_information();
    other.index_file = "";
    other.writer = NULL;
    other.files_managed.clear();
    other.inited = false;
    other.writing = false;
    return *this;
  }

  ~sarray() {
    // the RAII deleter in files_managed will take care of the deletion
  }

  /**
   * Attempts to construct an sarray which reads from an sfrom the given file index file.
   * If the index cannot be opened, an exception is thrown.
   */
  explicit sarray(std::string sidx_or_directory) {
    open_for_read(sidx_or_directory);
  }

  /**
   * Create an sarray of given value and size.
   */
  sarray(const flexible_type& value, size_t size,
         size_t num_segments = SFRAME_DEFAULT_NUM_SEGMENTS,
         flex_type_enum type = flex_type_enum::UNDEFINED) {
    if (type == flex_type_enum::UNDEFINED) {
      type = value.get_type();
    }
    ASSERT_GT(num_segments, (size_t)0);
    open_for_write(num_segments);
    set_type(type);
    size_t size_per_segment = size / num_segments;
    parallel_for(0, num_segments, [&](size_t i) {
        auto out = get_output_iterator(i);
        size_t begin = i * size_per_segment;
        size_t end = (i + 1) * size_per_segment;
        if (i == num_segments -1) end = size;
        for (size_t iter = begin; iter < end; ++iter) {
          *out = value;
          ++out;
        }
    });
    close();
  }

  /**
   * Initializes the SArray with an index info.
   * If the SArray is already inited, this will throw an exception
   */
  void open_for_read(index_file_information info) {
    ASSERT_MSG(!inited, "Attempting to init an SArray "
               "which has already been inited");
    index_info = info;
    keep_array_file_ref();

    inited = true;
    writing = false;
    if (index_info.version == 0) {
      logprogress_stream << "Version 0 file format has been deprecated. "
                         << "Operations may not work as expected, or will be slow."
                         << "Please re-save the SFrame/SArray to update it to "
                         << "the latest version which has substantial "
                         << "performance optimizations\n";
    }
  }

  /**
   * Initializes the SArray with an index file.
   * If the SArray is already inited, this will throw an exception
   */
  void open_for_read(std::string sidx_file) {
    ASSERT_MSG(!inited, "Attempting to init an SArray "
               "which has already been inited");
    index_file = sidx_file;

    index_info = read_index_file(index_file);
    keep_array_file_ref();

    inited = true;
    writing = false;
    if (index_info.version == 0) {
      logprogress_stream << "Version 0 file format has been deprecated. "
                         << "Operations may not work as expected, or will be slow."
                         << "Please re-save the SFrame/SArray to update it to "
                         << "the latest version which has substantial "
                         << "performance optimizations\n";

    }
  }

  /**
   * Opens the Array for writing with an arbitrary temporary file.
   * The array must not already been inited.
   *
   * \param num_segments The number of segments in the array
   */
  void open_for_write(size_t num_segments = SFRAME_DEFAULT_NUM_SEGMENTS) {
    ASSERT_MSG(!inited, "Attempting to init an SArray "
               "which has already been inited");
    std::string sidx_file = fileio::fixed_size_cache_manager::get_instance().get_temp_cache_id(".sidx");
    index_file = sidx_file;
    writer = new sarray_group_format_writer_v2<T>;
    ASSERT_TRUE(writer != NULL);
    if (writer) writer->open(sidx_file, num_segments, 1);
    inited = true;
    writing = true;
    index_info = writer->get_index_info().columns[0];
  }

  /**
   * Opens the Array for writing with a location on disk.
   * The array must not already been inited.
   *
   * \param sidx_file If not specified, an argitrary temporary
   *                  file will be created. Otherwise, all frame
   *                  files will be written to the same location
   *                  as the frame_sidx_file. Must end in
   *                  ".sidx"
   * \param num_segments The number of segments in the array
   */
  void open_for_write(std::string sidx_file,
                      size_t num_segments = SFRAME_DEFAULT_NUM_SEGMENTS) {
    ASSERT_MSG(!inited, "Attempting to init an SArray "
        "which has already been inited");
    index_file = sidx_file;
    writer = new sarray_group_format_writer_v2<T>;
    // writer->open will test if the sidx file ends with .sidx
    if (writer) writer->open(sidx_file, num_segments, 1);
    inited = true;
    writing = true;
    index_info = writer->get_index_info().columns[0];
  }

  /**
   * Returns true if the Array is opened for reading.
   * i.e. get_reader() will succeed
   */
  bool is_opened_for_read() const {
    return (inited && !writing);
  }

  /**
   * Returns true if the Array is opened for writing.
   * i.e. get_output_iterator() will succeed
   */
  bool is_opened_for_write() const {
    return (inited && writing);
  }

  /**
   * Return the location of the index file of the sarray
   */
  std::string get_index_file() const {
    ASSERT_TRUE(inited);
    return index_file;
  }

  /**
   * Return the underlying writer of the sarray
   */
  sarray_group_format_writer<T>* get_writer() {
    ASSERT_MSG(inited, "Invalid SArray");
    ASSERT_MSG(writing, "SArray not opened for writing");
    ASSERT_NE(writer, NULL);
    return writer;
  }

  /**
   * Reads the value of a key associated with the sarray.
   * Returns true on success, false on failure.
   */
  bool get_metadata(std::string key, std::string &val) const {
    bool ret;
    std::tie(ret, val) = get_metadata(key);
    return ret;
  }

  /**
   * Reads the value of a key associated with the sarray.
   * Returns a pair of (true, value) on success, and (false, empty_string)
   * on failure.
   */
  std::pair<bool, std::string> get_metadata(std::string key) const {
    ASSERT_MSG(inited, "Invalid SArray");
    if (index_info.metadata.count(key)) {
      return std::pair<bool, std::string>(true, index_info.metadata.at(key));
    } else {
      return std::pair<bool, std::string>(false, "");
    }
  }

  /**
   * Returns the number of elements in the SArray
   */
  size_t size() const {
    if(!inited)
      return 0;
    size_t ret = 0;
    for (size_t i = 0;i < index_info.segment_sizes.size(); ++i) {
      ret += index_info.segment_sizes[i];
    }
    return ret;
  }

  /**
   * Gets an sarray reader object using the segmentation produced by the
   * actual file segments on disk.
   */
  std::unique_ptr<reader_type> get_reader() const {
    ASSERT_MSG(inited, "Invalid SArray");
    ASSERT_MSG(!writing, "Cannot open an SArraying which is still writing.");
    std::unique_ptr<reader_type> sarray_reader(new reader_type());
    sarray_reader->init(*this);
    return sarray_reader;
  }

  /**
   * Gets an sarray reader object with num_segments number of logical segments.
   */
  std::unique_ptr<reader_type> get_reader(size_t num_segments) const {
    ASSERT_MSG(inited, "Invalid SArray");
    ASSERT_MSG(!writing, "Cannot open an SArraying which is still writing.");
    std::unique_ptr<reader_type> sarray_reader(new reader_type());
    sarray_reader->init(*this, num_segments);
    return sarray_reader;
  }

  /**
   * Gets an sarray reader object with a custom segment layout. segment_lengths
   * must sum up to the same length as the original array.
   */
  std::unique_ptr<reader_type> get_reader(const std::vector<size_t>& segment_lengths) const {
    ASSERT_MSG(inited, "Invalid SArray");
    ASSERT_MSG(!writing, "Cannot open an SArraying which is still writing.");
    std::unique_ptr<reader_type> sarray_reader(new reader_type());
    sarray_reader->init(*this, segment_lengths);
    return sarray_reader;
  }

  /**
   * Return the number of segments in the array.
   */
  size_t num_segments() const {
    ASSERT_MSG(inited, "Invalid SArray");
    return index_info.nsegments;
  }

  /**
   * Return the length of segment i in the array.
   */
  size_t segment_length(size_t i) const {
    ASSERT_MSG(inited, "Invalid SArray");
    return index_info.segment_sizes[i];
  }

  /**
   * Returns all the index information of the array.
   */
  const index_file_information get_index_info() const {
    return index_info;
  }

  /**
   * Appends another SArray of the same type with the current SArray,
   * returning a new sarray.
   * without destroying the other array. Both SArrays can be empty, but
   * cannot be opened for writing.
   */
  sarray append(const sarray& other) const {
    // both cannot be writing
    ASSERT_EQ(writing, false);
    ASSERT_EQ(other.writing, false);
    // if one is inited, return the other
    if (!other.inited) return *this;
    if (!inited) return other;

    // cannot combine across format version
    ASSERT_EQ(index_info.version, other.index_info.version);
    ASSERT_EQ(index_info.block_size, other.index_info.block_size);

    sarray ret;
    ret.inited = true;
    ret.index_info = index_info;
    ret.files_managed = files_managed;

    ret.index_info.nsegments += other.index_info.nsegments;
    std::copy(other.index_info.segment_sizes.begin(), other.index_info.segment_sizes.end(),
              std::inserter(ret.index_info.segment_sizes, ret.index_info.segment_sizes.end()));
    std::copy(other.index_info.segment_files.begin(), other.index_info.segment_files.end(),
              std::inserter(ret.index_info.segment_files, ret.index_info.segment_files.end()));
    std::copy(other.files_managed.begin(), other.files_managed.end(),
              std::inserter(ret.files_managed, ret.files_managed.end()));
    return ret;
  }
  /**
   * Return a new sarray that contains a copy of the data in the current array.
   */
  sarray* clone() const {
    sarray* ret = new sarray();
    ret->open_for_write(num_segments());
    ret->set_type(get_type());
    auto reader = get_reader();
    parallel_for(0, num_segments(), [&](size_t segment_id) {
        auto iter = reader->begin(segment_id);
        auto end = reader->end(segment_id);
        auto out = ret->get_output_iterator(segment_id);
        while (iter != end) {
          *out = *iter;
          ++out;
          ++iter;
        }
    });
    ret->close();
    return ret;
  }

  /**
   * Sarray serializer. iarc must be associated with a directory.
   * Saves into a prefix inside the directory.
   */
  void save(oarchive& oarc) const {
    std::string prefix = oarc.get_prefix();
    save(prefix + ".sidx");
  }

  /**
   * SArray deserializer. iarc must be associated with a directory.
   * Loads from the next prefix inside the directory.
   */
  void load(iarchive& iarc) {
    std::string prefix = iarc.get_prefix();
    open_for_read(prefix + ".sidx");
  }

/**************************************************************************/
/*                                                                        */
/*                           Writing Functions                            */
/*                                                                        */
/**************************************************************************/
// These functions are only valid when the array is opened for writing

  /**
   * Sets the number of segments in the output.
   * Array must be first opened for writing.
   * If any writes has occured prior to this, those writes will be lost.
   * Returns true on sucess, false on failure.
   */
  bool set_num_segments(size_t numseg) {
    ASSERT_MSG(inited, "Invalid SArray");
    ASSERT_MSG(writing, "SArray not opened for writing");
    if (numseg == 0) return false;
    if (numseg != writer->num_segments()) {
      delete writer;
      writer = new sarray_group_format_writer_v2<T>;
      writer->open(index_file, numseg, 1);
      // restore metadata
      writer->get_index_info().columns[0].metadata = index_info.metadata;
      index_info = writer->get_index_info().columns[0];
      return true;
    }
    return false;
  }

  /**
   * Return an output iterator which can be used to write data to the segment.
   * Array must be first opened for writing.
   * The iterator (\ref iterator) is of the output iterator type and
   * has value_type T.
   *
   * The iterator is invalid once the segment is closed (See \ref close).
   * Accessing the iterator after the writer is destroyed is undefined
   * behavior.
   *
   * \code
   * // example to write a little array to segment 1
   * // say sw is of type sarray<int>
   * auto iter = sw.get_output_iterator(1);
   * std::vector<int> vals{1,2,3}
   * auto(int i, vals) {
   *   *iter = i;
   *   ++iter;
   * }
   * \endcode
   *
   * Will throw an exception if the array is invalid (there is an error
   * opening/writing files) Also segmentid must be a valid segment ID. Will
   * throw an exception otherwise.
   *
   * When T is a flexible_type, the output iterator performs type checking.
   */
  iterator get_output_iterator(size_t segmentid);

  /**
   * Closes the array.
   * Array must be first opened for writing.
   * close() also implicitly closes all segments.
   * After the writer is closed, no segments can be written.
   * Only once the array is closed, the SArray becomes readable with the
   * get_reader() function.
   */
  void close() {
    writer->close();
    writer->write_index_file();
    index_info = writer->get_index_info().columns[0];
    delete writer;
    writing = false;

    keep_array_file_ref();
  }

  /**
   * Adds meta data to the array.
   * Array must be first opened for writing.
   */
  bool set_metadata(std::string key, std::string val) {
    ASSERT_MSG(inited, "Invalid SArray");
    ASSERT_MSG(writing, "SArray not opened for writing");
    ASSERT_NE(writer, NULL);
    writer->get_index_info().columns[0].metadata[key] = val;
    index_info = writer->get_index_info().columns[0];
    return true;
  }

/**************************************************************************/
/*                                                                        */
/*                SArray<flexible_type> specific functions                */
/*                                                                        */
/**************************************************************************/

  /**
   * Returns the type of the SArray (as set by
   * \ref swriter<flexible_type>::set_type). If the type of the SArray was
   * not set, this returns \ref flex_type_enum::UNDEFINED, in which case
   * each row can be of arbitrary type.
   *
   * This function should only be used for sarray<flexible_type> and
   * will fail fatally otherwise.
   */
  flex_type_enum get_type() const {
    if (!std::is_same<T, flexible_type>::value) {
      ASSERT_MSG(false,
                  "Use of get_type() in SArray which "
                  "does not contain flexible_types");
    }
    ASSERT_MSG(inited, "Invalid SArray");
    if (!index_info.metadata.count("__type__")) {
      return flex_type_enum::UNDEFINED;
    }
    return flex_type_enum(std::stoi(index_info.metadata.at("__type__")));
  }

  /**
   * Sets the internal type of the flexible_type when written.
   * All writes will cast to this type.
   *
   * This function should only be used for sarray<flexible_type> and
   * will fail fatally otherwise.
   */
  void set_type(flex_type_enum type) {
    if (!std::is_same<T, flexible_type>::value) {
      ASSERT_MSG(false,
                  "Use of set_type() in SArray which "
                  "does not contain flexible_types");
    }
    ASSERT_MSG(inited, "Invalid SArray");
    ASSERT_MSG(writing, "SArray not opened for writing");
    set_metadata("__type__",
                 std::to_string(static_cast<int>(type)));
  }

  /**
   * Set the writer index_info for a given segment.
   * This function can be called, when the actual segment writing is done by other
   * logics.
   */
  void set_segment(size_t segmentid, const std::string& segment_file, size_t segment_size) {
    ASSERT_MSG(inited, "Invalid SArray");
    ASSERT_MSG(writing, "SArray not opened for writing");
    auto& index_info = writer->get_index_info().columns[0];
    index_info.segment_files[segmentid] = segment_file;
    index_info.segment_sizes[segmentid] = segment_size;
  }

  /**
   * Saves a copy of the current sarray into a different location.
   * Does not modify the current sarray.
   */
  void save(std::string index_file) const {
    ASSERT_TRUE(inited);
    ASSERT_FALSE(writing);
    std::string expected_ext(".sidx");
    if (!boost::algorithm::ends_with(index_file, expected_ext)) {
      log_and_throw("Index file must end with " + expected_ext);
    }
    sarray_save_blockwise(*this, index_file);
  }

  void delete_files_on_destruction() {
    for(auto &file : files_managed) {
      logstream(LOG_INFO) << "Will delete data file: " << file->m_file << std::endl;
      file->delete_on_destruction();
    }
  }

 private:
  // put all the files into the files_managed to manage their lifespan.
  // For non temp file, we also use a global handle pool to manage the lifespan
  // so that in normal case they are not going to be removed when the file is
  // in use
  void keep_array_file_ref() {
    std::vector<std::string> managed_files;
    for (const auto& file: index_info.segment_files) {
      managed_files.push_back(parse_v2_segment_filename(file).first);
    }
    if (!index_info.index_file.empty()) {
      managed_files.push_back(parse_v2_segment_filename(index_info.index_file).first);
    }
    if (!index_file.empty()) {
      managed_files.push_back(parse_v2_segment_filename(index_file).first);
    }
    for(auto& file : managed_files) {
      std::shared_ptr<fileio::file_ownership_handle> file_handle;
      file_handle = fileio::file_handle_pool::get_instance().register_file(file);

      files_managed.push_back(file_handle);
    }
  }

  // read when the sarray is opened for reading
  // synchronized against the writer class when used for writing
  index_file_information index_info;
  std::string index_file;
  sarray_group_format_writer<T>* writer = NULL;
  mutex lock;
  // this flag only matters on construction
  // it tells me whether I should insert the created segment files into the
  // "files_managed" RAII structure
  bool inited = false;
  bool writing = false;
  std::vector<std::shared_ptr<fileio::file_ownership_handle>> files_managed;

  friend class sarray_reader<T>;
};

/*
 * When T is a regular type, the output iterator just writes directly
 */
template <typename T>
typename sarray<T>::iterator inline sarray<T>::get_output_iterator(size_t segmentid) {
  ASSERT_MSG(inited, "Invalid SArray");
  ASSERT_MSG(writing, "SArray not opened for writing");
  ASSERT_NE(writer, NULL);
  ASSERT_LT(segmentid, num_segments());
  // return an output iterator which when written to,
  // will write to the segment
  return iterator(
      [=](const T& val)->void {
        writer->write_segment(0, segmentid, val);
      },
      [=](T&& val)->void {
        writer->write_segment(0, segmentid, std::forward<T>(val));
      },
      [=](const sframe_rows&)->void {
        ASSERT_TRUE(false);
      } );
}

/*
 * When T is a flexible type, the output iterator performs type checking
 */
template <>
sarray<flexible_type>::iterator
inline sarray<flexible_type>::get_output_iterator(size_t segmentid) {
  ASSERT_NE(writer, NULL);
  ASSERT_LT(segmentid, num_segments());
  flex_type_enum stored_type = get_type();
  // return an output iterator which when written to,
  // will write to the segment
  return iterator(
      [=](const value_type& val)->void {
        if (val.get_type() == stored_type ||
            val.get_type() == flex_type_enum::UNDEFINED ||
            stored_type == flex_type_enum::UNDEFINED) {
              writer->write_segment(0, segmentid, val);
            } else if (flex_type_is_convertible(val.get_type(), stored_type)) {
              flexible_type res(stored_type);
              res.soft_assign(val);
              writer->write_segment(0, segmentid, res);
            } else {
              std::string message = "Cannot convert " + std::string(val) + " to " + flex_type_enum_to_name(stored_type);
              logstream(LOG_ERROR) <<  message << std::endl;
              throw(bad_cast(message));
            }
      },
      [=](value_type&& val)->void {
        if (val.get_type() == stored_type ||
            val.get_type() == flex_type_enum::UNDEFINED ||
            stored_type == flex_type_enum::UNDEFINED) {
              writer->write_segment(0, segmentid, std::forward<flexible_type>(val));
            } else if (flex_type_is_convertible(val.get_type(), stored_type)) {
              flexible_type res(stored_type);
              res.soft_assign(val);
              writer->write_segment(0, segmentid, std::move(res));
            } else {
              std::string message = "Cannot convert " + std::string(val) + " to " + flex_type_enum_to_name(stored_type);
              logstream(LOG_ERROR) <<  message << std::endl;
              throw(bad_cast(message));
            }
      },
      [=](const sframe_rows& sfr)->void {
        ASSERT_TRUE(sfr.num_columns() == 1);
        writer->write_segment(segmentid, sfr.type_check({stored_type}));
      });
}

} // namespace graphlab

#include <sframe/sarray_reader.hpp>

////////////////////////////////////////////////////////////////////////////////
// Implement serialization for
// std::shared_ptr<sarray<flexible_type> >

BEGIN_OUT_OF_PLACE_SAVE(arc, std::shared_ptr<graphlab::sarray<graphlab::flexible_type> >, m) {
  if(m == nullptr) {
    arc << false;
  } else {
    arc << true;
    arc << (*m);
  }
} END_OUT_OF_PLACE_SAVE()

BEGIN_OUT_OF_PLACE_LOAD(arc, std::shared_ptr<graphlab::sarray<graphlab::flexible_type> >, m) {
  bool is_not_nullptr;
  arc >> is_not_nullptr;
  if(is_not_nullptr) {
    m.reset(new graphlab::sarray<graphlab::flexible_type>);
    arc >> (*m);
  } else {
    m = std::shared_ptr<graphlab::sarray<graphlab::flexible_type> >(nullptr);
  }
} END_OUT_OF_PLACE_LOAD()

#endif

max_stars_repo_name: parquette/ParFrame

max_issues_repo_name: parquette/ParFrame

max_forks_repo_name: parquette/ParFrame


text:
#ifndef BOOST_THREAD_PTHREAD_RECURSIVE_MUTEX_HPP
#define BOOST_THREAD_PTHREAD_RECURSIVE_MUTEX_HPP
// (C) Copyright 2007-8 Anthony Williams
// (C) Copyright 2011-2012 Vicente J. Botet Escriba
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <pthread.h>

#include <boost/thread/exceptions.hpp>
#include <boost/throw_exception.hpp>
#if defined BOOST_THREAD_PROVIDES_NESTED_LOCKS
#include <boost/thread/lock_types.hpp>
#endif
#include <boost/assert.hpp>
#include <boost/thread/thread_time.hpp>
#ifndef _WIN32
#include <unistd.h>
#endif
#include <errno.h>

#include <boost/date_time/posix_time/conversion.hpp>
#include <boost/thread/detail/platform_time.hpp>
#include <boost/thread/pthread/pthread_helpers.hpp>
#include <boost/thread/pthread/pthread_mutex_scoped_lock.hpp>
#ifdef BOOST_THREAD_USES_CHRONO
#include <boost/chrono/ceil.hpp>
#include <boost/chrono/system_clocks.hpp>
#endif
#include <boost/thread/detail/delete.hpp>

#if defined BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE || defined __ANDROID__
#define BOOST_THREAD_HAS_PTHREAD_MUTEXATTR_SETTYPE
#endif

#if defined BOOST_THREAD_HAS_PTHREAD_MUTEXATTR_SETTYPE &&                      \
    defined BOOST_THREAD_USES_PTHREAD_TIMEDLOCK
#define BOOST_USE_PTHREAD_RECURSIVE_TIMEDLOCK
#endif

#include <boost/config/abi_prefix.hpp>

namespace boost
{
class recursive_mutex
{
  private:
    pthread_mutex_t m;
#ifndef BOOST_THREAD_HAS_PTHREAD_MUTEXATTR_SETTYPE
    pthread_cond_t cond;
    bool is_locked;
    pthread_t owner;
    unsigned count;
#endif
  public:
    BOOST_THREAD_NO_COPYABLE(recursive_mutex)
    recursive_mutex()
    {
#ifdef BOOST_THREAD_HAS_PTHREAD_MUTEXATTR_SETTYPE
        pthread_mutexattr_t attr;

        int const init_attr_res = pthread_mutexattr_init(&attr);
        if (init_attr_res)
        {
            boost::throw_exception(thread_resource_error(
                init_attr_res, "boost:: recursive_mutex constructor failed in "
                               "pthread_mutexattr_init"));
        }
        int const set_attr_res =
            pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        if (set_attr_res)
        {
            BOOST_VERIFY(!pthread_mutexattr_destroy(&attr));
            boost::throw_exception(thread_resource_error(
                set_attr_res, "boost:: recursive_mutex constructor failed in "
                              "pthread_mutexattr_settype"));
        }

        int const res = pthread_mutex_init(&m, &attr);
        if (res)
        {
            BOOST_VERIFY(!pthread_mutexattr_destroy(&attr));
            boost::throw_exception(thread_resource_error(
                res, "boost:: recursive_mutex constructor failed in "
                     "pthread_mutex_init"));
        }
        BOOST_VERIFY(!pthread_mutexattr_destroy(&attr));
#else
        int const res = pthread_mutex_init(&m, NULL);
        if (res)
        {
            boost::throw_exception(thread_resource_error(
                res, "boost:: recursive_mutex constructor failed in "
                     "pthread_mutex_init"));
        }
        int const res2 = pthread::cond_init(cond);
        if (res2)
        {
            BOOST_VERIFY(!pthread_mutex_destroy(&m));
            boost::throw_exception(thread_resource_error(
                res2, "boost:: recursive_mutex constructor failed in "
                      "pthread::cond_init"));
        }
        is_locked = false;
        count = 0;
#endif
    }
    ~recursive_mutex()
    {
        BOOST_VERIFY(!pthread_mutex_destroy(&m));
#ifndef BOOST_THREAD_HAS_PTHREAD_MUTEXATTR_SETTYPE
        BOOST_VERIFY(!pthread_cond_destroy(&cond));
#endif
    }

#ifdef BOOST_THREAD_HAS_PTHREAD_MUTEXATTR_SETTYPE
    void lock()
    {
        BOOST_VERIFY(!posix::pthread_mutex_lock(&m));
    }

    void unlock()
    {
        BOOST_VERIFY(!posix::pthread_mutex_unlock(&m));
    }

    bool try_lock() BOOST_NOEXCEPT
    {
        int const res = posix::pthread_mutex_trylock(&m);
        BOOST_ASSERT(!res || res == EBUSY);
        return !res;
    }
#define BOOST_THREAD_DEFINES_RECURSIVE_MUTEX_NATIVE_HANDLE
    typedef pthread_mutex_t* native_handle_type;
    native_handle_type native_handle()
    {
        return &m;
    }

#else
    void lock()
    {
        boost::pthread::pthread_mutex_scoped_lock const local_lock(&m);
        if (is_locked && pthread_equal(owner, pthread_self()))
        {
            ++count;
            return;
        }

        while (is_locked)
        {
            BOOST_VERIFY(!pthread_cond_wait(&cond, &m));
        }
        is_locked = true;
        ++count;
        owner = pthread_self();
    }

    void unlock()
    {
        boost::pthread::pthread_mutex_scoped_lock const local_lock(&m);
        if (!--count)
        {
            is_locked = false;
        }
        BOOST_VERIFY(!pthread_cond_signal(&cond));
    }

    bool try_lock()
    {
        boost::pthread::pthread_mutex_scoped_lock const local_lock(&m);
        if (is_locked && !pthread_equal(owner, pthread_self()))
        {
            return false;
        }
        is_locked = true;
        ++count;
        owner = pthread_self();
        return true;
    }

#endif

#if defined BOOST_THREAD_PROVIDES_NESTED_LOCKS
    typedef unique_lock<recursive_mutex> scoped_lock;
    typedef detail::try_lock_wrapper<recursive_mutex> scoped_try_lock;
#endif
};

typedef recursive_mutex recursive_try_mutex;

class recursive_timed_mutex
{
  private:
    pthread_mutex_t m;
#ifndef BOOST_USE_PTHREAD_RECURSIVE_TIMEDLOCK
    pthread_cond_t cond;
    bool is_locked;
    pthread_t owner;
    unsigned count;
#endif
  public:
    BOOST_THREAD_NO_COPYABLE(recursive_timed_mutex)
    recursive_timed_mutex()
    {
#ifdef BOOST_USE_PTHREAD_RECURSIVE_TIMEDLOCK
        pthread_mutexattr_t attr;

        int const init_attr_res = pthread_mutexattr_init(&attr);
        if (init_attr_res)
        {
            boost::throw_exception(thread_resource_error(
                init_attr_res, "boost:: recursive_timed_mutex constructor "
                               "failed in pthread_mutexattr_init"));
        }
        int const set_attr_res =
            pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        if (set_attr_res)
        {
            boost::throw_exception(thread_resource_error(
                set_attr_res, "boost:: recursive_timed_mutex constructor "
                              "failed in pthread_mutexattr_settype"));
        }

        int const res = pthread_mutex_init(&m, &attr);
        if (res)
        {
            BOOST_VERIFY(!pthread_mutexattr_destroy(&attr));
            boost::throw_exception(thread_resource_error(
                res, "boost:: recursive_timed_mutex constructor failed in "
                     "pthread_mutex_init"));
        }
        BOOST_VERIFY(!pthread_mutexattr_destroy(&attr));
#else
        int const res = pthread_mutex_init(&m, NULL);
        if (res)
        {
            boost::throw_exception(thread_resource_error(
                res, "boost:: recursive_timed_mutex constructor failed in "
                     "pthread_mutex_init"));
        }
        int const res2 = pthread::cond_init(cond);
        if (res2)
        {
            BOOST_VERIFY(!pthread_mutex_destroy(&m));
            boost::throw_exception(thread_resource_error(
                res2, "boost:: recursive_timed_mutex constructor failed in "
                      "pthread::cond_init"));
        }
        is_locked = false;
        count = 0;
#endif
    }
    ~recursive_timed_mutex()
    {
        BOOST_VERIFY(!pthread_mutex_destroy(&m));
#ifndef BOOST_USE_PTHREAD_RECURSIVE_TIMEDLOCK
        BOOST_VERIFY(!pthread_cond_destroy(&cond));
#endif
    }

#if defined BOOST_THREAD_USES_DATETIME
    template <typename TimeDuration>
    bool timed_lock(TimeDuration const& relative_time)
    {
        if (relative_time.is_pos_infinity())
        {
            lock();
            return true;
        }
        if (relative_time.is_special())
        {
            return true;
        }
        detail::platform_duration d(relative_time);
#if defined(BOOST_THREAD_HAS_MONO_CLOCK) &&                                    \
    !defined(BOOST_THREAD_INTERNAL_CLOCK_IS_MONO)
        const detail::mono_platform_timepoint ts(
            detail::mono_platform_clock::now() + d);
        d = (std::min)(d, detail::platform_milliseconds(
                              BOOST_THREAD_POLL_INTERVAL_MILLISECONDS));
        while (!do_try_lock_until(detail::internal_platform_clock::now() + d))
        {
            d = ts - detail::mono_platform_clock::now();
            if (d <= detail::platform_duration::zero())
                return false; // timeout occurred
            d = (std::min)(d, detail::platform_milliseconds(
                                  BOOST_THREAD_POLL_INTERVAL_MILLISECONDS));
        }
        return true;
#else
        return do_try_lock_until(detail::internal_platform_clock::now() + d);
#endif
    }
#endif

#ifdef BOOST_USE_PTHREAD_RECURSIVE_TIMEDLOCK
    void lock()
    {
        BOOST_VERIFY(!posix::pthread_mutex_lock(&m));
    }

    void unlock()
    {
        BOOST_VERIFY(!posix::pthread_mutex_unlock(&m));
    }

    bool try_lock()
    {
        int const res = posix::pthread_mutex_trylock(&m);
        BOOST_ASSERT(!res || res == EBUSY);
        return !res;
    }

  private:
    bool do_try_lock_until(detail::internal_platform_timepoint const& timeout)
    {
        int const res = pthread_mutex_timedlock(&m, &timeout.getTs());
        BOOST_ASSERT(!res || res == ETIMEDOUT);
        return !res;
    }

  public:
#else
    void lock()
    {
        boost::pthread::pthread_mutex_scoped_lock const local_lock(&m);
        if (is_locked && pthread_equal(owner, pthread_self()))
        {
            ++count;
            return;
        }

        while (is_locked)
        {
            BOOST_VERIFY(!pthread_cond_wait(&cond, &m));
        }
        is_locked = true;
        ++count;
        owner = pthread_self();
    }

    void unlock()
    {
        boost::pthread::pthread_mutex_scoped_lock const local_lock(&m);
        if (!--count)
        {
            is_locked = false;
        }
        BOOST_VERIFY(!pthread_cond_signal(&cond));
    }

    bool try_lock() BOOST_NOEXCEPT
    {
        boost::pthread::pthread_mutex_scoped_lock const local_lock(&m);
        if (is_locked && !pthread_equal(owner, pthread_self()))
        {
            return false;
        }
        is_locked = true;
        ++count;
        owner = pthread_self();
        return true;
    }

  private:
    bool do_try_lock_until(detail::internal_platform_timepoint const& timeout)
    {
        boost::pthread::pthread_mutex_scoped_lock const local_lock(&m);
        if (is_locked && pthread_equal(owner, pthread_self()))
        {
            ++count;
            return true;
        }
        while (is_locked)
        {
            int const cond_res =
                pthread_cond_timedwait(&cond, &m, &timeout.getTs());
            if (cond_res == ETIMEDOUT)
            {
                break;
            }
            BOOST_ASSERT(!cond_res);
        }
        if (is_locked)
        {
            return false;
        }
        is_locked = true;
        ++count;
        owner = pthread_self();
        return true;
    }

  public:

#endif

#if defined BOOST_THREAD_USES_DATETIME
    bool timed_lock(system_time const& abs_time)
    {
        const detail::real_platform_timepoint ts(abs_time);
#if defined BOOST_THREAD_INTERNAL_CLOCK_IS_MONO
        detail::platform_duration d(ts - detail::real_platform_clock::now());
        d = (std::min)(d, detail::platform_milliseconds(
                              BOOST_THREAD_POLL_INTERVAL_MILLISECONDS));
        while (!do_try_lock_until(detail::internal_platform_clock::now() + d))
        {
            d = ts - detail::real_platform_clock::now();
            if (d <= detail::platform_duration::zero())
                return false; // timeout occurred
            d = (std::min)(d, detail::platform_milliseconds(
                                  BOOST_THREAD_POLL_INTERVAL_MILLISECONDS));
        }
        return true;
#else
        return do_try_lock_until(ts);
#endif
    }
#endif
#ifdef BOOST_THREAD_USES_CHRONO
    template <class Rep, class Period>
    bool try_lock_for(const chrono::duration<Rep, Period>& rel_time)
    {
        return try_lock_until(chrono::steady_clock::now() + rel_time);
    }
    template <class Clock, class Duration>
    bool try_lock_until(const chrono::time_point<Clock, Duration>& t)
    {
        typedef typename common_type<Duration, typename Clock::duration>::type
            common_duration;
        common_duration d(t - Clock::now());
        d = (std::min)(d, common_duration(chrono::milliseconds(
                              BOOST_THREAD_POLL_INTERVAL_MILLISECONDS)));
        while (!try_lock_until(detail::internal_chrono_clock::now() + d))
        {
            d = t - Clock::now();
            if (d <= common_duration::zero())
                return false; // timeout occurred
            d = (std::min)(d, common_duration(chrono::milliseconds(
                                  BOOST_THREAD_POLL_INTERVAL_MILLISECONDS)));
        }
        return true;
    }
    template <class Duration>
    bool try_lock_until(
        const chrono::time_point<detail::internal_chrono_clock, Duration>& t)
    {
        detail::internal_platform_timepoint ts(t);
        return do_try_lock_until(ts);
    }
#endif

#define BOOST_THREAD_DEFINES_RECURSIVE_TIMED_MUTEX_NATIVE_HANDLE
    typedef pthread_mutex_t* native_handle_type;
    native_handle_type native_handle()
    {
        return &m;
    }

#if defined BOOST_THREAD_PROVIDES_NESTED_LOCKS
    typedef unique_lock<recursive_timed_mutex> scoped_timed_lock;
    typedef detail::try_lock_wrapper<recursive_timed_mutex> scoped_try_lock;
    typedef scoped_timed_lock scoped_lock;
#endif
};

} // namespace boost

#include <boost/config/abi_suffix.hpp>

#endif

max_stars_repo_name: sotaoverride/backup

max_issues_repo_name: sotaoverride/backup

max_forks_repo_name: sotaoverride/backup


text:
// Copyright 2020 The Defold Foundation
// Licensed under the Defold License version 1.0 (the "License"); you may not use
// this file except in compliance with the License.
//
// You may obtain a copy of the License, together with FAQs at
// https://www.defold.com/license
//
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

#define JC_TEST_IMPLEMENTATION
#include <jc_test/jc_test.h>

#include <stdint.h>

#include <dlib/hash.h>
#include <dlib/log.h>

#include "../gameobject.h"
#include "../gameobject_private.h"

using namespace Vectormath::Aos;

class BonesTest : public jc_test_base_class
{
protected:
    virtual void SetUp()
    {
        m_UpdateContext.m_DT = 1.0f / 60.0f;

        dmResource::NewFactoryParams params;
        params.m_MaxResources = 16;
        params.m_Flags = RESOURCE_FACTORY_FLAGS_EMPTY;
        m_Factory = dmResource::NewFactory(&params, "build/default/src/gameobject/test/bones");
        m_ScriptContext = dmScript::NewContext(0, m_Factory, true);
        dmScript::Initialize(m_ScriptContext);
        m_Register = dmGameObject::NewRegister();
        dmGameObject::Initialize(m_Register, m_ScriptContext);

        m_Contexts.SetCapacity(7,16);
        m_Contexts.Put(dmHashString64("goc"), m_Register);
        m_Contexts.Put(dmHashString64("collectionc"), m_Register);
        m_Contexts.Put(dmHashString64("scriptc"), m_ScriptContext);
        m_Contexts.Put(dmHashString64("luac"), &m_ModuleContext);
        dmResource::RegisterTypes(m_Factory, &m_Contexts);

        dmGameObject::ComponentTypeCreateCtx component_create_ctx = {};
        component_create_ctx.m_Script = m_ScriptContext;
        component_create_ctx.m_Register = m_Register;
        component_create_ctx.m_Factory = m_Factory;
        dmGameObject::CreateRegisteredComponentTypes(&component_create_ctx);
        dmGameObject::SortComponentTypes(m_Register);

        dmResource::Result e;
        e = dmResource::RegisterType(m_Factory, "a", this, 0, ACreate, 0, ADestroy, 0);
        ASSERT_EQ(dmResource::RESULT_OK, e);

        dmResource::ResourceType resource_type;
        dmGameObject::Result result;

        // A has component_user_data
        e = dmResource::GetTypeFromExtension(m_Factory, "a", &resource_type);
        ASSERT_EQ(dmResource::RESULT_OK, e);
        dmGameObject::ComponentType a_type;
        a_type.m_Name = "a";
        a_type.m_ResourceType = resource_type;
        a_type.m_Context = this;
        a_type.m_CreateFunction = AComponentCreate;
        a_type.m_DestroyFunction = AComponentDestroy;
        a_type.m_InstanceHasUserData = true;
        result = dmGameObject::RegisterComponentType(m_Register, a_type);
        dmGameObject::SetUpdateOrderPrio(m_Register, resource_type, 2);
        ASSERT_EQ(dmGameObject::RESULT_OK, result);
    }

    virtual void TearDown()
    {
        dmScript::Finalize(m_ScriptContext);
        dmScript::DeleteContext(m_ScriptContext);
        dmGameObject::DeleteRegister(m_Register);
        dmResource::DeleteFactory(m_Factory);
    }

    static dmResource::FResourceCreate    ACreate;
    static dmResource::FResourceDestroy   ADestroy;
    static dmGameObject::ComponentCreate  AComponentCreate;
    static dmGameObject::ComponentDestroy AComponentDestroy;

public:
    dmScript::HContext m_ScriptContext;
    dmGameObject::UpdateContext m_UpdateContext;
    dmGameObject::HRegister m_Register;
    dmGameObject::HCollection m_Collection;
    dmGameObject::ModuleContext m_ModuleContext;
    dmHashTable64<void*> m_Contexts;
    dmResource::HFactory m_Factory;
};

static dmResource::Result NullResourceCreate(const dmResource::ResourceCreateParams& params)
{
    params.m_Resource->m_Resource = (void*)1; // asserted for != 0 in dmResource
    return dmResource::RESULT_OK;
}

static dmResource::Result NullResourceDestroy(const dmResource::ResourceDestroyParams& params)
{
    return dmResource::RESULT_OK;
}

struct ComponentData {
    dmGameObject::HInstance m_Child;
};

static dmGameObject::CreateResult TestComponentCreate(const dmGameObject::ComponentCreateParams& params)
{
    BonesTest* test = (BonesTest*)params.m_Context;
    ComponentData* data = new ComponentData();

    data->m_Child = dmGameObject::New(test->m_Collection, 0x0);
    if (data->m_Child == 0x0 || data->m_Child->m_Index < params.m_Instance->m_Index) {
        return dmGameObject::CREATE_RESULT_UNKNOWN_ERROR;
    }
    dmGameObject::SetBone(data->m_Child, true);
    dmGameObject::SetParent(data->m_Child, params.m_Instance);

    *params.m_UserData = (uintptr_t)data;
    return dmGameObject::CREATE_RESULT_OK;
}

static dmGameObject::CreateResult TestComponentDestroy(const dmGameObject::ComponentDestroyParams& params)
{
    ComponentData* data = (ComponentData*)*params.m_UserData;
    dmGameObject::DeleteBones(params.m_Instance);
    delete data;
    return dmGameObject::CREATE_RESULT_OK;
}

dmResource::FResourceCreate BonesTest::ACreate              = NullResourceCreate;
dmResource::FResourceDestroy BonesTest::ADestroy            = NullResourceDestroy;
dmGameObject::ComponentCreate BonesTest::AComponentCreate   = TestComponentCreate;
dmGameObject::ComponentDestroy BonesTest::AComponentDestroy = TestComponentDestroy;

/**
 * Purpose is to test that delete bones indeed deletes a bone.
 */
TEST_F(BonesTest, DeleteBones)
{
    m_Collection = dmGameObject::NewCollection("collection", m_Factory, m_Register, 1024, 0x0);
    ASSERT_EQ(0, m_Collection->m_Collection->m_InstanceIndices.Size());

    // Create the game object, the component above will create a child bone to that game object, which in turn will get a lower index because of the gap above
    dmGameObject::HInstance test_inst = dmGameObject::New(m_Collection, "/test_bones.goc");
    ASSERT_NE((void*)0, test_inst);

    ASSERT_EQ(2, m_Collection->m_Collection->m_InstanceIndices.Size());

    dmGameObject::Delete(m_Collection, test_inst, false);
    dmGameObject::PostUpdate(m_Collection);

    ASSERT_EQ(0, m_Collection->m_Collection->m_InstanceIndices.Size());

    dmGameObject::DeleteCollection(m_Collection);
    dmGameObject::PostUpdate(m_Register);
}

/**
 * This test is modeled after the way the spine model component interacts with the game object system.
 */
TEST_F(BonesTest, ComponentCreatingInstances)
{
    m_Collection = dmGameObject::NewCollection("collection", m_Factory, m_Register, 1024, 0x0);

    // First create three game objects to create gaps in the instance array
    dmGameObject::HInstance tmp_inst[3];
    for (int i = 0; i < 3; ++i) {
        tmp_inst[i] = dmGameObject::New(m_Collection, 0x0);
        ASSERT_NE((void*)0, tmp_inst[i]);
    }
    // Delete the first two in reverse order; the next created will have index 1, the second created will have index 0
    dmGameObject::Delete(m_Collection, tmp_inst[1], false);
    dmGameObject::Delete(m_Collection, tmp_inst[0], false);

    // Create the game object, the component above will create a child bone to that game object, which in turn will get a lower index because of the gap above
    dmGameObject::HInstance test_inst = dmGameObject::New(m_Collection, "/test_bones.goc");
    ASSERT_NE((void*)0, test_inst);

    dmGameObject::DeleteCollection(m_Collection);
    dmGameObject::PostUpdate(m_Register);
}

int main(int argc, char **argv)
{
    jc_test_init(&argc, argv);

    int ret = jc_test_run_all();
    return ret;
}

ext: cpp

max_stars_repo_name: hakoptak/defold

max_issues_repo_name: hakoptak/defold

max_forks_repo_name: hakoptak/defold