/*
* This file is part of ArmarX.
*
* ArmarX is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* ArmarX is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package    ArmarX::
* @author     Philipp Schmidt
* @date       2015
* @license    http://www.gnu.org/licenses/gpl-2.0.txt
*             GNU General Public License

*/

#include "RobotIKGuiPlugin.h"
#include "RobotViewer.h"

/* ArmarX includes */
#include <ArmarXCore/core/system/ArmarXDataPath.h>
#include <ArmarXCore/core/ArmarXObjectScheduler.h>
#include <ArmarXCore/core/system/cmake/CMakePackageFinder.h>
#include <ArmarXCore/core/application/Application.h>

/* Virtual Robot includes */
#include <VirtualRobot/XML/RobotIO.h>
#include <VirtualRobot/Visualization/CoinVisualization/CoinVisualizationFactory.h>

/* Coin includes */
#include <Inventor/actions/SoGetMatrixAction.h>
#include <Inventor/actions/SoSearchAction.h>
#include <Inventor/SbViewportRegion.h>
#include <ArmarXCore/util/json/JSONObject.h>
#include <QClipboard>

#define ROBOT_UPDATE_TIMER_MS 33
#define TEXTFIELD_UPDATE_TIMER_MS 200
#define AUTO_FOLLOW_UPDATE 333

armarx::RobotIKGuiPlugin::RobotIKGuiPlugin()
{
    addWidget<RobotIKWidgetController>();
}

armarx::RobotIKWidgetController::RobotIKWidgetController() : manipulatorMoved(false), startUpCameraPositioningFlag(true)
{


    //Component names
    robotStateComponentName = "";
    robotIKComponentName = "";
    kinematicUnitComponentName = "";

    visualization = NULL;
    robotUpdateSensor = NULL;
    textFieldUpdateSensor = NULL;
}

void armarx::RobotIKWidgetController::onInitComponent()
{
    QMetaObject::invokeMethod(this, "initWidget");
    //Prepare proxies
    usingProxy(robotStateComponentName);
    usingProxy(robotIKComponentName);
    usingProxy(kinematicUnitComponentName);
}

void armarx::RobotIKWidgetController::onConnectComponent()
{
    //Get all proxies
    robotStateComponentPrx = getProxy<RobotStateComponentInterfacePrx>(robotStateComponentName);
    kinematicUnitInterfacePrx = getProxy<KinematicUnitInterfacePrx>(kinematicUnitComponentName);
    robotIKPrx = getProxy<RobotIKInterfacePrx>(robotIKComponentName);

    //Load robot
    robot = loadRobot(getIncludePaths());

    if (!robot)
    {
        getObjectScheduler()->terminate();

        if (getWidget()->parentWidget())
        {
            getWidget()->parentWidget()->close();
        }

        ARMARX_ERROR << "RobotIKPlugin: Unable to load robot file! Terminating." << std::endl;
        return;
    }

    //Add in our visualization for manipulators
    visualization = new ManipulatorVisualization;
    this->ui.robotViewer->getRobotViewer()->getRootNode()->addChild((SoNode*)visualization);

    //Get visualization for our robot and add it to scene graph
    CoinVisualizationPtr robotViewerVisualization = robot->getVisualization<VirtualRobot::CoinVisualization>();
    this->ui.robotViewer->getRobotViewer()->getRootNode()->addChild(robotViewerVisualization->getCoinVisualization());

    //Make a timer update robo pose every time tick
    SoSensorManager* sensor_mgr = SoDB::getSensorManager();
    robotUpdateSensor = new SoTimerSensor(robotUpdateTimerCB, this);
    robotUpdateSensor->setInterval(SbTime(ROBOT_UPDATE_TIMER_MS / 1000.0f));
    sensor_mgr->insertTimerSensor(robotUpdateSensor);

    //And a timer to update the labels
    textFieldUpdateSensor = new SoTimerSensor(textFieldUpdateTimerCB, this);
    textFieldUpdateSensor->setInterval(SbTime(TEXTFIELD_UPDATE_TIMER_MS / 1000.0f));
    sensor_mgr->insertTimerSensor(textFieldUpdateSensor);

    //Get all kinematic chain descriptions and add them to the combo box
    auto robotNodeSets = robot->getRobotNodeSets();

    for (VirtualRobot::RobotNodeSetPtr s : robotNodeSets)
    {
        this->ui.comboBox->addItem(QString::fromStdString(s->getName()));
    }

    //Make sure comboBox is enabled so you can select a kinematic chain
    this->ui.comboBox->setEnabled(true);

    //Get all caertesian selections and add them
    this->ui.cartesianselection->addItem(QString::fromStdString("Orientation and Position"));
    this->ui.cartesianselection->addItem(QString::fromStdString("Position"));
    this->ui.cartesianselection->addItem(QString::fromStdString("Orientation"));
    this->ui.cartesianselection->addItem(QString::fromStdString("X position"));
    this->ui.cartesianselection->addItem(QString::fromStdString("Y position"));
    this->ui.cartesianselection->addItem(QString::fromStdString("Z position"));

    //Make sure it is enabled
    this->ui.cartesianselection->setEnabled(true);
    this->ui.cartesianselection->setCurrentIndex(0);

    connectSlots();
    enableMainWidgetAsync(true);
}

void armarx::RobotIKWidgetController::onDisconnectComponent()
{
    //Stop timers
    SoSensorManager* sensor_mgr = SoDB::getSensorManager();
    sensor_mgr->removeTimerSensor(textFieldUpdateSensor);
    sensor_mgr->removeTimerSensor(robotUpdateSensor);

    //Remove all options in comboBox
    this->ui.comboBox->clear();

    //Disable ui controls
    this->ui.comboBox->setEnabled(false);
    this->ui.moveTCP->setEnabled(false);
    this->ui.reachableLabel->setEnabled(false);
    this->ui.reachableLabel->setText(QString::fromStdString("No kinematic chain selected"));
    this->ui.reachableLabel->setStyleSheet("QLabel { color : red; }");
    this->ui.errorValue->setEnabled(false);
    this->ui.errorValue->setText(QString::fromStdString("Calculated error: 0"));
    this->ui.errorValue->setEnabled(false);
    this->ui.pushButton->setEnabled(false);
    this->ui.checkBox->setChecked(false);
    this->ui.checkBox->setEnabled(false);
    this->ui.currentPose->setEnabled(false);
    this->ui.currentPoseMatrix->setEnabled(false);
    this->ui.desiredPose->setEnabled(false);
    this->ui.desiredPoseMatrix->setEnabled(false);
    this->ui.resetManip->setEnabled(false);

    //Remove all visualization
    this->ui.robotViewer->getRobotViewer()->getRootNode()->removeAllChildren();

    visualization = NULL;
    robotUpdateSensor = NULL;
    textFieldUpdateSensor = NULL;
}

void armarx::RobotIKWidgetController::onExitComponent()
{
    //    enableMainWidgetAsync(false);
}

QPointer<QDialog> armarx::RobotIKWidgetController::getConfigDialog(QWidget* parent)
{
    if (!dialog)
    {
        dialog = new RobotIKConfigDialog(parent);
    }

    return qobject_cast<RobotIKConfigDialog*>(dialog);
}

void armarx::RobotIKWidgetController::loadSettings(QSettings* settings)
{

}

void armarx::RobotIKWidgetController::saveSettings(QSettings* settings)
{

}

void armarx::RobotIKWidgetController::configured()
{
    robotStateComponentName = dialog->robotStateComponentProxyFinder->getSelectedProxyName().trimmed().toStdString();
    robotIKComponentName = dialog->robotIKComponentProxyFinder->getSelectedProxyName().trimmed().toStdString();
    kinematicUnitComponentName = dialog->kinematicUnitComponentProxyFinder->getSelectedProxyName().trimmed().toStdString();
}

void armarx::RobotIKWidgetController::initWidget()
{
    //Initialize Gui
    ui.setupUi(getWidget());
    getWidget()->setEnabled(false);
    //Alignment for ui
    this->ui.gridLayout->setAlignment(Qt::AlignTop);

    //Label color can not be set in designer, so we do it here
    this->ui.reachableLabel->setStyleSheet("QLabel { color : red; }");
}

void armarx::RobotIKWidgetController::solveIK()
{
    auto targetJointAngles = getIKSolution();

    if (targetJointAngles.isReachable)
    {
        //Switch all control modes to ePositionControl
        std::vector<VirtualRobot::RobotNodePtr> rn = robot->getRobotNodeSet(this->ui.comboBox->currentText().toStdString())->getAllRobotNodes();
        NameControlModeMap jointModes;
        NameValueMap jointAngles;

        for (unsigned int i = 0; i < rn.size(); i++)
        {
            jointModes[rn[i]->getName()] = ePositionControl;
            jointAngles[rn[i]->getName()] = 0.0f;
        }

        kinematicUnitInterfacePrx->switchControlMode(jointModes);
        kinematicUnitInterfacePrx->setJointAngles(targetJointAngles.jointAngles);
    }
}

void armarx::RobotIKWidgetController::kinematicChainChanged(const QString& arg1)
{
    //An item has been selected, so we can allow the user to use the ui now
    //The manipulator will be set to the position of the current tcp, so this pose
    //has to be reachable!
    this->ui.moveTCP->setEnabled(true);
    this->ui.reachableLabel->setEnabled(true);
    this->ui.reachableLabel->setText(QString::fromStdString("Pose reachable!"));
    this->ui.reachableLabel->setStyleSheet("QLabel { color : green; }");
    this->ui.errorValue->setText(QString::fromStdString("Calculated error: 0"));
    this->ui.errorValue->setEnabled(true);
    this->ui.pushButton->setEnabled(true);
    this->ui.currentPose->setEnabled(true);
    this->ui.currentPoseMatrix->setEnabled(true);
    this->ui.desiredPose->setEnabled(true);
    this->ui.desiredPoseMatrix->setEnabled(true);
    this->ui.resetManip->setEnabled(true);
    this->ui.checkBox->setEnabled(true);


    //Add visualization
    visualization->setVisualization(robot, robot->getRobotNodeSet(arg1.toStdString()));
    //Add callbacks
    visualization->addManipFinishCallback(manipFinishCallback, this);
    visualization->addManipMovedCallback(manipMovedCallback, this);
}

void armarx::RobotIKWidgetController::caertesianSelectionChanged(const QString& arg1)
{
    //If there is a manip in the scene we pretend it just moved to update color etc.
    if (visualization->getIsVisualizing())
    {
        manipFinishCallback(this, NULL);
    }
}

void armarx::RobotIKWidgetController::resetManip()
{
    //Triggers reset of manipulator in kinematicChainChanged
    kinematicChainChanged(this->ui.comboBox->currentText());
}

void armarx::RobotIKWidgetController::connectSlots()
{
    connect(ui.resetManip, SIGNAL(clicked()), this, SLOT(resetManip()), Qt::QueuedConnection);
    connect(ui.pushButton, SIGNAL(clicked()), this, SLOT(solveIK()), Qt::QueuedConnection);
    connect(ui.comboBox, SIGNAL(currentIndexChanged(QString)), this, SLOT(kinematicChainChanged(QString)), Qt::UniqueConnection);
    connect(ui.cartesianselection, SIGNAL(currentIndexChanged(QString)), this, SLOT(caertesianSelectionChanged(QString)), Qt::UniqueConnection);
    connect(ui.checkBox, SIGNAL(toggled(bool)), this, SLOT(autoFollowChanged(bool)), Qt::UniqueConnection);
    connect(ui.btnCopyCurrentPoseToClipboard, SIGNAL(clicked()), this, SLOT(on_btnCopyCurrentPoseToClipboard_clicked()), Qt::UniqueConnection);
}

armarx::StringList armarx::RobotIKWidgetController::getIncludePaths()
{
    StringList includePaths;

    try
    {
        StringList packages = robotStateComponentPrx->getArmarXPackages();
        packages.push_back(Application::GetProjectName());

        for (const std::string& projectName : packages)
        {
            if (projectName.empty())
            {
                continue;
            }

            CMakePackageFinder project(projectName);
            StringList projectIncludePaths;
            auto pathsString = project.getDataDir();
            boost::split(projectIncludePaths,
                         pathsString,
                         boost::is_any_of(";,"),
                         boost::token_compress_on);
            includePaths.insert(includePaths.end(), projectIncludePaths.begin(), projectIncludePaths.end());
        }
    }
    catch (...)
    {
        ARMARX_ERROR << "Unable to retrieve robot filename." << std::endl;
    }

    return includePaths;
}

VirtualRobot::RobotPtr armarx::RobotIKWidgetController::loadRobot(StringList includePaths)
{
    try
    {
        std::string rfile = robotStateComponentPrx->getRobotFilename();
        ArmarXDataPath::getAbsolutePath(rfile, rfile, includePaths);
        return VirtualRobot::RobotIO::loadRobot(rfile);
    }
    catch (...)
    {
        ARMARX_ERROR << "Unable to load robot from file" << std::endl;
        return VirtualRobot::RobotPtr();
    }
}

void armarx::RobotIKWidgetController::manipFinishCallback(void* data, SoDragger* dragger)
{
    RobotIKWidgetController* controller = static_cast<RobotIKWidgetController*>(data);

    if (controller)
    {
        auto targetJointAngles = controller->getIKSolution();

        if (targetJointAngles.isReachable)
        {
            //Green
            controller->ui.reachableLabel->setText(QString::fromStdString("Pose reachable!"));
            controller->ui.reachableLabel->setStyleSheet("QLabel { color : green; }");
            controller->visualization->setColor(0, 1, 0);
        }
        else
        {
            //Red
            controller->ui.reachableLabel->setText(QString::fromStdString("Pose unreachable!"));
            controller->ui.reachableLabel->setStyleSheet("QLabel { color : red; }");
            controller->visualization->setColor(1, 0, 0);
        }

        //Display calculated error
        controller->ui.errorValue->setText(QString::fromStdString("Calculated error: " + boost::lexical_cast<std::string>(targetJointAngles.error)));
    }
}

void armarx::RobotIKWidgetController::manipMovedCallback(void* data, SoDragger* dragger)
{
    RobotIKWidgetController* controller = static_cast<RobotIKWidgetController*>(data);

    if (controller)
    {
        controller->manipulatorMoved = true;
    }
}

void armarx::RobotIKWidgetController::robotUpdateTimerCB(void* data, SoSensor* sensor)
{
    RobotIKWidgetController* controller = static_cast<RobotIKWidgetController*>(data);

    if (!controller || !controller->robotStateComponentPrx || !controller->robot)
    {
        return;
    }

    try
    {
        armarx::RemoteRobot::synchronizeLocalClone(controller->robot, controller->robotStateComponentPrx);

        if (controller->startUpCameraPositioningFlag)
        {
            controller->ui.robotViewer->getRobotViewer()->cameraViewAll();
            controller->startUpCameraPositioningFlag = false;
        }
    }
    catch (...) {};
}

void armarx::RobotIKWidgetController::textFieldUpdateTimerCB(void* data, SoSensor* sensor)
{
    RobotIKWidgetController* controller = static_cast<RobotIKWidgetController*>(data);

    if (!controller)
    {
        return;
    }

    if (controller->visualization->getIsVisualizing())
    {
        Eigen::Matrix4f tcpMatrix = controller->robot->getRobotNodeSet(controller->ui.comboBox->currentText().toStdString())->getTCP()->getPoseInRootFrame();
        FramedPose actualPose(tcpMatrix,
                              controller->robot->getRootNode()->getName(),
                              controller->robot->getName());
        //Set text label to tcp matrix
        controller->ui.currentPoseMatrix->setText(QString::fromStdString(actualPose.output()));

        //Set text label for desired tcp pose
        FramedPose desired(controller->robot->getRootNode()->toLocalCoordinateSystem(controller->visualization->getUserDesiredPose()),
                           controller->robot->getRootNode()->getName(),
                           controller->robot->getName());
        controller->ui.desiredPoseMatrix->setText(QString::fromStdString(desired.output()));
    }
}

void armarx::RobotIKWidgetController::autoFollowSensorTimerCB(void* data, SoSensor* sensor)
{
    RobotIKWidgetController* controller = static_cast<RobotIKWidgetController*>(data);

    if (controller && controller->manipulatorMoved)
    {
        controller->solveIK();
        controller->manipulatorMoved = false;
    }
}

armarx::ExtendedIKResult armarx::RobotIKWidgetController::getIKSolution()
{
    //    mat = robot->getRootNode()->toLocalCoordinateSystem(mat);
    return (robotIKPrx->computeExtendedIKGlobalPose(this->ui.comboBox->currentText().toStdString(), new armarx::Pose(visualization->getUserDesiredPose()),
            convertOption(this->ui.cartesianselection->currentText().toStdString())));
}

armarx::CartesianSelection armarx::RobotIKWidgetController::convertOption(std::string option)
{
    if (option == "Orientation and Position")
    {
        return eAll;
    }
    else if (option == "Position")
    {
        return ePosition;
    }
    else if (option == "Orientation")
    {
        return eOrientation;
    }
    else if (option == "X position")
    {
        return eX;
    }
    else if (option == "Y position")
    {
        return eY;
    }
    else if (option == "Z position")
    {
        return eZ;
    }

    return eAll;
}

Q_EXPORT_PLUGIN2(armarx_gui_RobotIKGuiPlugin, armarx::RobotIKGuiPlugin)

void armarx::RobotIKWidgetController::autoFollowChanged(bool checked)
{
    if (checked)
    {
        //Make a timer update robo pose every time tick
        SoSensorManager* sensor_mgr = SoDB::getSensorManager();
        autoFollowSensor = new SoTimerSensor(autoFollowSensorTimerCB, this);
        autoFollowSensor->setInterval(SbTime(AUTO_FOLLOW_UPDATE / 1000.0f));
        sensor_mgr->insertTimerSensor(autoFollowSensor);
    }
    else
    {
        SoSensorManager* sensor_mgr = SoDB::getSensorManager();
        sensor_mgr->removeTimerSensor(autoFollowSensor);
    }
}

void armarx::RobotIKWidgetController::on_btnCopyCurrentPoseToClipboard_clicked()
{

    if (visualization->getIsVisualizing())
    {

        Eigen::Matrix4f tcpMatrix = robot->getRobotNodeSet(ui.comboBox->currentText().toStdString())->getTCP()->getPoseInRootFrame();
        FramedPosePtr pose = new FramedPose(tcpMatrix, robot->getRootNode()->getName(), robot->getName());
        JSONObjectPtr obj = new JSONObject();
        obj->serializeIceObject(pose);


        QClipboard* clipboard = QApplication::clipboard();
        clipboard->setText(QString::fromStdString(obj->asString(true)));
    }
}